9種分佈式ID生成之美團(Leaf)實戰

整理了一些Java方面的架構、面試資料(微服務、集羣、分佈式、中間件等),有需要的小夥伴可以關注公衆號【程序員內點事】,無套路自行領取

更多優選

引言

前幾天寫過一篇《一口氣說出 9種 分佈式ID生成方式,面試官有點懵了》,裏邊簡單的介紹了九種分佈式ID生成方式,但是對於像美團(Leaf)滴滴(Tinyid)百度(uid-generator)都是一筆帶過。而通過讀者留言發現,大家普遍對他們哥三更感興趣,所以後邊會結合實戰,詳細的對三種分佈式ID生成器學習,今天先啃下美團(Leaf)

不瞭解分佈式ID的同學,先行去看《一口氣說出 9種 分佈式ID生成方式,面試官有點懵了》溫習一下基礎知識,這裏就不再贅述了

美團(Leaf)

Leaf是美團推出的一個分佈式ID生成服務,名字取自德國哲學家、數學家萊布尼茨的一句話:“There are no two identical leaves in the world.”(“世界上沒有兩片相同的樹葉”),取個名字都這麼有寓意,美團程序員牛掰啊!

Leaf的優勢:高可靠低延遲全局唯一等特點。

目前主流的分佈式ID生成方式,大致都是基於數據庫號段模式雪花算法(snowflake),而美團(Leaf)剛好同時兼具了這兩種方式,可以根據不同業務場景靈活切換。

接下來結合實戰,詳細的介紹一下LeafLeaf-segment號段模式Leaf-snowflake模式

一、 Leaf-segment號段模式

Leaf-segment號段模式是對直接用數據庫自增ID充當分佈式ID的一種優化,減少對數據庫的頻率操作。相當於從數據庫批量的獲取自增ID,每次從數據庫取出一個號段範圍,例如 (1,1000] 代表1000個ID,業務服務將號段在本地生成1~1000的自增ID並加載到內存.。

大致的流程入下圖所示:
在這裏插入圖片描述
號段耗盡之後再去數據庫獲取新的號段,可以大大的減輕數據庫的壓力。對max_id字段做一次update操作,update max_id= max_id + step,update成功則說明新號段獲取成功,新的號段範圍是(max_id ,max_id +step]。

由於依賴數據庫,我們先設計一下表結構:

CREATE TABLE `leaf_alloc` (
  `biz_tag` varchar(128) NOT NULL DEFAULT '' COMMENT '業務key',
  `max_id` bigint(20) NOT NULL DEFAULT '1' COMMENT '當前已經分配了的最大id',
  `step` int(11) NOT NULL COMMENT '初始步長,也是動態調整的最小步長',
  `description` varchar(256) DEFAULT NULL COMMENT '業務key的描述',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '數據庫維護的更新時間',
  PRIMARY KEY (`biz_tag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

預先插入一條測試的業務數據

INSERT INTO `leaf_alloc` (`biz_tag`, `max_id`, `step`, `description`, `update_time`) VALUES ('leaf-segment-test', '0', '10', '測試', '2020-02-28 10:41:03');
  • biz_tag:針對不同業務需求,用biz_tag字段來隔離,如果以後需要擴容時,只需對biz_tag分庫分表即可

  • max_id:當前業務號段的最大值,用於計算下一個號段

  • step:步長,也就是每次獲取ID的數量

  • description:對於業務的描述,沒啥好說的

將Leaf項目下載到本地:https://github.com/Meituan-Dianping/Leaf

修改一下項目中的leaf.properties文件,添加數據庫配置

leaf.name=com.sankuai.leaf.opensource.test
leaf.segment.enable=true
leaf.jdbc.url=jdbc:mysql://127.0.0.1:3306/xin-master?useUnicode=true&characterEncoding=utf8
leaf.jdbc.username=junkang
leaf.jdbc.password=junkang

leaf.snowflake.enable=false

注意leaf.snowflake.enableleaf.segment.enable 是無法同時開啓的,否則項目將無法啓動。

配置相當的簡單,直接啓動LeafServerApplication後就OK了,接下來測試一下,leaf是基於Http請求的發號服務, LeafController 中只有兩個方法,一個號段接口,一個snowflake接口,key就是數據庫中預先插入的業務biz_tag


@RestController
public class LeafController {
    private Logger logger = LoggerFactory.getLogger(LeafController.class);

    @Autowired
    private SegmentService segmentService;
    @Autowired
    private SnowflakeService snowflakeService;

    /**
     * 號段模式
     * @param key
     * @return
     */
    @RequestMapping(value = "/api/segment/get/{key}")
    public String getSegmentId(@PathVariable("key") String key) {
        return get(key, segmentService.getId(key));
    }

    /**
     * 雪花算法模式
     * @param key
     * @return
     */
    @RequestMapping(value = "/api/snowflake/get/{key}")
    public String getSnowflakeId(@PathVariable("key") String key) {
        return get(key, snowflakeService.getId(key));
    }

    private String get(@PathVariable("key") String key, Result id) {
        Result result;
        if (key == null || key.isEmpty()) {
            throw new NoKeyException();
        }
        result = id;
        if (result.getStatus().equals(Status.EXCEPTION)) {
            throw new LeafServerException(result.toString());
        }
        return String.valueOf(result.getId());
    }
}

訪問:http://127.0.0.1:8080/api/segment/get/leaf-segment-test,結果正常返回,感覺沒毛病,但當查了一下數據庫表中數據時發現了一個問題。
在這裏插入圖片描述
在這裏插入圖片描述
通常在用號段模式的時候,取號段的時機是在前一個號段消耗完的時候進行的,可剛剛纔取了一個ID,數據庫中卻已經更新了max_id,也就是說leaf已經多獲取了一個號段,這是什麼鬼操作?
在這裏插入圖片描述

Leaf爲啥要這麼設計呢?

Leaf 希望能在DB中取號段的過程中做到無阻塞!

當號段耗盡時再去DB中取下一個號段,如果此時網絡發生抖動,或者DB發生慢查詢,業務系統拿不到號段,就會導致整個系統的響應時間變慢,對流量巨大的業務,這是不可容忍的。

所以Leaf在當前號段消費到某個點時,就異步的把下一個號段加載到內存中。而不需要等到號段用盡的時候纔去更新號段。這樣做很大程度上的降低了系統的風險。

那麼某個點到底是什麼時候呢?

這裏做了一個實驗,號段設置長度爲step=10max_id=1
在這裏插入圖片描述
當我拿第一個ID時,看到號段增加了,1/10
在這裏插入圖片描述
在這裏插入圖片描述
當我拿第三個Id時,看到號段又增加了,3/10
在這裏插入圖片描述
在這裏插入圖片描述
Leaf採用雙buffer的方式,它的服務內部有兩個號段緩存區segment。當前號段已消耗10%時,還沒能拿到下一個號段,則會另啓一個更新線程去更新下一個號段。

簡而言之就是Leaf保證了總是會多緩存兩個號段,即便哪一時刻數據庫掛了,也會保證發號服務可以正常工作一段時間。

在這裏插入圖片描述
通常推薦號段(segment)長度設置爲服務高峯期發號QPS的600倍(10分鐘),這樣即使DB宕機,Leaf仍能持續發號10-20分鐘不受影響。

優點:

  • Leaf服務可以很方便的線性擴展,性能完全能夠支撐大多數業務場景。
  • 容災性高:Leaf服務內部有號段緩存,即使DB宕機,短時間內Leaf仍能正常對外提供服務。

缺點:

  • ID號碼不夠隨機,能夠泄露發號數量的信息,不太安全。
  • DB宕機會造成整個系統不可用(用到數據庫的都有可能)。

二、Leaf-snowflake

Leaf-snowflake基本上就是沿用了snowflake的設計,ID組成結構:正數位(佔1比特)+ 時間戳(佔41比特)+ 機器ID(佔5比特)+ 機房ID(佔5比特)+ 自增值(佔12比特),總共64比特組成的一個Long類型。

Leaf-snowflake不同於原始snowflake算法地方,主要是在workId的生成上,Leaf-snowflake依靠Zookeeper生成workId,也就是上邊的機器ID(佔5比特)+ 機房ID(佔5比特)。Leaf中workId是基於ZooKeeper的順序Id來生成的,每個應用在使用Leaf-snowflake時,啓動時都會都在Zookeeper中生成一個順序Id,相當於一臺機器對應一個順序節點,也就是一個workId。

在這裏插入圖片描述
Leaf-snowflake啓動服務的過程大致如下:

  • 啓動Leaf-snowflake服務,連接Zookeeper,在leaf_forever父節點下檢查自己是否已經註冊過(是否有該順序子節點)。
  • 如果有註冊過直接取回自己的workerID(zk順序節點生成的int類型ID號),啓動服務。
  • 如果沒有註冊過,就在該父節點下面創建一個持久順序節點,創建成功後取回順序號當做自己的workerID號,啓動服務。

Leaf-snowflake對Zookeeper是一種弱依賴關係,除了每次會去ZK拿數據以外,也會在本機文件系統上緩存一個workerID文件。一旦ZooKeeper出現問題,恰好機器出現故障需重啓時,依然能夠保證服務正常啓動。

啓動Leaf-snowflake模式也比較簡單,起動本地ZooKeeper,修改一下項目中的leaf.properties文件,關閉leaf.segment模式,啓用leaf.snowflake模式即可。

leaf.segment.enable=false
#leaf.jdbc.url=jdbc:mysql://127.0.0.1:3306/xin-master?useUnicode=true&characterEncoding=utf8
#leaf.jdbc.username=junkang
#leaf.jdbc.password=junkang

leaf.snowflake.enable=true
leaf.snowflake.zk.address=127.0.0.1
leaf.snowflake.port=2181
    /**
     * 雪花算法模式
     * @param key
     * @return
     */
    @RequestMapping(value = "/api/snowflake/get/{key}")
    public String getSnowflakeId(@PathVariable("key") String key) {
        return get(key, snowflakeService.getId(key));
    }

測試一下,訪問:http://127.0.0.1:8080/api/snowflake/get/leaf-segment-test
在這裏插入圖片描述
優點:

  • ID號碼是趨勢遞增的8byte的64位數字,滿足上述數據庫存儲的主鍵要求。

缺點:

  • 依賴ZooKeeper,存在服務不可用風險(實在不知道有啥缺點了)

三、Leaf監控

請求地址:http://127.0.0.1:8080/cache

針對服務自身的監控,Leaf提供了Web層的內存數據映射界面,可以實時看到所有號段的下發狀態。比如每個號段雙buffer的使用情況,當前ID下發到了哪個位置等信息都可以在Web界面上查看。

在這裏插入圖片描述

總結

對於Leaf具體使用哪種模式,還是根據具體的業務場景使用,本文並沒有對Leaf源碼做過多的分析,因爲Leaf 代碼量簡潔很好閱讀。後續還會把其他幾種分佈式ID生成器,依次結合實戰介紹給大家,歡迎大家關注。


今天就說這麼多,如果本文對您有一點幫助,希望能得到您一個點贊👍哦

您的認可纔是我寫作的動力!


整理了一些Java方面的架構、面試資料(微服務、集羣、分佈式、中間件等),有需要的小夥伴可以關注公衆號【程序員內點事】,無套路自行領取

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章