整理了一些Java方面的架構、面試資料(微服務、集羣、分佈式、中間件等),有須要的小夥伴能夠關注公衆號【程序員內點事】,無套路自行領取javascript
更多優選java
前幾天寫過一篇《一口氣說出 9種 分佈式ID生成方式,面試官有點懵了》,裏邊簡單的介紹了九種分佈式ID生成方式,可是對於像美團(Leaf)
、滴滴(Tinyid)
、百度(uid-generator)
都是一筆帶過。而經過讀者留言發現,你們廣泛對他們哥三更感興趣,因此後邊會結合實戰,詳細的對三種分佈式ID生成器學習,今天先啃下美團(Leaf)
。mysql
不瞭解分佈式ID的同窗,先行去看《一口氣說出 9種 分佈式ID生成方式,面試官有點懵了》溫習一下基礎知識,這裏就再也不贅述了git
Leaf
是美團推出的一個分佈式ID生成服務,名字取自德國哲學家、數學家萊布尼茨的一句話:「There are no two identical leaves in the world.」(「世界上沒有兩片相同的樹葉」),取個名字都這麼有寓意,美團程序員牛掰啊!程序員
Leaf
的優點:高可靠
、低延遲
、全局惟一
等特色。github
目前主流的分佈式ID生成方式,大體都是基於數據庫號段模式
和雪花算法(snowflake)
,而美團(Leaf)恰好同時兼具了這兩種方式,能夠根據不一樣業務場景靈活切換。面試
接下來結合實戰,詳細的介紹一下Leaf
的Leaf-segment號段模式
和Leaf-snowflake模式
算法
Leaf-segment
號段模式是對直接用數據庫自增ID
充當分佈式ID
的一種優化,減小對數據庫的頻率操做。至關於從數據庫批量的獲取自增ID,每次從數據庫取出一個號段範圍,例如 (1,1000] 表明1000個ID,業務服務將號段在本地生成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 '業務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.enable
與 leaf.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
,結果正常返回,感受沒毛病,但當查了一下數據庫表中數據時發現了一個問題。
max_id
,也就是說
leaf
已經多獲取了一個號段,這是什麼鬼操做?
Leaf
爲啥要這麼設計呢?
Leaf
但願能在DB中取號段的過程當中作到無阻塞!
當號段耗盡時再去DB中取下一個號段,若是此時網絡發生抖動,或者DB發生慢查詢,業務系統拿不到號段,就會致使整個系統的響應時間變慢,對流量巨大的業務,這是不可容忍的。
因此Leaf
在當前號段消費到某個點時,就異步的把下一個號段加載到內存中。而不須要等到號段用盡的時候纔去更新號段。這樣作很大程度上的下降了系統的風險。
那麼某個點
究竟是何時呢?
這裏作了一個實驗,號段設置長度爲step=10
,max_id=1
,
Leaf
採用
雙buffer
的方式,它的服務內部有兩個號段緩存區
segment
。當前號段已消耗10%時,還沒能拿到下一個號段,則會另啓一個更新線程去更新下一個號段。
簡而言之就是Leaf
保證了老是會多緩存兩個號段,即使哪一時刻數據庫掛了,也會保證發號服務能夠正常工做一段時間。
segment
)長度設置爲服務高峯期發號QPS的600倍(10分鐘),這樣即便DB宕機,Leaf仍能持續發號10-20分鐘不受影響。
優勢:
缺點:
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是一種弱依賴關係,除了每次會去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
缺點:
請求地址:http://127.0.0.1:8080/cache
針對服務自身的監控,Leaf提供了Web層的內存數據映射界面,能夠實時看到全部號段的下發狀態。好比每一個號段雙buffer的使用狀況,當前ID下發到了哪一個位置等信息均可以在Web界面上查看。
對於Leaf具體使用哪一種模式,仍是根據具體的業務場景使用,本文並無對Leaf源碼作過多的分析,由於Leaf 代碼量簡潔很好閱讀。後續還會把其餘幾種分佈式ID生成器,依次結合實戰介紹給你們,歡迎你們關注。
今天就說這麼多,若是本文對您有一點幫助,但願能獲得您一個點贊👍哦
您的承認纔是我寫做的動力!
整理了一些Java方面的架構、面試資料(微服務、集羣、分佈式、中間件等),有須要的小夥伴能夠關注公衆號【程序員內點事】,無套路自行領取