分布式高并发下唯一标识算法 snowflake算法/Redis自增

£神魔★判官ぃ 2022-05-18 08:55 263阅读 0赞

#

简介:

在系统小时,唯一标识的产生,可以利用公用模块来处理,比如数据库表的唯一键、或者缓存的唯一id等等方式。但在分布式高并发的系统中,如果还是这样使用公共模块,就会产生很大的风险和瓶颈。网上也有相关推荐的,twitter的snowflake就能解决这个问题。

snowflake满足了以下个要求:

  1. 只用64位就能达到要求;而无需128的方式。
  2. 考虑到排序的要求,标识的排序跟时间上基本能保持一致。
  3. 满足了预期服务时间内的,即在多少年内此算法适用。

snowflake算法核心:

这里写图片描述

如图,64位。主要由3块构成:时间戳、工作机器id、序列号。
- 其中第一位不用,也可以理解作为正负数来使用,默认正数的。
- 随后41位表示时间戳,在实际使用时,可以当做时间差来使用,比如现在离2017-01-01 00:00:00的时间差。这样的话,时间范围就能达到: (2^41-1)/(1000*60*60*24*365)=69.7年。
- 中间10位用于工作机器的。可以用于 2^10-1=1023台机器。
- 最后12位表示序列号,一个机器在一个毫秒时最大能产生 2^12-1=4095个。
**在实际应用中,可能无需最大化的,比如时间戳只用30位就能达到要求的就无需41位,其他的同理。
工作机器ID,可以是进程级别。机器级别的话,可以使用机器的mac地址或ip地址经过算法;如果是进程级别的话,可以使用path+进程标识;也可以混编,列如前5位标识机器,后5位标识进程。
关于序列号有个注意点,如果一个毫秒内,序列号已经达到上限,就等到下一毫秒,同时序列号置零开始。**

具体代码:

  1. /**
  2. * @author zcl
  3. * @date 2017/7/12
  4. **/
  5. public class MagicSnowFlake {
  6. //其实时间戳 2017-01-01 00:00:00
  7. private final static long twepoch = 1483200000000l;
  8. //10bit(位)的工作机器id 中IP标识所占的位数 8bit(位)
  9. private final static long ipIdBits = 8L;
  10. //IP标识最大值 255 即2的8次方减一。
  11. private final static long ipIdMax = ~ (-1L << ipIdBits);
  12. //10bit(位)的工作机器id 中数字标识id所占的位数 2bit(位)
  13. private final static long dataCenterIdBits = 2L;
  14. //数字标识id最大值 3 即2的2次方减一。
  15. private final static long dataCenterIdMax = ~ (-1L << dataCenterIdBits);
  16. //序列在id中占的位数 12bit
  17. private final static long seqBits = 12L;
  18. //序列最大值 4095 即2的12次方减一。
  19. private final static long seqMax = ~(-1L << seqBits);
  20. // 64位的数字:首位0 随后41位表示时间戳 随后10位工作机器id(8位IP标识 + 2位数字标识) 最后12位序列号
  21. private final static long dataCenterIdLeftShift = seqBits;
  22. private final static long ipIdLeftShift = seqBits + dataCenterIdBits;
  23. private final static long timeLeftShift = seqBits + dataCenterIdBits + ipIdLeftShift;
  24. //IP标识(0~255)
  25. private long ipId;
  26. // 数据中心ID(0~3)
  27. private long dataCenterId;
  28. // 毫秒内序列(0~4095)
  29. private long seq = 0L;
  30. // 上次生成ID的时间截
  31. private long lastTime = -1L;
  32. public MagicSnowFlake(long ipId, long dataCenterId) {
  33. if(ipId < 0 || ipId > ipIdMax) {
  34. System.out.println(" ---------- ipId不在正常范围内(0~"+ipIdMax +") " + ipId);
  35. System.exit(0);
  36. }
  37. if(dataCenterId < 0 || dataCenterId > dataCenterIdMax) {
  38. System.out.println(" ---------- dataCenterId不在正常范围内(0~"+dataCenterIdMax +") " + dataCenterId);
  39. System.exit(0);
  40. }
  41. this.ipId = ipId;
  42. this.dataCenterId = dataCenterId;
  43. }
  44. public synchronized long nextId() {
  45. long nowTime = System.currentTimeMillis();
  46. if(nowTime < lastTime) {
  47. System.out.println(" ---------- 当前时间前于上次操作时间,当前时间有误: " + nowTime);
  48. System.exit(0);
  49. }
  50. if(nowTime == lastTime) {
  51. seq = (seq + 1) & seqMax;
  52. if(seq == 0) {
  53. nowTime = getNextTimeStamp();
  54. }
  55. } else {
  56. seq = 0L;
  57. }
  58. lastTime = nowTime;
  59. return ((nowTime - twepoch) << timeLeftShift)
  60. | (ipId << ipIdLeftShift)
  61. | (dataCenterId << dataCenterIdLeftShift)
  62. | seq;
  63. }
  64. private long getNextTimeStamp() {
  65. long nowTime;
  66. do {
  67. nowTime = System.currentTimeMillis();
  68. } while(nowTime <= lastTime);
  69. return nowTime;
  70. }
  71. }
  72. ---------------------------------------------------------------
  73. /**
  74. * @author zcl
  75. * @date 2017/7/12
  76. **/
  77. public class ThreadSnowFlake extends Thread {
  78. MagicSnowFlake msf;
  79. int cnt = 0;
  80. public ThreadSnowFlake(MagicSnowFlake msf) {
  81. this.msf = msf;
  82. }
  83. public void run() {
  84. System.out.println();
  85. if(msf != null) {
  86. while(cnt < 10) {
  87. System.out.print(Thread.currentThread().getId() + " : " + msf.nextId());
  88. cnt ++;
  89. }
  90. }
  91. }
  92. }
  93. -----------------------------------------------------------------------
  94. /**
  95. * @author zcl
  96. * @date 2017/7/12
  97. **/
  98. public class AlgorithmMain {
  99. public static void main(String[] args) {
  100. MagicSnowFlake msf = new MagicSnowFlake(196, 2);
  101. ThreadSnowFlake t1 = new ThreadSnowFlake(msf);
  102. ThreadSnowFlake t2 = new ThreadSnowFlake(msf);
  103. t1.start();
  104. t2.start();
  105. }
  106. }

使用redis的RedisAtomicLong可以生成分布式自增的ID值。
SequenceFactory是封装的一个工具类,利用redisTemplate生成自增ID,实现如下:

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.data.redis.core.RedisTemplate;
  3. import org.springframework.data.redis.support.atomic.RedisAtomicLong;
  4. import org.springframework.stereotype.Service;
  5. import java.util.Date;
  6. import java.util.concurrent.TimeUnit;
  7. @Service
  8. public class SequenceFactory {
  9. @Autowired
  10. RedisTemplate<String, String> redisTemplate;
  11. /**
  12. * @param key
  13. * @param value
  14. * @param expireTime
  15. * @Title: set
  16. * @Description: set cache.
  17. */
  18. public void set(String key, int value, Date expireTime) {
  19. RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
  20. counter.set(value);
  21. counter.expireAt(expireTime);
  22. }
  23. /**
  24. * @param key
  25. * @param value
  26. * @param timeout
  27. * @param unit
  28. * @Title: set
  29. * @Description: set cache.
  30. */
  31. public void set(String key, int value, long timeout, TimeUnit unit) {
  32. RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
  33. counter.set(value);
  34. counter.expire(timeout, unit);
  35. }
  36. /**
  37. * @param key
  38. * @return
  39. * @Title: generate
  40. * @Description: Atomically increments by one the current value.
  41. */
  42. public long generate(String key) {
  43. RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
  44. return counter.incrementAndGet();
  45. }
  46. /**
  47. * @param key
  48. * @return
  49. * @Title: generate
  50. * @Description: Atomically increments by one the current value.
  51. */
  52. public long generate(String key, Date expireTime) {
  53. RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
  54. counter.expireAt(expireTime);
  55. return counter.incrementAndGet();
  56. }
  57. /**
  58. * @param key
  59. * @param increment
  60. * @return
  61. * @Title: generate
  62. * @Description: Atomically adds the given value to the current value.
  63. */
  64. public long generate(String key, int increment) {
  65. RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
  66. return counter.addAndGet(increment);
  67. }
  68. /**
  69. * @param key
  70. * @param increment
  71. * @param expireTime
  72. * @return
  73. * @Title: generate
  74. * @Description: Atomically adds the given value to the current value.
  75. */
  76. public long generate(String key, int increment, Date expireTime) {
  77. RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
  78. counter.expireAt(expireTime);
  79. return counter.addAndGet(increment);
  80. }
  81. }

发表评论

表情:
评论列表 (有 0 条评论,263人围观)

还没有评论,来说两句吧...

相关阅读