Twitter 早期用 MySQL 存儲數據,隨着用戶的增加,單一的 MySQL 實例無法承受海量的數據,後來團隊就研究如何產生完美的自增ID,以知足兩個基本的要求:php
Twitter-Snowflake算法就是在這樣的背景下產生的。git
Twitter 解決這兩個問題的方案很是簡單高效:每個 ID 都是 64 位數字,由時間戳、工做機器節點和序列號組成, ID是由當前所在的機器節點生成的。如圖:github
下面先說明一下各個區間的做用。redis
你們會以爲這個時間不夠用啊,不要緊,後面會講如何優化。算法
若是工做機器比較少,可使用配置文件來設置這個id,或者使用隨機數。若是機器過多就得單獨實現一共工做機器ID分配器了,好比使用redis自增,或者利用Mysql auto_increment機制也能夠達到效果。sql
綜上:同一臺機器1毫秒內可產生4095個ID,所有機器1毫秒內可產生 4095 * 1023 個ID。因爲全是在各個機器本地生成,效率很是高。數據庫
下面是一個簡單實現:僅有時間戳,機器位爲0,序列號爲0:架構
#include <stdio.h> int main() { long long id; id = 1572057648000 << 22; //至關於 id = 1572057648000 << 22 | 0 << 12 | 0; printf("id=%lld\n", id); return 0; }
輸出:併發
id=6593687681236992000
代碼實現主要用到了左移和或位運算(或運算),各個語言相似。上面的實現輸出的結果是一個19位長度的整數。分佈式
一、時間戳優化
若是時間戳取當前毫秒級時間戳,那麼只能表示到2039年,遠遠不夠。咱們發現,1970到當前時間這個區間實際上是永遠都不會用了,那麼,爲什麼不使用偏移量呢?也就是時間戳部分不直接取當前毫秒級時間戳,而是在此基礎上減去一個過去時間:
id = (1572057648000 - 1569859200000) << 22;
輸出:
id=9220959240192000
上面代碼中,第一個時間戳是當前毫秒級時間戳,第二個則是一個過去時間戳(1569859200000表示2019-10-01 00:00:00)。這樣咱們能夠表示的年大概是 當前年份(例如2019) + 69
= 2088 年,很長一段時間內都夠用。
二、序列號
序列號默認取0,若是已經使用了則自增。若自增到4096,也就是同一毫秒內的序列號用完了,怎麼辦呢?須要等待至下一毫秒。部分代碼示例:
//同一毫秒併發調用 if (ts == (iw.last_time_stamp)) { //序列號自增 iw.sequence = (iw.sequence+1) & MASK_SEQUENCE; //序列號自增到最大值4096,4095 & 4096 = 0 if (iw.sequence == 0) { //等待至下一毫秒 ts = time_re_gen(ts); } } else { //同一毫秒沒有重複的 iw.last_time_stamp = ts; }
一、53bits版本:由於js只支持53位bit的數值
* 0 32 51 53 +-----------+------+------+ |0|time(32) |workid(8) |seq(12) | +-----------+------+------+
二、其它版本
咱們也能夠根據本身的業務需求,將不一樣區間的bit位進行調整。機器位和序列號ID並非必須的,能夠合併。或者拆分出更多的區間表示更多的意義。例如訂單號:
* 0 41 47 59 64 +-----------+------+------+------+------+ |0|time(41) |workid(6) |seq(12) | uid(4) +-----------+------+------+------+------+
咱們對訂單分16個(2^4)表,每次將 uid & 0xF
(也就是 uid & 15)的結果放到後四位,這樣之後根據uid查訂單的時候,uid mod 16
就能獲得數據在哪一個分表;同時根據訂單ID自己也能找到對應的分表。示例:
php > echo 1572070381000 << 22 | 1 << 16 | 0 << 4 | (1820 & 15); 6593741087309889548 php > echo 1572070381000 << 22 | 1 << 16 | 0 << 4 | (5177331 & 15); 6593741087309889539
驗證測試:
php > echo 1572070381000 << 22 | 1 << 16 | 0 << 4 | (5177331 & 15); 6593741087309889539 php > echo 6593741087309889548 % 16; 12 php > echo 1820 % 16; 12 php > echo 6593741087309889539 % 16; 3 php > echo 5177331 % 16; 3
從上面的結果能夠看出來,uid、訂單號都能定位到相同的分表。
對一個2的n次冪的數num取模(2^n),本質就是num對應二進制的末尾n個bit的和取模。
參考網上其它語言的版本,本身寫了C和PHP版本的:
github上其它版本:
fgy58963/php_snowflake: 推特分佈式主鍵生成算法的php擴展
https://github.com/fgy58963/p...
一、Twitter-Snowflake,64位自增ID算法詳解 - 漫漫路
https://www.lanindex.com/twit...
二、多key業務,數據庫水平切分架構一次搞定
https://mp.weixin.qq.com/s/PC...
本文首發於公衆號"飛鴻影的博客(fhyblog)",歡迎關注。博客地址: https://52fhy.cnblogs.com 。
(本文完)