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

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

更多優選html

引言

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

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

美團(Leaf)

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

Leaf的優點:高可靠低延遲全局惟一等特色。程序員

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

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

1、 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]。sql

因爲依賴數據庫,咱們先設計一下表結構:

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宕機會形成整個系統不可用(用到數據庫的都有可能)。

2、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,存在服務不可用風險(實在不知道有啥缺點了)

3、Leaf監控

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

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

在這裏插入圖片描述

總結

對於Leaf具體使用哪一種模式,仍是根據具體的業務場景使用,本文並無對Leaf源碼作過多的分析,由於Leaf 代碼量簡潔很好閱讀。後續還會把其餘幾種分佈式ID生成器,依次結合實戰介紹給你們,歡迎你們關注。


今天就說這麼多,若是本文對您有一點幫助,但願能獲得您一個點贊👍哦

您的承認纔是我寫做的動力!


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

原文出處:https://www.cnblogs.com/chengxy-nds/p/12377352.html

相關文章
相關標籤/搜索