在项目开发中,随着业务的不断发展数据日增增大。这个时候就会出现数据表的分库分表的操作。一旦进行了分库和分表操作。传统的id就失去了意义。所以需要分布式全局ID。
分布式全局ID的特点
1:全局唯一性,不能出现重复的ID
2:单调递增,保证下一个ID一定大于上一个ID
3:范围趋势递增。在一个时间段内,生成的ID是递增趋势的比如:202012120001 202012120002….
第二天的时候又要从1开始计数。202012130001 202012130002……
4:安全性,在不同的领域中我们有些业务不要出现连续的递增,可以很好的保护数据格式和形态。因为很容易让竞争对手套取数据。
上述的2和4是互斥的。无法同时满足。
实现方式
- UUID
- Redis
- Twitter的雪花算法
- 美团的Leaf算法
redis实现分布式全局唯一ID
第一种:单调递增的的具体实现:
就我个人看来,redisAtomicLong做分布式ID还是有一定的局限性。单纯针对redisAtomicLong数据丢失的解决方案,如果有什么好的建议,期待您的继续分享
思路:调用incr命令
1:Redis的incr命令具备了incr and get的原子性操作,即增加了结果的原子性操作,这个原子性操作很方便实现了唯一的全局分布式ID。
2:Redis本身是单线程架构。INCR命令不会出现重复的ID
代码:
1:采用redis的incr命令,从1自增id.
2:海量的数据存储一般都采用分库分表来进行存储。一般最多分1024个表,一般建表如:product_0,product_1,product_2……product_1023个表。
service
package com.kuangstudy.service.creatorid;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Service;
@Service
@Log4j2
public class RedisCreatorIdService {
@Autowired
private RedisTemplate redisTemplate;
/**
* @Description 生成全局id
* @Date 18:50 2021/5/20
**/
public Long incrmentid() {
RedisAtomicLong entityIdCounter = new RedisAtomicLong("id:creator:product", redisTemplate.getConnectionFactory());//0
// 计数器累加
Long increment = entityIdCounter.incrementAndGet();
if (null == incr || incr.longValue() == 0) {// 初始设置过期时间
// 设置过期时间 24小时
entityIdCounter.expire(24, TimeUnit.HOURS);
// 这里取第二次 incr 就是从1开始了,默认从0开始
incr = entityIdCounter.getAndIncrement();
}
log.debug(keyName + "==========" + incr);
return incr;
// 或者
//Long increment = redisTemplate.opsForValue().increment(key);
//return increment;
}
}
controller
package com.kuangstudy.controller.creatorid;
import com.kuangstudy.entity.Product;
import com.kuangstudy.service.creatorid.RedisCreatorIdService;
import com.kuangstudy.vo.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
@Autowired
private RedisCreatorIdService redisCreatorIdService;
@PostMapping("/product/creator")
public R creatorIds() {
// 1:创建产品的分布式全局ID
Long productid = redisCreatorIdService.incrmentid();
// 2:创建产品
Product product = new Product();
product.setId(productid);
product.setTitle("title:" + productid);
System.out.println("保存产品是:" + product.toString());
// 3: 存储到指定的数据库中
int table = (int) (productid % 1024);
System.out.println("保存到的数据库表是:product_" + table);
return R.ok().data("tablename", "product_" + table);
}
}
基于范围的规则递增的分布式全局id
比如:系统需要生成根据业务类型生成流水号,每天从1开始生成,第二天会清零继续从0开始,流水号格式为: bizCode + date + incr 如:TT-201711230000100。
思路:
1、利用Redis Incr 生成序列号,使用日期加业务编码作为组合Key,这样保证第二天生成的序列号又是从1开始。
2、由于我们业务量不是很大,这里在生成序列号之前先判断一下当前key是否存在,若不存在,设置此key过期时间为当天晚上23:59:59,避免生成很多过期的key。
service
package com.itheima.itheimadistributelock.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class CacheService {
//这里因为有其他的template,所以名字起得不好看
@Autowired
RedisTemplate redisTemplate;
public Long getIncrementNum(String key) {
// 不存在准备创建 键值对
RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());//0
// 计数器累加
Long counter = entityIdCounter.incrementAndGet();
System.out.println("=========================>"+ counter);
if ((null == counter || counter.longValue() == 1)) {// 初始设置过期时间
System.out.println("设置过期时间为1天!");
// 设置清除的目的,是让每天的计数器都从0开始
entityIdCounter.expire(1, TimeUnit.DAYS);// 单位天
}
return counter;
}
}
utils
package com.kuangstudy.service.creatorid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @Author 徐柯
* @Description 统一订单流水号
* @Date 19:29 2021/5/20
* @Param
* @return
**/
@Component
public class SequenceUtils {
@Autowired
CacheService cacheService;
// 1:每天公司的订单数量,如果一天是几百写 3 几千就 4 几万 5
static final int DEFAULT_LENGTH = 3;
public String getSequence() {
// 1: 时间前缀
String currentDate = new SimpleDateFormat("yyyyMMdd").format(new Date());
// 2:redis递增获取每天的递增数量
Long num = cacheService.getIncrementNum("id:generator:order:" + currentDate);
// 3:获取递增长度,是否小于DEFAULT_LENGTH 如果小于就前面补零。如果大于就递增即可
String str = String.valueOf(num);
// 4:补零操作
int len = str.length();
// 4-1:是否小于DEFAULT_LENGTH 如果小于就前面补零。如果大于就递增即可
StringBuilder sb = new StringBuilder();
// 5:添加时间
sb.append(currentDate);
if (len >= DEFAULT_LENGTH) {
sb.append(str);
return sb.toString();
}
int rest = DEFAULT_LENGTH - len;
for (int i = 0; i < rest; i++) {
sb.append('0');
}
sb.append(str);
// 6: 时间+补零操作返回订单流水号
return sb.toString();
}
}
controller
package com.kuangstudy.controller.creatorid;
/**
* @author 飞哥
* @Title: 学相伴出品
* @Description: 我们有一个学习网站:https://www.kuangstudy.com
* @date 2021/5/20 18:46
*/
@RestController
public class ProductController {
@Autowired
private SequenceUtils sequenceUtils;
@PostMapping("/product/creator2")
public R creatorIds2() {
// 1:创建产品的分布式全局ID
List<String> ids = new ArrayList<>();
for (int i = 0; i < 100; i++) {
String sequence = sequenceUtils.getSequence();
ids.add(sequence);
}
return R.ok().data("ids", ids);
}
}