分布式ID生成器-雪花算法(snowflake)


背景

当下绝大部分互联网公司采用的是分布式的架构系统,而分布式系统中有一些场景需要使用到全局性唯一ID,例如:订单编号、付款单编号、交易流水号等等,在这之前,我们可以使用UUID、数据库自增ID等去实现它,但是要么生成的ID是无序的,要么ID生成效率低下。
所以在该背景下,twitter公司提出了snowflake算法,最初Twitter把存储系统从MySQL迁移到Cassandra,因为Cassandra没有顺序ID生成机制,为了满足Twitter每秒上万条消息的请求,每条消息都必须分配一条唯一的id,这些id还需要一些大致的顺序(方便客户端排序),并且在分布式系统中不同机器产生的id必须不同,所以twitter开发了这样一套全局唯一ID生成服务。

介绍
snowflake算法生成的64位ID = 1位符号位 + 41位时间戳 + 10位机器id + 12位序列号。


 
64位ID组成结构.png

1、符号位:一般默认为0,表明该ID为正数。

2、时间戳:该时间戳并不是指当前的时间戳,而是当前时间戳-初始时间戳(初始时间戳离当前时间越近越好,而且必须小于当前时间戳)的值。这样做的好处是有更多的数值可以使用,最多可以使用(1L << 41)/ (1000毫秒 * 60秒 * 60 分钟 * 24小时 * 365天) = 69年。

3、机器id:取值在0 ~ 1023之间,最多支持1024个节点。
假设你们公司的机器节点超过了1024个,而你们的业务没有达到1毫秒需要2408个序列号的并发的情况下,可以将序列号的位数减少,给机器id增加位数。

4、序列号,取值在0~4096之间,最多支持一毫秒内生成4096个序列号。
假设你们公司的机器节点没有超过512个,而你们的业务1毫秒需要超过4096个序列号的并发的情况下,可以将机器id的位数减少,给序列号增加位数。

优点:
1、完全基于内存,ID生成效率高。
2、生成的ID基于时间戳和序列号,具有有序性的特点,方便排序、查询。
3、可以根据生产机器节点数和业务并发量的情况,调整ID的生成策略。

代码:

package com.jiepos.api.demo; /** * 雪花算法 * 64位ID = 1位符号位(固定为0,表示正数) + 41位时间戳 + 10位工作机器id + 12位序列号 * @author shuyan.qi * @date 2020/5/3 9:42 下午 */ public class SnowFlakeID { //12位序列号 private long sequence;//序列号 private long sequenceBits = 12L;//序列号位数 private long maxSequence = -1 ^ (-1 << sequenceBits);//序列号最大值 //10位工作机器id = 5位机房id + 5位机器id private long workerId;//机器id private long workerIdBits = 5L;//机器id位数 private long maxWorkerId = -1 ^ (-1 << workerIdBits);//机器id最大值 private long workerIdMoveBits = sequenceBits;//机器id左移位数 = 序列号位数 private long workerIdAfterMove = 0L;//左移后的机器id private long workerCenterId;//机房id private long workerCenterIdBits = 5L;//机房id位数 private long maxWorkerCenterId = -1 ^ (-1 << workerCenterIdBits);//机房id最大值 private long workerCenterIdMoveBits = workerIdBits + workerIdMoveBits;//机房id左移位数 = 机器id位数 + 序列号位数 private long workerCenterIdAfterMove = 0L;//左移后的机房id //41位时间戳 private long lastTimestamp = -1L;//默认-1L private long initTimestamp = 1588518046057L;//初始时间戳 private long timestampMoveBits = workerCenterIdBits + workerCenterIdMoveBits;//时间戳左移位数 = 机房id位数 + 机器id位数 + 序列号位数 public SnowFlakeID(long workerCenterId , long workerId){ if(workerCenterId < 0 || workerCenterId > maxWorkerCenterId){ throw new IllegalArgumentException("workerCenterId is illegal"); } if(workerId < 0 || workerId > maxWorkerId){ throw new IllegalArgumentException("workerId is illegal"); } this.workerCenterId = workerCenterId; this.workerId = workerId; this.workerCenterIdAfterMove = this.workerCenterId << this.workerCenterIdMoveBits; this.workerIdAfterMove = this.workerCenterId << this.workerCenterIdMoveBits; } /** * 生成ID的核心方法 */ public synchronized long nextId(){ long currentTimestamp = timestamp(); if(currentTimestamp < lastTimestamp){ String s = String.format("currentTimestamp is earlier than lastTimestamp,lastTimestamp=%s,currentTimestamp=%s",lastTimestamp,currentTimestamp); System.out.println(s); //throw new RuntimeException(s); // 时钟回拨后手动拨正。 // 因为依赖lastTimestamp,所以重启后第一次就发生时钟回拨的情况无法处理。 // 可以将lastTimestamp存放到redis之类第三方缓存中,但这样生成id的效率会降低,请开发者根据实际情况去选择。 currentTimestamp = lastTimestamp; } if(currentTimestamp == lastTimestamp){ //同一时间戳,序列号加1 sequence = (sequence + 1) & maxSequence; if(sequence == 0L){ //如果序列号加1后的值为0,表示当前时间戳内的序列号已用完,需要获取下一个时间戳 currentTimestamp = nextTimestamp(currentTimestamp); } }else{ sequence = 0L;//不同时间戳,重置序列号 } lastTimestamp = currentTimestamp;//更新成功生成id的最新时间戳 return ((currentTimestamp - initTimestamp) << timestampMoveBits) | workerCenterIdAfterMove | workerIdAfterMove | sequence; } /** * 获取timestamp的下一毫秒数 * @param timestamp 当前毫秒数 * @return */ public long nextTimestamp(long timestamp){ long timestamp1 = 0L; do{ timestamp1 = timestamp(); }while (timestamp >= timestamp1); return timestamp1; } /** * 获取当前时间戳 * @return */ public long timestamp(){ return System.currentTimeMillis(); } public static void main(String[] args) throws InterruptedException { /* //测试并发 Map<String,Object> map = new ConcurrentHashMap<>(); SnowFlakeID snowFlakeID = new SnowFlakeID(1, 1); for(int i = 0;i < 100;i++){ new Thread(() -> { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } for (int j = 0 ;j < 100000;j++){ map.put(String.valueOf(snowFlakeID.nextId()),1); } }).start();; } for(;;){ TimeUnit.SECONDS.sleep(1); System.out.println("size="+map.size()); } */ /* //测试速度 SnowFlakeID snowFlakeID = new SnowFlakeID(1, 1); long startTime = System.currentTimeMillis(); for(int i = 0;i < 3000000;i++){ snowFlakeID.nextId(); } System.out.println("耗时:"+(System.currentTimeMillis() - startTime)/1000.0d + "秒"); */ } 


作者:钢铁加鲁鲁_d59c
链接:https://www.jianshu.com/p/d230443d0e60
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM