美團Leaf分布式ID生成策略


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模式

image

 

 

弱依賴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); }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM