雪花算法生成ID

博客围绕数据库ID设计展开,提到将自增id和UUID换成一个字段,找到雪花算法。介绍了ID生成器在单机和分布式下的使用情况,给出分布式下分配workId的三种方案,还给出Spring boot项目实例化ID生成器及基于apache curator的ZkClient代码,并指出版本匹配问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言
我们的数据库在设计时一般有两个ID,自增的id为主键,还有一个业务ID使用UUID生成。自增id在需要分表的情况下做为业务主键不太理想,所以我们增加了uuid作为业务ID,有了业务id仍然还存在自增id的原因具体我也说不清楚,只知道和插入的性能以及db的要求有关。

我个人一直想将这两个ID换成一个字段来处理,所以要求这个id是数字类似的,且是趋抛增长的,这样mysql创建索引以及查询时性能会比较好。于时网上找到了雪花算法.关于雪花算法大家可以看一下我后面引用的资料。

ID生成器代码:
从网上抄的,自己改的,目前我还没有应用到实际项目中,如需应用,请先进行严格自测

  1  
  2 import java.time.LocalDateTime;
  3 import java.time.ZoneOffset;
  4 import java.time.format.DateTimeFormatter;
  5  
  6 /**
  7  * <p>
  8  *  在雪花算法基础生稍做改造生成Long Id
  9  *  https://www.jianshu.com/p/d3881a6a895e
 10  * </p>
 11  * 1 - 41位 - 10位 - 12位
 12  * 0 - 41位 - 10位 - 12位
 13  * <p>
 14  * <PRE>
 15  * <BR>    修改记录
 16  * <BR>-----------------------------------------------
 17  * <BR>    修改日期         修改人          修改内容
 18  * </PRE>
 19  *
 20  * @author cuiyh9
 21  * @version 1.0
 22  * @Date Created in 2018年11月29日 20:46
 23  * @since 1.0
 24  */
 25 public final class ZfIdGenerator {
 26  
 27     /**
 28      * 起始的时间戳
 29      */
 30     private static final long START_TIME_MILLIS;
 31  
 32     /**
 33      * 每一部分占用的位数
 34      */
 35     private final static long SEQUENCE_BIT = 12; //序列号占用的位数
 36     private final static long WORKID_BIT = 10;   //机器标识占用的位数
 37  
 38     /**
 39      * 每一部分的最大值
 40      */
 41     private final static long MAX_WORK_NUM = -1L ^ (-1L << WORKID_BIT);
 42     private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
 43  
 44     /**
 45      * 每一部分向左的位移
 46      */
 47     private final static long WORKID_SHIFT = SEQUENCE_BIT;
 48     private final static long TIMESTMP_SHIFT = WORKID_SHIFT + WORKID_BIT;
 49  
 50     private long sequence = 0L; //序列号
 51     private long lastStmp = -1L;
 52  
 53     /** workId */
 54     private long workId;
 55  
 56     static {
 57         String startDate = "2018-01-01 00:00:00";
 58         DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 59         LocalDateTime localDateTime = LocalDateTime.parse(startDate, df);
 60         START_TIME_MILLIS = localDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
 61  
 62     }
 63  
 64  
 65  
 66  
 67     /**
 68      * 获取分部署式发号器
 69      * @param workId 每台服务需要传一个服务id
 70      * @return
 71      */
 72     public static synchronized ZfIdGenerator getDistributedIdGenerator(long workId) {
 73         return new ZfIdGenerator(workId);
 74     }
 75  
 76     public static synchronized ZfIdGenerator getStandAloneIdGenerator() {
 77         long workId = MAX_WORK_NUM;
 78         return new ZfIdGenerator(workId);
 79     }
 80  
 81  
 82     private ZfIdGenerator(long workId) {
 83         if (workId > MAX_WORK_NUM || workId <= 0) {
 84             throw  new RuntimeException("workdId的值设置错误");
 85         }
 86         this.workId = workId;
 87     }
 88  
 89     /**
 90      * 生成id
 91      * @return
 92      */
 93     public synchronized long nextId() {
 94         long currStmp = System.currentTimeMillis();
 95         if (currStmp < START_TIME_MILLIS) {
 96             throw new RuntimeException("机器时间存在问题,请注意查看");
 97         }
 98  
 99         if (currStmp == lastStmp) {
100             sequence =  (sequence + 1) & MAX_SEQUENCE;
101             if (sequence == 0L) {
102                 currStmp = getNextMillis(currStmp);
103             }
104         } else {
105             sequence = 0L;
106         }
107         lastStmp = currStmp;
108  
109         return ((currStmp - START_TIME_MILLIS) << TIMESTMP_SHIFT)
110                 | (workId << WORKID_SHIFT)
111                 | (sequence);
112     }
113  
114     public long getNextMillis(long currStmp) {
115         long millis = System.currentTimeMillis();
116         while (millis <= currStmp) {
117             millis = System.currentTimeMillis();
118         }
119         return millis;
120     }
121  
122     /**
123      * 获取最大的工作数量
124      * @return
125      */
126     public static long getMaxWorkNum() {
127         return MAX_WORK_NUM;
128     }
129  
130     public static void main(String[] args) {
131         ZfIdGenerator idGenerator1 = ZfIdGenerator.getDistributedIdGenerator(1);
132 //        ZfIdGenerator idGenerator2 = ZfIdGenerator.getDistributedIdGenerator(2);
133         for (int i = 0; i < 1000000; i++) {
134             System.out.println(idGenerator1.nextId());
135         }
136  
137 //        System.out.println(idGenerator2.nextId());
138  
139  
140  
141     }
142  
143 }

 

分布式情况
上面的ID生成器在单机情况下使用没有问题,但如果在分布下使用,就需要分配不同的workId,如果workId相同,可能会导致生成的id相同。

解决方案:

1、使用java环境变量,人为通过-D预先设置workid.这种方案简单,不会出现重复情况,但需要每个服务的启动脚本不同.

2、使用sharding-jdbc中的算法,使用IP后几位来做workId,这种方案也很简单,不需要修改服务的启动脚本,但在某些情况下会出现生成重复ID的情况,详细见我下面的参考资料

3、使用zk,在启动时给每个服务分配不同的workId,缺点:多了依赖,需要zk,优点:不会出现重复情况,且不需要修改服务的启动脚本。这个是我个人使用的方案,实现思路为,系统启动时创建一个永久性的结点(zookeeper保证原子性),然后在这个永久性的节点下,遍历workId去zookeeper创建临时结点,zookeeper会保证相同路径只会有一个可能创建成功,如果创建失败继续遍历即可。详细可看一下代码

实例化ID生成器如下(Spring boot项目):

 1  
 2 import lombok.extern.slf4j.Slf4j;
 3 import org.springframework.beans.factory.annotation.Autowired;
 4 import org.springframework.beans.factory.annotation.Value;
 5 import org.springframework.boot.SpringBootConfiguration;
 6 import org.springframework.context.annotation.Bean;
 7  
 8 /**
 9  * <p>TODO</p>
10  * <p>
11  * <PRE>
12  * <BR>    修改记录
13  * <BR>-----------------------------------------------
14  * <BR>    修改日期         修改人          修改内容
15  * </PRE>
16  *
17  * @author cuiyh9
18  * @version 1.0
19  * @Date Created in 2018年11月30日 16:37
20  * @since 1.0
21  */
22 @Slf4j
23 @SpringBootConfiguration
24 public class IdGeneratorConfig {
25  
26     @Autowired
27     private ZkClient zkClient;
28  
29     @Value("${idgenerator.zookeeper.parent.path}")
30     private String IDGENERATOR_PARENT_PATH;
31  
32     @Bean
33     public ZfIdGenerator idGenerator() {
34         boolean flag = zkClient.createParent(IDGENERATOR_PARENT_PATH);
35         if (!flag) {
36             throw new RuntimeException("创建发号器父节点失败");
37         }
38  
39         // 获取workId
40         long workId = 0;
41         long maxWorkNum = ZfIdGenerator.getMaxWorkNum();
42         for (long i = 1; i < maxWorkNum; i++) {
43             String workPath = IDGENERATOR_PARENT_PATH + "/" + i;
44             flag = zkClient.createNotExistEphemeralNode(workPath);
45             if (flag) {
46                 workId =  i;
47                 break;
48             }
49         }
50  
51         if (workId == 0) {
52             throw new RuntimeException("获取机器id失败");
53         }
54         log.warn("idGenerator workId:{}", workId);
55         return ZfIdGenerator.getDistributedIdGenerator(workId);
56  
57     }
58 }

ZkClient代码(基于apache curator)

注意apache curator版本,我最初使用的是4.x版本,程序执行到forPath()方法就会阻塞,后来查到是与zookeeper版本不匹配导致.

 1  
 2 import lombok.extern.slf4j.Slf4j;
 3 import org.apache.curator.framework.CuratorFramework;
 4 import org.apache.zookeeper.CreateMode;
 5 import org.apache.zookeeper.KeeperException;
 6 import org.springframework.beans.factory.annotation.Autowired;
 7 import org.springframework.stereotype.Component;
 8  
 9 /**
10  * <p>TODO</p>
11  * <p>
12  * <PRE>
13  * <BR>    修改记录
14  * <BR>-----------------------------------------------
15  * <BR>    修改日期         修改人          修改内容
16  * </PRE>
17  *
18  * @author cuiyh9
19  * @version 1.0
20  * @Date Created in 2018年11月30日 16:36
21  * @since 1.0
22  */
23 @Slf4j
24 @Component
25 public class ZkClient {
26  
27     @Autowired
28     private CuratorFramework client;
29  
30     /**
31      * 创建父节点,创建成功或存在都返回成功
32      * @param path
33      * @return
34      */
35     public boolean createParent(String path) {
36         try {
37             client.create().creatingParentsIfNeeded().forPath(path);
38             return true;
39         } catch (KeeperException.NodeExistsException e) {
40             return true;
41         } catch (Exception e) {
42             log.error("createParent fail path:{}", path, e);
43         }
44         return false;
45     }
46  
47     /**
48      * 创建不存在的节点。如果存在或创建失败,返回false
49      * @param path
50      * @throws Exception
51      */
52     public boolean createNotExistEphemeralNode(String path) {
53         try {
54             client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
55             return true;
56         } catch (KeeperException.NodeExistsException e) {
57             return false;
58         }  catch (Exception e) {
59             log.error("createNotExistNode fail path:{}", path, e);
60         }
61         return false;
62     }
63 }

 

转载于:https://www.cnblogs.com/Jeremy2001/p/10557693.html

使用Mybatis-Plus生成雪花算法生成id非常简单。Mybatis-Plus已经内置了雪花算法生成分布式唯一id的功能。你可以在IDEA中双击shift搜索Sequence类来查看具体的实现代码。这个类使用的就是雪花算法生成id。关于如何在项目中使用雪花算法生成id,你可以参考优快云上的一篇博文《mybatis-plus雪花算法增强idworker》。这篇博文详细介绍了如何在Mybatis-Plus中配置和使用雪花算法生成id。你可以按照这篇博文的步骤进行操作,非常简单易懂。总结起来,使用Mybatis-Plus生成雪花算法生成id的步骤包括建表、新建测试工程和单元测试等。在实现分析中,你可以了解到为什么Mybatis-Plus默认就是使用雪花算法生成id。此外,你还可以通过主动设置id生成策略来使用Mybatis-Plus生成雪花算法生成id。Mybatis-Plus还提供了内置的雪花算法工具类IdWorker,方便你在项目中使用雪花算法生成id。希望这些信息对你有帮助!<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [mybatis-plus雪花算法生成Id使用详解](https://blog.youkuaiyun.com/w1014074794/article/details/125604191)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值