ID生成原則
全局唯一性:不能出現重復的ID號,既然是唯一標識,這是最基本的要求。
趨勢遞增:在MySQL InnoDB引擎中使用的是聚集索引,由於多數RDBMS使用B-tree的數據結構來存儲索引數據,在主鍵的選擇上面我們應該盡量使用有序的主鍵保證寫入性能。
信息安全:如果ID是連續的,惡意用戶的扒取工作就非常容易做了,直接按照順序下載指定URL即可;如果是訂單號就更危險了,競對可以直接知道我們一天的單量。所以在一些應用場景下,會需要ID無規則、不規則。
segment號段模式
重要字段說明:biz_tag用來區分業務,max_id表示該biz_tag目前所被分配的ID號段的最大值,step表示每次分配的號段長度。
test_tag在第一台Leaf機器上是1~1000的號段,當這個號段用完時,會去加載另一個長度為step=1000的號段,假設另外兩台號段都沒有更新,這個時候第一台機器新加載的號段就應該是3001~4000。同時數據庫對應的biz_tag這條數據的max_id會從3000被更新成4000,更新號段的SQL語句如下:
UPDATE table SET max_id=max_id+step WHERE biz_tag=xxx
SELECT tag, max_id, step FROM table WHERE biz_tag=xxx
實時監控:
優點:
- ID號碼是趨勢遞增的8byte的64位數字,滿足上述數據庫存儲的主鍵要求。
- 容災性高:Leaf服務內部有號段緩存,即使DB宕機,短時間內Leaf仍能正常對外提供服務。
- 可以自定義max_id的大小,多個服務器不會出現主鍵重復。
- 熱加載序列分類,Schedule定時器。
- 加鎖控制,線程安全。
缺點:
- 初始化策略不友好,服務重啟后id可能會由10跳到2001。
- 當號段使用完之后還是會hang在更新數據庫的I/O上,tg999數據會出現偶爾的尖刺。
Snowflake模式
弱依賴ZooKeeper
除了每次會去ZK拿數據以外,也會在本機文件系統上緩存一個workerID文件。當ZooKeeper出現問題,恰好機器出現問題需要重啟時,能保證服務能夠正常啟動。這樣做到了對三方組件的弱依賴。一定程度上提高了SLA
解決時鍾問題
因為這種方案依賴時間,如果機器的時鍾發生了回撥,那么就會有可能生成重復的ID號,需要解決時鍾回退的問題。
由於強依賴時鍾,對時間的要求比較敏感,在機器工作時NTP同步也會造成秒級別的回退,建議可以直接關閉NTP同步。要么在時鍾回撥的時候直接不提供服務直接返回ERROR_CODE,等時鍾追上即可。或者做一層重試,然后上報報警系統,更或者是發現有時鍾回撥之后自動摘除本身節點並報警,如下:
public synchronized Result get(String key) { long timestamp = timeGen(); if (timestamp < lastTimestamp) { long offset = lastTimestamp - timestamp; if (offset <= 5) { try { wait(offset << 1); timestamp = timeGen(); if (timestamp < lastTimestamp) { return new Result(-1, Status.EXCEPTION); } } catch (InterruptedException e) { LOGGER.error("wait interrupted"); return new Result(-2, Status.EXCEPTION); } } else { return new Result(-3, Status.EXCEPTION); } } if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { //seq 為0的時候表示是下一毫秒時間開始對seq做隨機 sequence = RANDOM.nextInt(100); timestamp = tilNextMillis(lastTimestamp); } } else { //如果是新的ms開始 sequence = RANDOM.nextInt(100); } lastTimestamp = timestamp;
//時間戳偏移+workId偏移+隨機數 long id = ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence; return new Result(id, Status.SUCCESS); }