概述
SnowFlake算法是Twitter設計的一個可以在分布式系統中生成唯一的ID的算法,它可以滿足Twitter每秒上萬條消息ID分配的請求,這些消息ID是唯一的且有大致的遞增順序。
原理
SnowFlake算法產生的ID是一個64位的整型,結構如下(每一部分用“-”符號分隔):
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
1位標識部分,在java中由於long的最高位是符號位,正數是0,負數是1,一般生成的ID為正數,所以為0;
41位時間戳部分,這個是毫秒級的時間,一般實現上不會存儲當前的時間戳,而是時間戳的差值(當前時間-固定的開始時間),這樣可以使產生的ID從更小值開始;41位的時間戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年;
10位節點部分,Twitter實現中使用前5位作為數據中心標識,后5位作為機器標識,可以部署1024個節點;
12位序列號部分,支持同一毫秒內同一個節點可以生成4096個ID;
SnowFlake算法生成的ID大致上是按照時間遞增的,用在分布式系統中時,需要注意數據中心標識和機器標識必須唯一,這樣就能保證每個節點生成的ID都是唯一的。或許我們不一定都需要像上面那樣使用5位作為數據中心標識,5位作為機器標識,可以根據我們業務的需要,靈活分配節點部分,如:若不需要數據中心,完全可以使用全部10位作為機器標識;若數據中心不多,也可以只使用3位作為數據中心,7位作為機器標識。
源碼
/** * twitter的snowflake算法 -- java實現 * * @author rock * @date 2016/11/26 */ public class SnowFlake { /** * 起始的時間戳 */ private final static long START_STMP = 1480166465631L; /** * 每一部分占用的位數 */ private final static long SEQUENCE_BIT = 12; //序列號占用的位數 private final static long MACHINE_BIT = 5; //機器標識占用的位數 private final static long DATACENTER_BIT = 5;//數據中心占用的位數 /** * 每一部分的最大值 */ private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT); private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); /** * 每一部分向左的位移 */ private final static long MACHINE_LEFT = SEQUENCE_BIT; private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT; private long datacenterId; //數據中心 private long machineId; //機器標識 private long sequence = 0L; //序列號 private long lastStmp = -1L;//上一次時間戳 public SnowFlake(long datacenterId, long machineId) { if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) { throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0"); } if (machineId > MAX_MACHINE_NUM || machineId < 0) { throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0"); } this.datacenterId = datacenterId; this.machineId = machineId; } /** * 產生下一個ID * * @return */ public synchronized long nextId() { long currStmp = getNewstmp(); if (currStmp < lastStmp) { throw new RuntimeException("Clock moved backwards. Refusing to generate id"); } if (currStmp == lastStmp) { //相同毫秒內,序列號自增 sequence = (sequence + 1) & MAX_SEQUENCE; //同一毫秒的序列數已經達到最大 if (sequence == 0L) { currStmp = getNextMill(); } } else { //不同毫秒內,序列號置為0 sequence = 0L; } lastStmp = currStmp; return (currStmp - START_STMP) << TIMESTMP_LEFT //時間戳部分 | datacenterId << DATACENTER_LEFT //數據中心部分 | machineId << MACHINE_LEFT //機器標識部分 | sequence; //序列號部分 } private long getNextMill() { long mill = getNewstmp(); while (mill <= lastStmp) { mill = getNewstmp(); } return mill; } private long getNewstmp() { return System.currentTimeMillis(); } }
可以寫一代碼測試一下,如下所示:
public static void main(String[] args) { SnowFlake snowFlake = new SnowFlake(2, 3); for (int i = 0; i < (1 << 12); i++) { System.out.println(snowFlake.nextId()); } }
循環生成2^12個ID,運行結果如下:
... 2099698216995 2099698216996 2099698216997 2099698216998 2099698216999 2099698217000 2099698217001 2099698217002 2099698217003 2099698217004 2099698217005 2099698217006 2099698217007 2099698217008 2099698217009 2099698217010 2099698217011 2099698217012 2099698217013 2099698217014 2099698217015 2099698217016 2099698217017 2099698217018 2099698217019 2099698217020 2099698217021 2099698217022 2099698217023 2099698217024 2099698217025 2099698217026 2099698217027 2099698217028 2099698217029 2099698217030 2099698217031 2099702411264 2099702411265 2099702411266 2099702411267 2099702411268 2099702411269 2099702411270 2099702411271 2099702411272 2099702411273 2099702411274 2099702411275 2099702411276 2099702411277 ...
可以看到生成的ID都是遞增的,而且都是唯一的。
源碼已經提交在GitHub:
https://github.com/beyondfengyu/SnowFlake