美团分布式mysql_9种分布式ID生成之美团(Leaf)实战
整理了一些Java方面的架構、面試資料(微服務、集群、分布式、中間件等),有須要的小伙伴能夠關注公眾號【程序員內點事】,無套路自行領取javascript
更多優(yōu)選java
引言
前幾天寫過一篇《一口氣說出 9種 分布式ID生成方式,面試官有點懵了》,里邊簡單的介紹了九種分布式ID生成方式,可是對于像美團(Leaf)、滴滴(Tinyid)、百度(uid-generator)都是一筆帶過。而經過讀者留言發(fā)現,你們廣泛對他們哥三更感興趣,因此后邊會結合實戰(zhàn),詳細的對三種分布式ID生成器學習,今天先啃下美團(Leaf)。mysql
不了解分布式ID的同窗,先行去看《一口氣說出 9種 分布式ID生成方式,面試官有點懵了》溫習一下基礎知識,這里就再也不贅述了git
美團(Leaf)
Leaf是美團推出的一個分布式ID生成服務,名字取自德國哲學家、數學家萊布尼茨的一句話:“There are no two identical leaves in the world.”(“世界上沒有兩片相同的樹葉”),取個名字都這么有寓意,美團程序員牛掰啊!程序員
Leaf的優(yōu)點:高可靠、低延遲、全局惟一等特色。github
目前主流的分布式ID生成方式,大體都是基于數據庫號段模式和雪花算法(snowflake),而美團(Leaf)恰好同時兼具了這兩種方式,能夠根據不一樣業(yè)務場景靈活切換。面試
接下來結合實戰(zhàn),詳細的介紹一下Leaf的Leaf-segment號段模式和Leaf-snowflake模式算法
1、 Leaf-segment號段模式
Leaf-segment號段模式是對直接用數據庫自增ID充當分布式ID的一種優(yōu)化,減小對數據庫的頻率操做。至關于從數據庫批量的獲取自增ID,每次從數據庫取出一個號段范圍,例如 (1,1000] 表明1000個ID,業(yè)務服務將號段在本地生成1~1000的自增ID并加載到內存.。sql
大體的流程入下圖所示:
號段耗盡以后再去數據庫獲取新的號段,能夠大大的減輕數據庫的壓力。對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 '業(yè)務key',
`max_id` bigint(20) NOT NULL DEFAULT '1' COMMENT '當前已經分配了的最大id',
`step` int(11) NOT NULL COMMENT '初始步長,也是動態(tài)調整的最小步長',
`description` varchar(256) DEFAULT NULL COMMENT '業(yè)務key的描述',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '數據庫維護的更新時間',
PRIMARY KEY (`biz_tag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
預先插入一條測試的業(yè)務數據
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:針對不一樣業(yè)務需求,用biz_tag字段來隔離,若是之后須要擴容時,只需對biz_tag分庫分表便可
max_id:當前業(yè)務號段的最大值,用于計算下一個號段
step:步長,也就是每次獲取ID的數量
description:對于業(yè)務的描述,沒啥好說的
將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.enable 與 leaf.segment.enable 是沒法同時開啟的,不然項目將沒法啟動。
配置至關的簡單,直接啟動LeafServerApplication后就OK了,接下來測試一下,leaf是基于Http請求的發(fā)號服務, LeafController 中只有兩個方法,一個號段接口,一個snowflake接口,key就是數據庫中預先插入的業(yè)務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,結果正常返回,感受沒毛病,但當查了一下數據庫表中數據時發(fā)現了一個問題。
一般在用號段模式的時候,取號段的時機是在前一個號段消耗完的時候進行的,可剛剛才取了一個ID,數據庫中卻已經更新了max_id,也就是說leaf已經多獲取了一個號段,這是什么鬼操做?
Leaf為啥要這么設計呢?
Leaf 但愿能在DB中取號段的過程當中作到無阻塞!
當號段耗盡時再去DB中取下一個號段,若是此時網絡發(fā)生抖動,或者DB發(fā)生慢查詢,業(yè)務系統(tǒng)拿不到號段,就會致使整個系統(tǒng)的響應時間變慢,對流量巨大的業(yè)務,這是不可容忍的。
因此Leaf在當前號段消費到某個點時,就異步的把下一個號段加載到內存中。而不須要等到號段用盡的時候才去更新號段。這樣作很大程度上的下降了系統(tǒng)的風險。
那么某個點究竟是何時呢?
這里作了一個實驗,號段設置長度為step=10,max_id=1,
當我拿第一個ID時,看到號段增長了,1/10
當我拿第三個Id時,看到號段又增長了,3/10
Leaf采用雙buffer的方式,它的服務內部有兩個號段緩存區(qū)segment。當前號段已消耗10%時,還沒能拿到下一個號段,則會另啟一個更新線程去更新下一個號段。
簡而言之就是Leaf保證了老是會多緩存兩個號段,即使哪一時刻數據庫掛了,也會保證發(fā)號服務能夠正常工做一段時間。
一般推薦號段(segment)長度設置為服務高峰期發(fā)號QPS的600倍(10分鐘),這樣即便DB宕機,Leaf仍能持續(xù)發(fā)號10-20分鐘不受影響。
優(yōu)勢:
Leaf服務能夠很方便的線性擴展,性能徹底可以支撐大多數業(yè)務場景。
容災性高:Leaf服務內部有號段緩存,即便DB宕機,短期內Leaf仍能正常對外提供服務。
缺點:
ID號碼不夠隨機,可以泄露發(fā)號數量的信息,不太安全。
DB宕機會形成整個系統(tǒng)不可用(用到數據庫的都有可能)。
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,至關于一臺機器對應一個順序節(jié)點,也就是一個workId。
Leaf-snowflake啟動服務的過程大體以下:
啟動Leaf-snowflake服務,鏈接Zookeeper,在leaf_forever父節(jié)點下檢查本身是否已經注冊過(是否有該順序子節(jié)點)。
若是有注冊過直接取回本身的workerID(zk順序節(jié)點生成的int類型ID號),啟動服務。
若是沒有注冊過,就在該父節(jié)點下面建立一個持久順序節(jié)點,建立成功后取回順序號當作本身的workerID號,啟動服務。
但Leaf-snowflake對Zookeeper是一種弱依賴關系,除了每次會去ZK拿數據之外,也會在本機文件系統(tǒng)上緩存一個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
優(yōu)勢:
ID號碼是趨勢遞增的8byte的64位數字,知足上述數據庫存儲的主鍵要求。
缺點:
依賴ZooKeeper,存在服務不可用風險(實在不知道有啥缺點了)
3、Leaf監(jiān)控
請求地址:http://127.0.0.1:8080/cache
針對服務自身的監(jiān)控,Leaf提供了Web層的內存數據映射界面,能夠實時看到全部號段的下發(fā)狀態(tài)。好比每一個號段雙buffer的使用狀況,當前ID下發(fā)到了哪一個位置等信息均可以在Web界面上查看。
總結
對于Leaf具體使用哪一種模式,仍是根據具體的業(yè)務場景使用,本文并無對Leaf源碼作過多的分析,由于Leaf 代碼量簡潔很好閱讀。后續(xù)還會把其余幾種分布式ID生成器,依次結合實戰(zhàn)介紹給你們,歡迎你們關注。
今天就說這么多,若是本文對您有一點幫助,但愿能獲得您一個點贊👍哦
您的承認才是我寫做的動力!
總結
以上是生活随笔為你收集整理的美团分布式mysql_9种分布式ID生成之美团(Leaf)实战的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微软跟投,估值31.5亿美元的光量子计算
- 下一篇: [Oracle] 一个通过添加本地分区索