雪花算法-生成分布式ID

背景

随着现在业务量的越来越大,数据库的划分也变的越来越细,分库分表的理念也渐渐的落地,自增主键或者序列之类的主键id生成方式已经不再满足需求,所以分布式ID的生成就应运而生,总的来说就是生成规则更加负责,减少重复的概率。

一、雪花算法

雪花算法的原始版本是scala版,用于生成分布式ID(纯数字,时间顺序),订单编号等。

自增ID:对于数据敏感场景不宜使用,且不适合于分布式场景。
GUID:采用无意义字符串,数据量增大时造成访问过慢,且不宜排序。

算法描述:

  • 最高位是符号位,始终为0,不可用。
  • 41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。
  • 10位的机器标识,10位的长度最多支持部署1024个节点。
  • 12位的计数序列号,序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。

 二、时间部分

时间部分的逻辑起始很简单,就是规定一个起始时间戳,然后用当前时间戳减去起始时间戳,这两个数的差就是我们要的结果,但是有一个问题如果系统的时间出错了怎么办,毕竟我们正常取的都是服务器的时间,所以需要校验


 
 
  1. //如果当前时间小于上次ID生成的时间,说明系统回退过抛出异常
  2. if (now < LAST_TIME_STAMP) {
  3. log.info( "系统时间异常,请检查!");
  4. throw new RuntimeException( "系统时间异常!");
  5. }

三、机器信息部分

机器信息,占10位。我们这里把机器信息分成两部分,一部分是数据中心id,占5位,一部分是机器id,占5位。这两个id可以在部署项目的时候根据不同的机器自定义不同的id,这样能人为的保障每个id都不同。jdk库中,有api可以获取本地机器的hostname和hostaddress,可以把hostname的信息作为数据中心id,把hostaddress的信息作为机器id,如何把两个字符串改为两个数字id呢?其实很简单。获取字符串的字节数组,然后把数组的每个数字相加,对节点数的最大值取余。因为都是5位,所以最大值是31,需要对32取余。那么雪花算法可以部署的机器总数就是32*32=1024个,这是机器信息的限制。


 
 
  1. private static int getDataId() {
  2. try {
  3. return getHostId(Inet4Address.getLocalHost().getHostName(), DATA_MAX_NUM);
  4. } catch (UnknownHostException e) {
  5. return new Random().nextInt(DATA_RANDOM);
  6. }
  7. }
  8. private static int getWorkId() {
  9. try {
  10. return getHostId(Inet4Address.getLocalHost().getHostAddress(), WORK_MAX_NUM);
  11. } catch (UnknownHostException e) {
  12. return new Random().nextInt(WORK_RANDOM);
  13. }
  14. }
  15. private static int getHostId(String str, int max) {
  16. byte[] bytes = str.getBytes();
  17. int sums = 0;
  18. for ( int b : bytes) {
  19. sums += b;
  20. }
  21. return sums % (max + 1);
  22. }

四、毫秒内序列

毫秒内的序列。什么意思呢?我们在生成时间部分获取时间戳的时候,使用 long now = System.currentTimeMillis(); 获取,是个毫秒级的时间戳,但是即使是这么短的时间,对于电脑来说也足够生成很多个id,所以很多id可能会在同一个毫秒内生成,也就是时间部分的数值一样。这个时候就要让同一个毫秒内生成的id加上数字序列标识,就是第三部分的序列。第三部分占的长度是12位,转成整数值就是4095,所以最后一部分的范围就是4095到0之间的数字。如果毫秒内访问的数量超过了这个限制怎么办?没法解决,只能强制等到下一毫秒再生产id。

五、实战

上面的讲述我们基本可以写出方法了,但是考虑到多线程,我们需要加锁保证。下面是完整代码。


 
 
  1. @Slf4j
  2. public class SnowflakeUtil {
  3. /**
  4. * 时间部分所占长度
  5. */
  6. private static final int TIME_LEN = 41;
  7. /**
  8. * 数据中心id所占长度
  9. */
  10. private static final int DATA_LEN = 5;
  11. /**
  12. * 机器id所占长度
  13. */
  14. private static final int WORK_LEN = 5;
  15. /**
  16. * 毫秒内序列所占长度
  17. */
  18. private static final int SEQ_LEN = 12;
  19. /**
  20. * 定义起始时间 2015-01-01 00:00:00
  21. */
  22. private static final long START_TIME = 1420041600000L;
  23. /**
  24. * 上次生成id的时间戳
  25. */
  26. private static long LAST_TIME_STAMP = -1L;
  27. /**
  28. * 时间部分向左移动的位数 22
  29. */
  30. private static final int TIME_LEFT_BIT = 64 - 1 - TIME_LEN;
  31. /**
  32. * 自动获取数据中心id(可以手动定义0-31之间的任意数)
  33. */
  34. private static final long DATA_ID = getDataId();
  35. /**
  36. * 自动获取机器id(可以手动定义0-31之间的任意数)
  37. */
  38. private static final long WORK_ID = getWorkId();
  39. /**
  40. * 数据中心id最大值 31
  41. */
  42. private static final int DATA_MAX_NUM = ~( -1 << DATA_LEN);
  43. /**
  44. * 机器id最大值 31
  45. */
  46. private static final int WORK_MAX_NUM = ~( -1 << WORK_LEN);
  47. /**
  48. * 随机获取数据中心id的参数 32
  49. */
  50. private static final int DATA_RANDOM = DATA_MAX_NUM + 1;
  51. /**
  52. * 随机获取机器id的参数 32
  53. */
  54. private static final int WORK_RANDOM = WORK_MAX_NUM + 1;
  55. /**
  56. * 数据中心id左位移数 17
  57. */
  58. private static final int DATA_LEFT_BIT = TIME_LEFT_BIT - DATA_LEN;
  59. /**
  60. * 机器id左位移数 12
  61. */
  62. private static final int WORK_LEFT_BIT = DATA_LEFT_BIT - WORK_LEN;
  63. /**
  64. * 上一次毫秒内序列值
  65. */
  66. private static long LAST_SEQ = 0L;
  67. /**
  68. * 毫秒内序列的最大值 4095
  69. */
  70. private static final long SEQ_MAX_NUM = ~( -1 << SEQ_LEN);
  71. private final Object object = new Object();
  72. public synchronized static long getId() {
  73. long now = System.currentTimeMillis();
  74. //如果当前时间小于上次ID生成的时间,说明系统回退过抛出异常
  75. if (now < LAST_TIME_STAMP) {
  76. log.info( "系统时间异常,请检查!");
  77. throw new RuntimeException( "系统时间异常!");
  78. }
  79. if (now == LAST_TIME_STAMP) {
  80. LAST_SEQ = (LAST_SEQ + 1) & SEQ_MAX_NUM;
  81. if (LAST_SEQ == 0) {
  82. now = nextMillis(LAST_TIME_STAMP);
  83. }
  84. } else {
  85. LAST_SEQ = 0;
  86. }
  87. LAST_TIME_STAMP = now;
  88. return ((now - START_TIME) << TIME_LEFT_BIT) | (DATA_ID << DATA_LEFT_BIT) | (WORK_ID << WORK_LEFT_BIT) | LAST_SEQ;
  89. }
  90. private static long nextMillis(Long lastMillis) {
  91. long now = System.currentTimeMillis();
  92. while (now <= lastMillis) {
  93. now = System.currentTimeMillis();
  94. }
  95. return now;
  96. }
  97. private static int getDataId() {
  98. try {
  99. return getHostId(Inet4Address.getLocalHost().getHostName(), DATA_MAX_NUM);
  100. } catch (UnknownHostException e) {
  101. return new Random().nextInt(DATA_RANDOM);
  102. }
  103. }
  104. private static int getWorkId() {
  105. try {
  106. return getHostId(Inet4Address.getLocalHost().getHostAddress(), WORK_MAX_NUM);
  107. } catch (UnknownHostException e) {
  108. return new Random().nextInt(WORK_RANDOM);
  109. }
  110. }
  111. private static int getHostId(String str, int max) {
  112. byte[] bytes = str.getBytes();
  113. int sums = 0;
  114. for ( int b : bytes) {
  115. sums += b;
  116. }
  117. return sums % (max + 1);
  118. }
  119. public static void main(String[] args) {
  120. // SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  121. // try {
  122. // long time = dateFormat.parse("2015-01-01 00:00:00").getTime();
  123. // System.out.println(time);
  124. // } catch (ParseException e) {
  125. // e.printStackTrace();
  126. // }
  127. for ( int i = 0; i < 10 ; i++) {
  128. new Thread( new Runnable() {
  129. @ Override
  130. public void run( ) {
  131. System. out.println( "-------------");
  132. System. out.println(getId());
  133. }
  134. }).start();
  135. }
  136. }
  137. }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
雪花算法是种生成分布式ID的算法,它可以生成一个64位的ID,其中包含了时间戳、数据中心ID和机器ID等信息。下面是雪花算法生成分布式ID的软件设计模型: 1. 定义一个Snowflake类,该类包含以下属性: - datacenter_id: 数据中心ID,占5位,取值围为0~31。 - worker_id: 机器ID,占5位,取值范围为0~31。 - sequence: 序列号,占12位,取值范围为0~4095。 - last_timestamp: 上一次生成ID的时间戳。 2. 实现Snowflake类的构造函数,初始化datacenter_id和worker_id属性。 3. 实现一个next_id方法,该方法用于生成下一个ID。具体实现如下: - 获取当前时间戳,单位为毫秒。 - 如果当前时间戳小于上一次生成ID的时间戳,则说明系统时钟回退过,抛出异常。 - 如果当前时间戳等于上一次生成ID的时间戳,则将序列号加1。 - 如果当前时间戳大于上一次生成ID的时间戳,则将序列号重置为0,并将last_timestamp属性更新为当前时间戳。 - 将datacenter_id、worker_id、时间戳和序列号按照一定的位数组合成一个64位的ID。 - 返回生成ID。 4. 在分布式系统中,每个节点都需要创建一个Snowflake实例,并指定不同的datacenter_id和worker_id。每个节点生成ID都是唯一的,且具有时间顺序。 下面是一个Python实现的雪花算法生成分布式ID的代码示例: ```python import time class Snowflake: def __init__(self, datacenter_id, worker_id): self.datacenter_id = datacenter_id self.worker_id = worker_id self.sequence = 0 self.last_timestamp = -1 def next_id(self): timestamp = int(time.time() * 1000) if timestamp < self.last_timestamp: raise Exception("Clock moved backwards. Refusing to generate id") if timestamp == self.last_timestamp: self.sequence = (self.sequence + 1) & 4095 if self.sequence == 0: timestamp = self.wait_next_millis(self.last_timestamp) else: self.sequence = 0 self.last_timestamp = timestamp return ((timestamp - 1288834974657) << 22) | (self.datacenter_id << 17) | (self.worker_id << 12) | self.sequence def wait_next_millis(self, last_timestamp): timestamp = int(time.time() * 1000) while timestamp <= last_timestamp: timestamp = int(time.time() * 1000) return timestamp ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值