雪花算法(Snowflake
)是一种经典的分布式ID生成算法。java
在分布式系统中,有一些须要使用全局惟一ID的场景,这种时候为了防止ID冲突可使用36位的UUID,可是UUID有一些缺点,首先他相对比较长,另外UUID通常是无序的。git
有些时候咱们但愿能使用一种简单一些的ID,而且但愿ID可以按照时间有序生成。github
而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra,由于Cassandra没有顺序ID生成机制,因此开发了这样一套全局惟一ID生成服务。算法
UUID数据库
缺点:太长,没法排序,使数据库性能下降。安全
自增IDless
缺点:对于数据敏感的场景不宜使用,且不适用于分布式场景。分布式
GUID函数
缺点:采用无心义字符串数据,数据量增大时形成访问过慢,且不宜排序。性能
snowflake算法产生的ID是一个64位的long整型,结构以下(每部分用-分开):
1位标志位 - 41位时间戳部分 - 10位节点部分 - 12位序列号部分
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
第一位为未使用,接下来的41位为毫秒级时间(41位的长度可使用69年),而后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点) ,最后12位是毫秒内的计数(12位的计数顺序号支持每一个节点每毫秒产生4096个ID序号)
一共加起来恰好64位,为一个Long型。(转换成字符串后长度最多19)
snowflake生成的ID总体上按照时间自增排序,而且整个分布式系统内不会产生ID碰撞(由datacenter和workerId做区分),而且效率较高。经测试snowflake每秒可以产生26万个ID。
本机实测:100万个ID 耗时5秒
/** * Twitter_Snowflake<br> * SnowFlake的结构以下(每部分用-分开):<br> * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br> * 1位标识,因为long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,因此id通常是正数,最高位是0<br> * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截) * 获得的值),这里的的开始时间截,通常是咱们的id生成器开始使用的时间,由咱们程序来指定的(以下下面程序IdWorker类的startTime属性)。41位的时间截,可使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br> * 10位的数据机器位,能够部署在1024个节点,包括5位datacenterId和5位workerId<br> * 12位序列,毫秒内的计数,12位的计数顺序号支持每一个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br> * 加起来恰好64位,为一个Long型。<br> * SnowFlake的优势是,总体上按照时间自增排序,而且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID做区分),而且效率较高,经测试,SnowFlake每秒可以产生26万ID左右。 */ public class SnowflakeIdWorker { // ==============================Fields=========================================== /** 开始时间截 (2015-01-01) */ private final long twepoch = 1420041600000L; /** 机器id所占的位数 */ private final long workerIdBits = 5L; /** 数据标识id所占的位数 */ private final long datacenterIdBits = 5L; /** 支持的最大机器id,结果是31 (这个移位算法能够很快的计算出几位二进制数所能表示的最大十进制数) */ private final long maxWorkerId = -1L ^ (-1L << workerIdBits); /** 支持的最大数据标识id,结果是31 */ private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); /** 序列在id中占的位数 */ private final long sequenceBits = 12L; /** 机器ID向左移12位 */ private final long workerIdShift = sequenceBits; /** 数据标识id向左移17位(12+5) */ private final long datacenterIdShift = sequenceBits + workerIdBits; /** 时间截向左移22位(5+5+12) */ private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */ private final long sequenceMask = -1L ^ (-1L << sequenceBits); /** 工做机器ID(0~31) */ private long workerId; /** 数据中心ID(0~31) */ private long datacenterId; /** 毫秒内序列(0~4095) */ private long sequence = 0L; /** 上次生成ID的时间截 */ private long lastTimestamp = -1L; //==============================Constructors===================================== /** * 构造函数 * @param workerId 工做ID (0~31) * @param datacenterId 数据中心ID (0~31) */ public SnowflakeIdWorker(long workerId, long datacenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); } if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); } this.workerId = workerId; this.datacenterId = datacenterId; } // ==============================Methods========================================== /** * 得到下一个ID (该方法是线程安全的) * @return SnowflakeId */ public synchronized long nextId() { long timestamp = timeGen(); //若是当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 if (timestamp < lastTimestamp) { throw new RuntimeException( String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } //若是是同一时间生成的,则进行毫秒内序列 if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; //毫秒内序列溢出 if (sequence == 0) { //阻塞到下一个毫秒,得到新的时间戳 timestamp = tilNextMillis(lastTimestamp); } } //时间戳改变,毫秒内序列重置 else { sequence = 0L; } //上次生成ID的时间截 lastTimestamp = timestamp; //移位并经过或运算拼到一块儿组成64位的ID return ((timestamp - twepoch) << timestampLeftShift) // | (datacenterId << datacenterIdShift) // | (workerId << workerIdShift) // | sequence; } /** * 阻塞到下一个毫秒,直到得到新的时间戳 * @param lastTimestamp 上次生成ID的时间截 * @return 当前时间戳 */ protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 返回以毫秒为单位的当前时间 * @return 当前时间(毫秒) */ protected long timeGen() { return System.currentTimeMillis(); } //==============================Test============================================= /** 测试 */ public static void main(String[] args) { SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0); long start = System.currentTimeMillis(); for (int i = 0; i < 1000; i++) { long id = idWorker.nextId(); System.out.println(Long.toBinaryString(id)); System.out.println(id); } System.out.println(System.currentTimeMillis() - start); } }
参考:https://github.com/twitter/snowflake