基于Redis实现全局唯一Id

 微信公众号访问地址基于Redis实现全局唯一Id

推荐文章:

    1、使用原生Redis命令实现分布式锁

​    2、为什么引入Redisson分布式锁?

    3、SpringBoot整合多数据源,并支持动态新增与切换(详细教程)

    4、SpringBoot统一标准响应格式及异常处理

   5、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据

一、简介

使用数据库自增ID就存在一些问题:

    1、受单表自增数量的限制;

      原因:mysql单表的容量不宜超过500W条,数据量过大之后,就需要进行拆库拆表,但拆分表之后,它们从逻辑上讲仍然是同一张表,所以他们的id是不能一样的(不同表,若使用自增ID,是可能一样的),所以随着我们的业务数据越来越大,我们需要保证id的唯一性。

    2、id的规律性太明显。

      原因:自增id具有太明显的规则,用户或者说商业对手很容易猜测出来一些敏感信息,例如:在一天时间内,我们卖出了多少单,这明显不合适。

二、全局唯一ID生成策略

      一般要满足下列特性:

      为了增加ID的安全性,可以不直接使用Redis自增的数值,而是拼接一些其它信息:

组成说明:

    1、符号位:1bit,永远为0;

    2、时间戳(31 Bit):31bit,以秒为单位,可以使用69年;

    3、序列号:32bit,秒内的计数器,支持每秒产生2^32个不同ID。

三、基于Redis实现全局唯一Id案例

    原理:基于Redis 的INCR 命令生成分布式全局唯一id。INCR 命令主要有以下2个特征:

      1、具备了“INCR AND GET”的原子操作,即:增加并返回结果的原子操作。这个原子性很方便我们实现获取ID。

      2、Redis是单进程单线程架构,INCR命令不会出现id重复。

3.1、构建RedisIdUtils类

/**
 * 功能描述:使用redis生成全局唯一ID
 * @Author: yyalin
 * @CreateDate: 2023/8/13 11:35
 */
@Component
public class RedisIdUtils {
    //预生成开始时间戳
    private static final long BEGIN_TIMESTAMP = 1640995200L;
    //序列号的位数
    private static final int COUNT_BITS = 32;
    //redis提供的字符串
    private StringRedisTemplate stringRedisTemplate;
    //有参构造函数
    public RedisIdUtils(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
    /**
     * 功能描述:根据keyPrefix前缀生成对应业务的全局唯一ID
     * @MethodName: nextId
     * @MethodParam: [keyPrefix:使用前缀来区分不同的业务]
     * @Return: long
     * @Author: yyalin
     * @CreateDate: 2023/8/13 12:20
     */
    public long nextId(String keyPrefix) {
        // 1.生成时间戳
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timestamp = nowSecond - BEGIN_TIMESTAMP;
        // 2.生成序列号
        // 2.1.获取当前日期,精确到天
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        // 2.2.自增长 icr:order:2023:08:13
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
        // 3.拼接并返回  timestamp << COUNT_BITS :向左移动32位
        //原本时间戳在低位上,通过向左移动32位,变位到高位存储,低32位都是0,然后与自增序列按位操作
        //形成低32位为序列号。
        return timestamp << COUNT_BITS | count;
    }
}

3.2、构建多线程测试类

** * @Description: TODO:测试RedisIdUtils * @Author: yyalin * @CreateDate: 2023/8/13 12:27 * @Version: V1.0 */@Service@Slf4jpublic class TestRedisIdUtils {    @Autowired    private RedisIdUtils redisIdUtils;    //使用自定义的线程池    private ExecutorService executorService = Executors.newFixedThreadPool(500);    /**     * 功能描述:使用多线程测试生成40000条数据耗时     * @MethodName: testIdWorker     * @MethodParam: [nums]     * @Return: void     * @Author: yyalin     * @CreateDate: 2023/8/13 12:36     */    public void testIdWorker(int nums) throws InterruptedException {        //同步协调在多线程的等待于唤醒问题 分线程全部走完之后,主线程再走        CountDownLatch latch = new CountDownLatch(nums);        Runnable task = () -> {            for (int i = 0; i < 100; i++) {                long id = redisIdUtils.nextId("order");                System.out.println("id = " + id);            }            latch.countDown();        };        long begin = System.currentTimeMillis();        for (int i = 0; i < nums; i++) {            executorService.submit(task);        }        //阻塞方法 让main线程阻塞        latch.await();        long end = System.currentTimeMillis();        log.info("生成"+nums*100+"条id共计耗时(毫秒) = " + (end - begin));    }}

3.3、测试结果

序列
 
线程数条数耗时(秒)
1500300000.781
2500400000.993
3500500001.164

      总结:从测试结果不难看出,基于redis实现全局唯一ID,性能还是非常高的,并且耗时非常短的。

更多优秀文章,请关注个人微信公众号或搜索“程序猿小杨”查阅。

参考文章:

https://blog.csdn.net/weixin_43811057/article/details/130798254

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值