snowflake ID 算法是 twitter 使用的惟一 ID 生成算法,爲了知足 Twitter 每秒上萬條消息的請求,使每條消息有惟1、有必定順序的 ID ,且支持分佈式生成。node
主要解決了高併發時 ID 生成不重複的問題
snowflake ID 的結構是一個 64 bit 的 int 型數據。git
如圖所示 :github
1 bit:不使用,能夠是 1 或 0算法
41 bit:記錄時間戳 (當前時間戳減去用戶設置的初始時間,毫秒錶示),可記錄最多 69 年的時間戳數據segmentfault
10 bit:用來記錄分佈式節點 ID,通常每臺機器一個惟一 ID,也能夠多進程每一個進程一個惟一 ID,最大可部署 1024 個節點安全
12 bit:序列號,用來記錄不一樣 ID 同一毫秒時的序列號,最多可生成 4096 個序列號併發
時間戳、節點 ID 和序列號的位數能夠根據業務自由浮動調整
假設在一個節點 (機器) 上,節點 ID 惟一,併發時有多個線程去生成 ID。
知足以上條件時,若是多個進程在同一毫秒內生成 ID,那麼序列號步進 (加一),這裏要保證序列號的操做併發安全,使同一毫秒內生成的 ID 擁有不一樣序列號。若是序列號達到上限,則等待這一毫秒結束,在新的毫秒繼續步進。分佈式
這樣保證了:
全部生成的 ID 按時間趨勢遞增 (有序)
整個分佈式系統內不會產生重複 ID (惟一)
go 有封裝好的協程 goroutine,能夠很好的處理併發,能夠加鎖保證數據的同步安全,有很好的性能。固然其它語言如 Java、Scala 也是徹底能夠的。函數
一、肯定惟一的節點 ID
二、設置一個初始時間戳 (毫秒錶示)
三、處理併發時序列號步進和併發安全問題
四、組裝各個 bits ,生成最終的 64 bit ID高併發
首先咱們要引入基礎的模塊
import ( "fmt" // 測試、打印 "time" // 獲取時間 "errors" // 生成錯誤 "sync" // 使用互斥鎖 )
基礎常量定義
這裏求最大值使用了位運算,-1 的二進制表示爲 1 的補碼,感興趣的同窗能夠本身算算試試 -1 ^ (-1 << nodeBits) 這裏是否是等於 1023
const ( nodeBits uint8 = 10 // 節點 ID 的位數 stepBits uint8 = 12 // 序列號的位數 nodeMax int64 = -1 ^ (-1 << nodeBits) // 節點 ID 的最大值,用於檢測溢出 stepMax int64 = -1 ^ (-1 << stepBits) // 序列號的最大值,用於檢測溢出 timeShift uint8 = nodeBits + stepBits // 時間戳向左的偏移量 nodeShift uint8 = stepBits // 節點 ID 向左的偏移量 )
設置初始時間的時間戳 (毫秒錶示),我這裏使用 twitter 設置的一個時間,這個能夠隨意設置 ,比如今的時間靠前便可。
var Epoch int64 = 1288834974657 // timestamp 2006-03-21:20:50:14 GMT
ID 結構和 Node 結構的實現
這裏咱們申明一個 int64 的 ID 類型 (這樣能夠爲此類型定義方法,比直接使用 int64 變量更靈活)
type ID int64
Node 結構用來存儲一個節點 (機器) 上的基礎數據
type Node struct { mu sync.Mutex // 添加互斥鎖,保證併發安全 timestamp int64 // 時間戳部分 node int64 // 節點 ID 部分 step int64 // 序列號 ID 部分 }
獲取 Node 類型實例的函數,用於得到當前節點的 Node 實例
func NewNode(node int64) (*Node, error) { // 若是超出節點的最大範圍,產生一個 error if node < 0 || node > nodeMax { return nil, errors.New("Node number must be between 0 and 1023") } // 生成並返回節點實例的指針 return &Node{ timestamp: 0, node: node, step: 0, }, nil }
最後一步,生成 ID 的方法
func (n *Node) Generate() ID { n.mu.Lock() // 保證併發安全, 加鎖 defer n.mu.Unlock() // 方法運行完畢後解鎖 // 獲取當前時間的時間戳 (毫秒數顯示) now := time.Now().UnixNano() / 1e6 if n.timestamp == now { // step 步進 1 n.step ++ // 當前 step 用完 if n.step > stepMax { // 等待本毫秒結束 for now <= n.timestamp { now = time.Now().UnixNano() / 1e6 } } } else { // 本毫秒內 step 用完 n.step = 0 } n.timestamp = now // 移位運算,生產最終 ID result := ID((now - Epoch) << timeShift | (n.node << nodeShift) | (n.step)) return result }
咱們使用循環去開啓多個 goroutine 去併發生成 ID,而後使用 map 以 ID 做爲鍵存儲,來判斷是否生成了惟一的 ID
main 函數代碼
func main() { // 測試腳本 // 生成節點實例 node, err := NewNode(1) if err != nil { fmt.Println(err) return } ch := make(chan ID) count := 10000 // 併發 count 個 goroutine 進行 snowflake ID 生成 for i := 0; i < count; i++ { go func() { id := node.Generate() ch <- id }() } defer close(ch) m := make(map[ID]int) for i := 0; i < count; i++ { id := <- ch // 若是 map 中存在爲 id 的 key, 說明生成的 snowflake ID 有重複 _, ok := m[id] if ok { fmt.Printf("ID is not unique!\n") return } // 將 id 做爲 key 存入 map m[id] = i } // 成功生成 snowflake ID fmt.Println("All ", count, " snowflake ID generate successed!\n") }
完整的程序實例 :點我查看
你能夠用 go 的 net/http 包處理併發請求,生成 ID 而且返回 http 響應結果。
Just do it