Twitter的分布式雪花算法 SnowFlake
為什么80%的碼農都做不了架構師?>>> ??
原理
Twitter的雪花算法SnowFlake,使用Java語言實現。
SnowFlake算法產生的ID是一個64位的整型,結構如下(每一部分用“-”符號分隔):
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位作為機器標識。
snowflake生成的ID整體上按照時間自增排序,并且整個分布式系統內不會產生ID碰撞(由datacenter和workerId作區分),并且效率較高。這個算法單機每秒內理論上最多可以生成1000*(2^12),也就是409.6萬個ID。
源碼
/*** 描述: Twitter的分布式自增ID雪花算法snowflake (Java版)** @author jinzg* @create 2018-03-14 12:37**/ public class IdWorker {/*** 起始的時間戳*/private final long twepoch = Date.from(LocalDate.of(2018, 1, 1).atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()).getTime();/*** 每一部分占用的位數*/private final long sequenceBits = 12L;private final long workerIdBits = 5L;private final long datacenterIdBits = 5L;/*** 每一部分的最大值*/private final long maxWorkerId = -1L ^ (-1L << workerIdBits);private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);private final long sequenceMask = -1L ^ (-1L << sequenceBits);/*** 每一部分向左的位移*/private final long workerIdShift = sequenceBits;private final long datacenterIdShift = sequenceBits + workerIdBits;private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;private long workerId;// 數據中心private long datacenterId;// 機器標識private long sequence = 0L;// 序列號private long lastTimestamp = -1L;// 上一次時間戳/*** Init.*/@PostConstructpublic void init() {try {String ip = IpUtils.getRealIp();if (StringUtils.isEmpty(ip)) {throw new RuntimeException("IdWorker get ip is empty");}this.workerId = this.datacenterId = Math.abs(ip.hashCode() % 31);log.info("ip:{},workerId:{},datacenterId;{}", ip, workerId, datacenterId);} catch (SocketException e) {log.error("init error,error:{}", e);throw new RuntimeException("IdWorker init error");}}/*** Instantiates a new Id worker.*/public IdWorker() {super();}/*** Instantiates a new Id worker.** @param workerId the worker id* @param datacenterId the datacenter id*/public IdWorker(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;}/*** Next id long.** @return the long*/public synchronized long nextId() {long timestamp = timeGen();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;}lastTimestamp = timestamp;return ((timestamp - twepoch) << timestampLeftShift)| (datacenterId << datacenterIdShift)| (workerId << workerIdShift)| sequence;}/*** Til next millis long.** @param lastTimestamp the last timestamp* @return the long*/protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** Time gen long.** @return the long*/protected long timeGen() {return System.currentTimeMillis();}/*** test*/static class IdWorkThread implements Runnable {private Set<Long> set;private IdWorker idWorker;/*** Instantiates a new Id work thread.** @param set the set* @param idWorker the id worker*/public IdWorkThread(Set<Long> set, IdWorker idWorker) {this.set = set;this.idWorker = idWorker;}@Overridepublic void run() {while (true) {long id = idWorker.nextId();System.out.println(Thread.currentThread().getName() + ":" + id);if (!set.add(id)) {System.out.println("duplicate:" + id);}}}}/*** The entry point of application.** @param args the input arguments*/public static void main(String[] args) {Set<Long> set = new HashSet<Long>();final IdWorker idWorker1 = new IdWorker(0, 0);final IdWorker idWorker2 = new IdWorker(1, 0);Thread t1 = new Thread(new IdWorkThread(set, idWorker1));Thread t2 = new Thread(new IdWorkThread(set, idWorker2));t1.setDaemon(true);t2.setDaemon(true);t1.start();t2.start();try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}} }特點
優點:
- 快。
- 沒有啥依賴,實現也特別簡單。
- 知道原理之后可以根據實際情況調整各各位段,方便靈活。
缺點:
- 只能趨勢遞增。(有些也不叫缺點,網上有些如果絕對遞增,競爭對手中午下單,第二天在下單即可大概判斷該公司的訂單量,危險!!!)
- 依賴機器時間,如果發生回撥會導致可能生成id重復。
轉載于:https://my.oschina.net/jzgycq/blog/1634534
總結
以上是生活随笔為你收集整理的Twitter的分布式雪花算法 SnowFlake的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Git知识总览(六) Git分支中的远程
- 下一篇: 娓娓道来Promise