這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
大部分分布式系統中,對一些互斥資源通常需要一個叢集唯一的ID,比如訊息id,訂單號等。而且很多業務需求往往要求這些ID必須具有先後順序,以方便分頁或者排序。這就要求ID具有兩個特性:
Snowflake
Twitter-Snowflake演算法很好的解決了這種需求,它可以非常高效的產生ID,其核心思想如下(圖片來自網路):
snowflake-64bit.jpg
- 時間戳記。時間戳記段位共41位,單位毫秒,可以使用約70年。為了增加剩餘可用期限,一般都會把起始日期盡量後移而不是直接使用1970-01-01。(ps:如果是使用1970,你的程式只能支援到2039年了)
- 機器id。用於區分叢集內不同機器,因為Snowflake產生ID是在每台機器上進行的。一般叢集中每個節點都會有一個自己的id標示,如果實在沒有也可以通過grpc,thrift等由master server產生。我們系統的nodeid是通過zookeeper來產生的。
- 序號。由於高並發的特性,即使時間戳記精確到了毫秒,也有可能出現重複。序號用於同一時間戳記下產生多個id。12位的長度,可以達到每秒上限1000*(2^12)=400W,完全夠用了。
Golang實現
實現代碼如下:
/** Snowflake** 1 42 52 64* +-----------------------------------------------+------------+---------------+* | timestamp(ms) | workerid | sequence |* +-----------------------------------------------+------------+---------------+* | 0000000000 0000000000 0000000000 0000000000 0 | 0000000000 | 0000000000 00 |* +-----------------------------------------------+------------+---------------+** 1. 41位時間截(毫秒級),注意這是時間截的差值(目前時間截 - 開始時間截)。可以使用約70年: (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69* 2. 10位元據機器位,可以部署在1024個節點* 3. 12位序列,毫秒內的計數,同一機器,同一時間截並發4096個序號*/const ( twepoch = int64(1483228800000) //開始時間截 (2017-01-01) workeridBits = uint(10) //機器id所佔的位元 sequenceBits = uint(12) //序列所佔的位元 workeridMax = int64(-1 ^ (-1 << workeridBits)) //支援的最大機器id數量 sequenceMask = int64(-1 ^ (-1 << sequenceBits)) // workeridShift = sequenceBits //機器id左移位元 timestampShift = sequenceBits + workeridBits //時間戳記左移位元)// A Snowflake struct holds the basic information needed for a snowflake generator workertype Snowflake struct { sync.Mutex timestamp int64 workerid int64 sequence int64}// NewNode returns a new snowflake worker that can be used to generate snowflake IDsfunc NewSnowflake(workerid int64) (*Snowflake, error) { if workerid < 0 || workerid > workeridMax { return nil, errors.New("workerid must be between 0 and 1023") } return &Snowflake{ timestamp: 0, workerid: workerid, sequence: 0, }, nil}// Generate creates and returns a unique snowflake IDfunc (s *Snowflake) Generate() int64 { s.Lock() now := time.Now().UnixNano() / 1000000 if s.timestamp == now { s.sequence = (s.sequence + 1) & sequenceMask if s.sequence == 0 { for now <= s.timestamp { now = time.Now().UnixNano() / 1000000 } } } else { s.sequence = 0 } s.timestamp = now r := int64((now-twepoch)<<timestampShift | (s.workerid << workeridShift) | (s.sequence)) s.Unlock() return r}
參考資料:https://github.com/twitter/snowflake