• Java常见ID生成方式比较


    1、为什么需要分布式全局唯一ID

            在单体架构环境下UUID或者auto_increment即可满足,保证ID的全局唯一,随着业务的发展,分布式微服务架构,导致UUID或者auto_increment不能保证全局的唯一,这就带来了需要生成全局唯一的分布式ID的需求。

    2、ID生成规则要求

    1、全局唯一,不能出现重复ID,用来标识唯一。
    2、趋势递增,Mysql的InnoDB引擎使用的是聚簇索引,由于多数RDBMS使用Btree的数据结构存储索引数据,主键有序可以提高写入性能。
    3、单调递增,主键ID单调递增,利于后续排序等功能。
    4、信息安全,连续的ID容易暴露交易信息,如果是订单号就可以推出订单量等信息,一些场景下的无规则才能保证信息安全,不容易泄露。
    5、含时间戳,时间戳有利于统计,问题定位分析等。

    3、分布式ID常见生成方案

    3.1、UUID

            UUID是Universally Unique Identifier的缩写,它是在一定的范围内(从特定的名字空间到全球)唯一的机器生成的标识符。

            UUID是16字节128位长的数字,通常以36字节的字符串表示,示例如下:

            3F2504E0-4F89-11D3-9A0C-0305E82C3301

            其中的字母是16进制表示,大小写无关。

    Java:jdk1.5以上支持UUID

    1. import java.util.UUID;
    2. String uuid = UUID.randomUUID().toString();

    优点:性能高,唯一性,没有网络消耗。
    缺点:无序,过长。

    3.2、数据库自增主键

    优点:有序,递增,唯一
    缺点:集群分布式下需要设置不同的增长步长。

    3.3、利用Redis生成id

    优点:Redis单线程天生保证原子性,可以使用INCR与INCRBY来实现,适合分布式集群,全局唯一,有序递增。
    缺点:要单独维护Redis集群,并保证高可用,维护成本高。与MySQL相同集群分布式下需要设置不同的增长步长同时key要设置有限期。比如一个集群5台Redis,初始化Redis值分别是1,2,3,4,5,然后步长都是5。

    1. import cn.jiqistudy.redis_1.Redis1Application;
    2. import cn.jiqistudy.redis_1.pojo.User;
    3. import cn.jiqistudy.redis_1.service.UserService;
    4. import org.junit.Test;
    5. import org.junit.runner.RunWith;
    6. import org.slf4j.Logger;
    7. import org.slf4j.LoggerFactory;
    8. import org.springframework.beans.factory.annotation.Autowired;
    9. import org.springframework.boot.test.context.SpringBootTest;
    10. import org.springframework.data.redis.core.StringRedisTemplate;
    11. import org.springframework.test.context.junit4.SpringRunner;
    12. @RunWith(SpringRunner.class)
    13. @SpringBootTest(classes = Redis1Application.class)
    14. public class Test_8 {
    15. private static final Logger log = LoggerFactory.getLogger(UserService.class);
    16. @Autowired
    17. private StringRedisTemplate stringRedisTemplate;
    18. private static final String ID_KEY = "id:generator:user";
    19. /**
    20. * 生成全局唯一id
    21. */
    22. @Test
    23. public void incrementId() {
    24. for (int i = 0; i <100 ; i++) {
    25. //步骤1:生成分布式id
    26. long id=this.stringRedisTemplate.opsForValue().increment(ID_KEY);
    27. System.out.println(id);
    28. //全局id,代替数据库的自增id
    29. User user = new User();
    30. user.setId(id);
    31. //步骤2:取模,计算表名
    32. //类似于海量的数据,例如淘宝一般是分为1024张表,这里为了演示方便,只分为8张表。
    33. int table=(int)id % 8;
    34. String tablename="user_"+table;
    35. log.info("插入表名{},插入内容{}",tablename,user);
    36. }
    37. }
    38. }

    3.4、雪花算法

            snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。

            使用hutool包生成:

    1. <dependency>
    2. <groupId>cn.hutoolgroupId>
    3. <artifactId>hutool-allartifactId>
    4. <version>5.4.2version>
    5. dependency>
    cn.hutool.core.lang.Snowflake
    
    1. public synchronized long nextId() {
    2. // 获取当前时间戳
    3. long timestamp = genTime();
    4. // lastTimestamp表示你的程序在最后一次获取分布式唯一标识的时间戳(ms)
    5. // 一台机器正常情况下,timestamp 是要大于 lastTimestamp的.如果timestamp < lastTimestamp表明服务器的时间有问题,存在时钟后退.
    6. if (timestamp < lastTimestamp) {
    7. // 容忍2秒内的时钟后退
    8. if(lastTimestamp - timestamp < 2000){
    9. timestamp = lastTimestamp;
    10. } else{
    11. // 如果服务器时间有问题(时钟后退) 报错。
    12. throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp));
    13. }
    14. }
    15. if (timestamp == lastTimestamp) {
    16. // 相同毫秒内,序列号自增
    17. sequence = (sequence + 1) & sequenceMask;
    18. // 同一毫秒的序列数已经达到最大
    19. if (sequence == 0) {
    20. // 循环等待下一个时间
    21. timestamp = tilNextMillis(lastTimestamp);
    22. }
    23. } else {// timestamp > lastTimestamp
    24. // 不同毫秒内, 序列号置为0
    25. sequence = 0L;
    26. }
    27. lastTimestamp = timestamp;
    28. // 通过按位或将各个部分拼接起来
    29. return ((timestamp - twepoch) << timestampLeftShift) // 时间戳部分
    30. | (dataCenterId << dataCenterIdShift) // 数据中心部分
    31. | (workerId << workerIdShift) // 机器标识部分
    32. | sequence; // 序列号部分
    33. }

    优点:

    (1)高性能高可用:生成时不依赖于数据库,完全在内存中生成。

    (2)容量大:每秒中能生成数百万的自增ID。

    (3)节省空间:生成64位id,只占用8个字节节省存储空间。

    (4)ID趋势自增:存入数据库中,索引效率高。

    缺点:依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能会造成id冲突或者重复。

    3.5、美团Leaf分布式ID生成系统

    ①Leaf-segment数据库方案

            在使用数据库的方案上,做了如下改变:

            - 原方案每次获取ID都得读写一次数据库,造成数据库压力大。改为利用proxy server批量获取,每次获取一个segment(step决定大小)号段的值。用完之后再去数据库获取新的号段,可以大大的减轻数据库的压力。

            - 各个业务不同的发号需求用biz_tag字段来区分,每个biz-tag的ID获取相互隔离,互不影响。如果以后有性能需求需要对数据库扩容,不需要上述描述的复杂的扩容操作,只需要对biz_tag分库分表就行。

            下表为数据库设计:

    1. +-------------+--------------+------+-----+-------------------+-----------------------------+
    2. | Field | Type | Null | Key | Default | Extra |
    3. +-------------+--------------+------+-----+-------------------+-----------------------------+
    4. | biz_tag | varchar(128) | NO | PRI | | |
    5. | max_id | bigint(20) | NO | | 1 | |
    6. | step | int(11) | NO | | NULL | |
    7. | desc | varchar(256) | YES | | NULL | |
    8. | update_time | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
    9. +-------------+--------------+------+-----+-------------------+-----------------------------+

            重要字段说明:biz_tag用来区分业务,max_id表示该biz_tag目前所被分配的ID号段的最大值,step表示每次分配的号段长度。原来获取ID每次都需要写数据库,现在只需要把step设置得足够大,比如1000。那么只有当1000个号被消耗完了之后才会去重新读写一次数据库。读写数据库的频率从1减小到了1/step。

    架构图:

    在这里插入图片描述

             test_tag在第一台Leaf机器上是1-1000的号段,当这个号段用完时,会去加载另一个长度为step=1000的号段,假设另外两台号段都没有更新,这个时候第一台机器新加载的号段就应该是3001~4000。同时数据库对应的biz_tag这条数据的max_id会从3000被更新成4000,更新号段的SQL语句如下:
     

    1. Begin
    2. UPDATE table SET max_id=max_id+step WHERE biz_tag=xxx
    3. SELECT tag, max_id, step FROM table WHERE biz_tag=xxx
    4. Commit

    优点:

    • Leaf服务可以很方便的线性扩展,性能完全能够支撑大多数业务场景。
    • ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求。
    • 容灾性高:Leaf服务内部有号段缓存,即使DB宕机,短时间内Leaf仍能正常对外提供服务。
    • 可以自定义max_id的大小,非常方便业务从原有的ID方式上迁移过来。
       

    缺点:

    • ID号码不够随机,能够泄露发号数量的信息,不太安全。
    • TP999数据波动大,当号段使用完之后还是会hang在更新数据库的I/O上,tg999数据会出现偶尔的尖刺。
    • DB宕机会造成整个系统不可用。

    ②Leaf-snowflake方案

            Leaf-segment方案可以生成趋势递增的ID,同时ID号是可计算的,不适用于订单ID生成场景,比如竞对在两天中午12点分别下单,通过订单id号相减就能大致计算出公司一天的订单量,这个是不能忍受的。面对这一问题,我们提供了 Leaf-snowflake方案。

            Leaf-snowflake方案完全沿用snowflake方案的bit位设计,即是“1+41+10+12”的方式组装ID号。对于workerID的分配,当服务集群数量较小的情况下,完全可以手动配置。Leaf服务规模较大,动手配置成本太高。所以使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID。Leaf-snowflake是按照下面几个步骤启动的:

    • 启动Leaf-snowflake服务,连接Zookeeper,在leaf_forever父节点下检查自己是否已经注册过(是否有该顺序子节点)。
    • 如果有注册过直接取回自己的workerID(zk顺序节点生成的int类型ID号),启动服务。
    • 如果没有注册过,就在该父节点下面创建一个持久顺序节点,创建成功后取回顺序号当做自己的workerID号,启动服务。

    在这里插入图片描述

     

            但Leaf-snowflake对Zookeeper是一种弱依赖关系,除了每次会去ZK拿数据以外,也会在本机文件系统上缓存一个workerID文件。一旦ZooKeeper出现问题,恰好机器出现故障需重启时,依然能够保证服务正常启动。

            启动Leaf-snowflake模式也比较简单,起动本地ZooKeeper,修改一下项目中的leaf.properties文件,关闭leaf.segment模式,启用leaf.snowflake模式即可。

    1. leaf.segment.enable=false
    2. #leaf.jdbc.url=jdbc:mysql://127.0.0.1:3306/xin-master?useUnicode=true&characterEncoding=utf8
    3. #leaf.jdbc.username=junkang
    4. #leaf.jdbc.password=junkang
    5. leaf.snowflake.enable=true
    6. leaf.snowflake.zk.address=127.0.0.1
    7. leaf.snowflake.port=2181
    1. /**
    2. * 雪花算法模式
    3. * @param key
    4. * @return
    5. */
    6. @RequestMapping(value = "/api/snowflake/get/{key}")
    7. public String getSnowflakeId(@PathVariable("key") String key) {
    8. return get(key, snowflakeService.getId(key));
    9. }

    优点:ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求。

    缺点:依赖ZooKeeper,存在服务不可用风险

    参考:美团(Leaf)分布式ID算法_wh柒八九的博客-CSDN博客_美团leaf算法

    微服务 分布式ID生成方式雪花算法_靖节先生的博客-CSDN博客_分布式id生成雪花算法

    redis 实现分布式全局唯一id_小哇666的博客-CSDN博客_redis生成全局唯一id

    hutool工具中的雪花算法_书唐瑞的博客-CSDN博客_hutool雪花算法

  • 相关阅读:
    2023下半年北京软考高项-系统架构设计师-考试心得分享
    文件包含漏洞学习小结
    7-tcp 三次握手和四次挥手、osi七层协议,哪七层,每层有哪些?tcp和udp的区别?udp用在哪里了?
    Word处理控件Aspose.Words功能演示:使用 Python 将 Word 文档的内容复制到另一个文档
    ubuntu搭建MongoDB副本集
    Java之XML解析-使用dom(org.w3c.dom)解析XML
    ORB SLAM3 使用二进制文件 ORBvoc.bin 加载Vocabulary
    Linux:syslog()的使用和示例
    数字IC手撕代码--投票表决器
    springboot 多环境配置(pom配置Profiles变量来,控制打包环境)
  • 原文地址:https://blog.csdn.net/qq_42900213/article/details/126174057