原文來自標點符的《高併發環境下生成訂單惟一流水號方法:SnowFlake》html
業務需求:java
關於訂單號的生成,一些比較簡單的方案:linux
一、數據庫自增加IDgit
二、時間戳+隨機數github
三、時間戳+會員ID算法
四、GUID/UUIDsql
今天要分享的方案:來自twitter的SnowFlake數據庫
Twitter-Snowflake算法產生的背景至關簡單,爲了知足Twitter每秒上萬條消息的請求,每條消息都必須分配一條惟一的id,這些id還須要一些大體的順序(方便客戶端排序),而且在分佈式系統中不一樣機器產生的id必須不一樣.Snowflake算法核心把時間戳,工做機器id,序列號(毫秒級時間41位+機器ID 10位+毫秒內序列12位)組合在一塊兒。服務器
在上面的字符串中,第一位爲未使用(實際上也可做爲long的符號位),接下來的41位爲毫秒級時間,而後5位datacenter標識位,5位機器ID(並不算標識符,實際是爲線程標識),而後12位該毫秒內的當前毫秒內的計數,加起來恰好64位,爲一個Long型。多線程
除了最高位bit標記爲不可用之外,其他三組bit佔位都可浮動,看具體的業務需求而定。默認狀況下41bit的時間戳能夠支持該算法使用到2082年,10bit的工做機器id能夠支持1023臺機器,序列號支持1毫秒產生4095個自增序列id。下文會具體分析。
Snowflake – 時間戳
這裏時間戳的細度是毫秒級,具體代碼以下,建議使用64位linux系統機器,由於有vdso,gettimeofday()在用戶態就能夠完成操做,減小了進入內核態的損耗。
1 2 3 4 5 6 |
uint64_t generateStamp() { timeval tv; gettimeofday(&tv, 0); return (uint64_t)tv.tv_sec * 1000 + (uint64_t)tv.tv_usec / 1000; } |
默認狀況下有41個bit能夠供使用,那麼一共有T(1llu << 41)毫秒供你使用分配,年份 = T / (3600 * 24 * 365 * 1000) = 69.7年。若是你只給時間戳分配39個bit使用,那麼根據一樣的算法最後年份 = 17.4年。
Snowflake – 工做機器id
嚴格意義上來講這個bit段的使用能夠是進程級,機器級的話你可使用MAC地址來惟一標示工做機器,工做進程級可使用IP+Path來區分工做進程。若是工做機器比較少,可使用配置文件來設置這個id是一個不錯的選擇,若是機器過多配置文件的維護是一個災難性的事情。
這裏的解決方案是須要一個工做id分配的進程,可使用本身編寫一個簡單進程來記錄分配id,或者利用Mysql auto_increment機制也能夠達到效果。
工做進程與工做id分配器只是在工做進程啓動的時候交互一次,而後工做進程能夠自行將分配的id數據落文件,下一次啓動直接讀取文件裏的id使用。這個工做機器id的bit段也能夠進一步拆分,好比用前5個bit標記進程id,後5個bit標記線程id之類:D
Snowflake – 序列號
序列號就是一系列的自增id(多線程建議使用atomic),爲了處理在同一毫秒內須要給多條消息分配id,若同一毫秒把序列號用完了,則「等待至下一毫秒」。
1 2 3 4 5 6 7 8 |
uint64_t waitNextMs(uint64_t lastStamp) { uint64_t cur = 0; do { cur = generateStamp(); } while (cur <= lastStamp); return cur; } |
整體來講,是一個很高效很方便的GUID產生算法,一個int64_t字段就能夠勝任,不像如今主流128bit的GUID算法,即便沒法保證嚴格的id序列性,可是對於特定的業務,好比用作遊戲服務器端的GUID產生會很方便。另外,在多線程的環境下,序列號使用atomic能夠在代碼實現上有效減小鎖的密度。
該項目地址爲:https://github.com/twitter/snowflake 是用Scala實現的。核心代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
/** Copyright 2010-2012 Twitter, Inc.*/ package com.twitter.service.snowflake
import com.twitter.ostrich.stats.Stats import com.twitter.service.snowflake.gen._ import java.util.Random import com.twitter.logging.Logger
/** * An object that generates IDs. * This is broken into a separate class in case * we ever want to support multiple worker threads * per process */ class IdWorker(val workerId: Long, val datacenterId: Long, private val reporter: Reporter, var sequence: Long = 0L) extends Snowflake.Iface { private[this] def genCounter(agent: String) = { Stats.incr("ids_generated") Stats.incr("ids_generated_%s".format(agent)) } private[this] val exceptionCounter = Stats.getCounter("exceptions") private[this] val log = Logger.get private[this] val rand = new Random
val twepoch = 1288834974657L
private[this] val workerIdBits = 5L private[this] val datacenterIdBits = 5L private[this] val maxWorkerId = -1L ^ (-1L << workerIdBits) private[this] val maxDatacenterId = -1L ^ (-1L << datacenterIdBits) private[this] val sequenceBits = 12L
private[this] val workerIdShift = sequenceBits private[this] val datacenterIdShift = sequenceBits + workerIdBits private[this] val timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits private[this] val sequenceMask = -1L ^ (-1L << sequenceBits)
private[this] var lastTimestamp = -1L
// sanity check for workerId if (workerId > maxWorkerId || workerId < 0) { exceptionCounter.incr(1) throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0".format(maxWorkerId)) }
if (datacenterId > maxDatacenterId || datacenterId < 0) { exceptionCounter.incr(1) throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0".format(maxDatacenterId)) }
log.info("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d", timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId)
def get_id(useragent: String): Long = { if (!validUseragent(useragent)) { exceptionCounter.incr(1) throw new InvalidUserAgentError }
val id = nextId() genCounter(useragent)
reporter.report(new AuditLogEntry(id, useragent, rand.nextLong)) id }
def get_worker_id(): Long = workerId def get_datacenter_id(): Long = datacenterId def get_timestamp() = System.currentTimeMillis
protected[snowflake] def nextId(): Long = synchronized { var timestamp = timeGen()
if (timestamp < lastTimestamp) { exceptionCounter.incr(1) log.error("clock is moving backwards. Rejecting requests until %d.", lastTimestamp); throw new InvalidSystemClock("Clock moved backwards. Refusing to generate id for %d milliseconds".format( lastTimestamp - timestamp)) }
if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp) } } else { sequence = 0 }
lastTimestamp = timestamp ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence }
protected def tilNextMillis(lastTimestamp: Long): Long = { var timestamp = timeGen() while (timestamp <= lastTimestamp) { timestamp = timeGen() } timestamp }
protected def timeGen(): Long = System.currentTimeMillis()
val AgentParser = """([a-zA-Z][a-zA-Z\-0-9]*)""".r
def validUseragent(useragent: String): Boolean = useragent match { case AgentParser(_) => true case _ => false } } |
由UC實現的JAVA版本代碼(略有修改)
來源:https://github.com/sumory/uc/blob/master/src/com/sumory/uc/id/IdWorker.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
package com.sumory.uc.id;
import java.math.BigInteger;
import org.slf4j.Logger; import org.slf4j.LoggerFactory;
/** * 42位的時間前綴+10位的節點標識+12位的sequence避免併發的數字(12位不夠用時強制獲得新的時間前綴) * <p> * <b>對系統時間的依賴性很是強,須要關閉ntp的時間同步功能,或者當檢測到ntp時間調整後,拒絕分配id。 * * @author sumory.wu * @date 2012-2-26 下午6:40:28 */ public class IdWorker { private final static Logger logger = LoggerFactory.getLogger(IdWorker.class);
private final long workerId; private final long snsEpoch = 1330328109047L;// 起始標記點,做爲基準 private long sequence = 0L;// 0,併發控制 private final long workerIdBits = 10L;// 只容許workid的範圍爲:0-1023 private final long maxWorkerId = -1L ^ -1L << this.workerIdBits;// 1023,1111111111,10位 private final long sequenceBits = 12L;// sequence值控制在0-4095
private final long workerIdShift = this.sequenceBits;// 12 private final long timestampLeftShift = this.sequenceBits + this.workerIdBits;// 22 private final long sequenceMask = -1L ^ -1L << this.sequenceBits;// 4095,111111111111,12位
private long lastTimestamp = -1L;
public IdWorker(long workerId) { super(); if (workerId > this.maxWorkerId || workerId < 0) {// workid < 1024[10位:2的10次方] throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", this.maxWorkerId)); } this.workerId = workerId; }
public synchronized long nextId() throws Exception { long timestamp = this.timeGen(); if (this.lastTimestamp == timestamp) {// 若是上一個timestamp與新產生的相等,則sequence加一(0-4095循環),下次再使用時sequence是新值 //System.out.println("lastTimeStamp:" + lastTimestamp); this.sequence = this.sequence + 1 & this.sequenceMask; if (this.sequence == 0) { timestamp = this.tilNextMillis(this.lastTimestamp);// 從新生成timestamp } } else { this.sequence = 0; } if (timestamp < this.lastTimestamp) { logger.error(String.format("Clock moved backwards.Refusing to generate id for %d milliseconds", (this.lastTimestamp - timestamp))); throw new Exception(String.format("Clock moved backwards.Refusing to generate id for %d milliseconds", (this.lastTimestamp - timestamp))); }
this.lastTimestamp = timestamp; // 生成的timestamp return timestamp - this.snsEpoch << this.timestampLeftShift | this.workerId << this.workerIdShift | this.sequence; }
/** * 保證返回的毫秒數在參數以後 * * @param lastTimestamp * @return */ private long tilNextMillis(long lastTimestamp) { long timestamp = this.timeGen(); while (timestamp <= lastTimestamp) { timestamp = this.timeGen(); } return timestamp; }
/** * 得到系統當前毫秒數 * * @return */ private long timeGen() { return System.currentTimeMillis(); }
public static void main(String[] args) throws Exception { IdWorker iw1 = new IdWorker(1); IdWorker iw2 = new IdWorker(2); IdWorker iw3 = new IdWorker(3);
// System.out.println(iw1.maxWorkerId); // System.out.println(iw1.sequenceMask);
System.out.println("---------------------------");
long workerId = 1L; long twepoch = 1330328109047L; long sequence = 0L;// 0 long workerIdBits = 10L; long maxWorkerId = -1L ^ -1L << workerIdBits;// 1023,1111111111,10位 long sequenceBits = 12L;
long workerIdShift = sequenceBits;// 12 long timestampLeftShift = sequenceBits + workerIdBits;// 22 long sequenceMask = -1L ^ -1L << sequenceBits;// 4095,111111111111,12位
long ct = System.currentTimeMillis();// 1330328109047L;// System.out.println(ct);
System.out.println(ct - twepoch); System.out.println(ct - twepoch << timestampLeftShift);// 左移22位:*2的22次方 System.out.println(workerId << workerIdShift);// 左移12位:*2的12次方 System.out.println("哈哈"); System.out.println(ct - twepoch << timestampLeftShift | workerId << workerIdShift); long result = ct - twepoch << timestampLeftShift | workerId << workerIdShift | sequence;// 取時間的低40位 | (小於1024:只有12位)的低12位 | 計算的sequence System.out.println(result);
System.out.println("---------------"); for (int i = 0; i < 10; i++) { System.out.println(iw1.nextId()); }
Long t1 = 66708908575965184l; Long t2 = 66712718304231424l; Long t3 = 66715908575739904l; Long t4 = 66717361423925248l; System.out.println(Long.toBinaryString(t1)); System.out.println(Long.toBinaryString(t2)); System.out.println(Long.toBinaryString(t3)); System.out.println(Long.toBinaryString(t4)); //1110110011111111011001100001111100 0001100100 000000000000 //1110110100000010110111010010010010 0001100100 000000000000 //1110110100000101110000111110111110 0001100100 000000000000 //1110110100000111000101100011010000 0001100100 000000000000 System.out.println(Long.toBinaryString(66706920114753536l)); //1110110011111101100101110010010110 0000000001 000000000000
String a = "0001100100";//輸入數值 BigInteger src = new BigInteger(a, 2);//轉換爲BigInteger類型 System.out.println(src.toString());//轉換爲2進制並輸出結果
} } |
Go語言版本:https://github.com/sumory/idgen
Python語言版本:https://github.com/erans/pysnowflake