snowflake主键生成策略

1.snowflake简介

在分布式系统中,我们需要各种各样的ID,既然是ID那么必然是要保证全局唯一,除此之外,不同当业务还需要不同的特性,比如像并发巨大的业务要求ID生成效率高,吞吐大;比如某些银行类业务,需要按每日日期制定交易流水号;又比如我们希望用户的ID是随机的,无序的,纯数字的,且位数长度是小于10位的。等等,不同的业务场景需要的ID特性各不一样,于是,衍生了各种ID生成器,但大多数利用数据库控制ID的生成,性能受数据库并发能力限制,那么有没有一款不需要依赖任何中间件(如数据库,分布式缓存服务等)的ID生成器呢?本着取之于开源,用之于开源的原则,今天,特此介绍Twitter开源的一款分布式自增ID算法snowflake,并附上算法原理推导和演算过程!

snowflake算法是一款本地生成的(ID生成过程不依赖任何中间件,无网络通信),保证ID全局唯一,并且ID总体有序递增,性能每秒生成300w+。

Snowflake是我见过生成唯一主键id最快的方法,它是生成的是一个64位的数字,其中42位时间戳,接下来10位是自定义的数,其作用就是区分集群中的所有机器,最后12位是毫秒内序列,集群内每个机器能够在1毫秒内生成2^12 - 1个ID

2.snowflake算法原理

snowflake生产的ID二进制结构表示如下(每部分用-分开):

0 - 00000000 00000000 00000000 00000000 00000000 0 - 00000 - 00000 - 00000000 0000

第一位未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年,从1970-01-01 08:00:00),然后是5位datacenterId(最大支持2^5=32个,二进制表示从00000-11111,也即是十进制0-31),和5位workerId(最大支持2^5=32个,原理同datacenterId),所以datacenterId*workerId最多支持部署1024个节点,最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生2^12=4096个ID序号).

所有位数加起来共64位,恰好是一个Long型(转换为字符串长度为18).

单台机器实例,通过时间戳保证前41位是唯一的,分布式系统多台机器实例下,通过对每个机器实例分配不同的datacenterId和workerId避免中间的10位碰撞。最后12位每毫秒从0递增生产ID,再提一次:每毫秒最多生成4096个ID,每秒可达4096000个。理论上,只要CPU计算能力足够,单机每秒可生产400多万个,实测300w+,效率之高由此可见。下面是一个demo,通过测试可以直接使用。

  1 package com.test;
  2 
  3 import java.util.ArrayList;
  4 import java.util.List;
  5 
  6 public class SnowflakeIdWorker {
  7 
  8     // ==============================Fields===========================================
  9     /** 开始时间截 (2015-01-01) */
 10     private final long twepoch = 1420041600000L;
 11 
 12     /** 机器id所占的位数 */
 13     private final long workerIdBits = 5L;
 14 
 15     /** 数据标识id所占的位数 */
 16     private final long datacenterIdBits = 5L;
 17 
 18     /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
 19     private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
 20 
 21     /** 支持的最大数据标识id,结果是31 */
 22     private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
 23 
 24     /** 序列在id中占的位数 */
 25     private final long sequenceBits = 12L;
 26 
 27     /** 机器ID向左移12位 */
 28     private final long workerIdShift = sequenceBits;
 29 
 30     /** 数据标识id向左移17位(12+5) */
 31     private final long datacenterIdShift = sequenceBits + workerIdBits;
 32 
 33     /** 时间截向左移22位(5+5+12) */
 34     private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
 35 
 36     /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
 37     private final long sequenceMask = -1L ^ (-1L << sequenceBits);
 38 
 39     /** 工作机器ID(0~31) */
 40     private long workerId;
 41 
 42     /** 数据中心ID(0~31) */
 43     private long datacenterId;
 44 
 45     /** 毫秒内序列(0~4095) */
 46     private long sequence = 0L;
 47 
 48     /** 上次生成ID的时间截 */
 49     private long lastTimestamp = -1L;
 50 
 51     //==============================Constructors=====================================
 52     /**
 53      * 构造函数
 54      * @param workerId 工作ID (0~31)
 55      * @param datacenterId 数据中心ID (0~31)
 56      */
 57     public SnowflakeIdWorker(long workerId, long datacenterId) {
 58         if (workerId > maxWorkerId || workerId < 0) {
 59             throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
 60         }
 61         if (datacenterId > maxDatacenterId || datacenterId < 0) {
 62             throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
 63         }
 64         this.workerId = workerId;
 65         this.datacenterId = datacenterId;
 66     }
 67 
 68     // ==============================Methods==========================================
 69     /**
 70      * 获得下一个ID (该方法是线程安全的)
 71      * @return SnowflakeId
 72      */
 73     public synchronized long nextId() {
 74         long timestamp = timeGen();
 75 
 76         //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
 77         if (timestamp < lastTimestamp) {
 78             throw new RuntimeException(
 79                     String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
 80         }
 81 
 82         //如果是同一时间生成的,则进行毫秒内序列
 83         if (lastTimestamp == timestamp) {
 84             sequence = (sequence + 1) & sequenceMask;
 85             //毫秒内序列溢出
 86             if (sequence == 0) {
 87                 //阻塞到下一个毫秒,获得新的时间戳
 88                 timestamp = tilNextMillis(lastTimestamp);
 89             }
 90         }
 91         //时间戳改变,毫秒内序列重置
 92         else {
 93             sequence = 0L;
 94         }
 95 
 96         //上次生成ID的时间截
 97         lastTimestamp = timestamp;
 98 
 99         //移位并通过或运算拼到一起组成64位的ID
100         return ((timestamp - twepoch) << timestampLeftShift) //
101                 | (datacenterId << datacenterIdShift) //
102                 | (workerId << workerIdShift) //
103                 | sequence;
104     }
105 
106     /**
107      * 阻塞到下一个毫秒,直到获得新的时间戳
108      * @param lastTimestamp 上次生成ID的时间截
109      * @return 当前时间戳
110      */
111     protected long tilNextMillis(long lastTimestamp) {
112         long timestamp = timeGen();
113         while (timestamp <= lastTimestamp) {
114             timestamp = timeGen();
115         }
116         return timestamp;
117     }
118 
119     /**
120      * 返回以毫秒为单位的当前时间
121      * @return 当前时间(毫秒)
122      */
123     protected long timeGen() {
124         return System.currentTimeMillis();
125     }
126 
127     //==============================Test=============================================
128     /** 测试 */
129     public static void main(String[] args) {
130         SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
131         List<String> list = new ArrayList<String>();
132         for (int i = 0; i < 100000; i++) {
133             long id = idWorker.nextId();
134 136             String string =new String().valueOf(id);
137             list.add(string);
138         }
139         String str1 ="";
140         String str2 ="";
141         for(int j=0;j<100000;j++){
142             str1=list.get(j);
143             for(int n =0;n<100000;n++){
144                 str2=list.get(n);
145                 if(j==n){
146                     break;
147                 }
148                 if(str1.equals(str2)){
149                     System.out.println("存在相等的id  "+str1+"------->  " +str2);
150                 }
151             }
152         }
153         System.out.println("测试成功");
154     }
155 }

 

posted @ 2017-09-07 19:03  少帅928  阅读(1263)  评论(0编辑  收藏  举报