目錄
闡述背景
Leaf snowflake 模式介紹
Leaf segment 模式介紹
Leaf 改造支持 RPC
闡述背景
不吹噓,不誇張,項目中用到 ID 生成的場景確實挺多。好比業務要作冪等的時候,若是沒有合適的業務字段去作惟一標識,那就須要單獨生成一個惟一的標識,這個場景相信你們不陌生。html
不少時候爲了圖方即可能就是寫一個簡單的 ID 生成工具類,直接開用。作的好點的可能單獨出一個 Jar 包讓其餘項目依賴,作的很差的頗有可能就是 Copy 了 N 份同樣的代碼。git
單獨搞一個獨立的 ID 生成服務很是有必要,固然咱們也不必本身作造輪子,有現成開源的直接用就是了。若是人手夠,不差錢,自研也能夠。github
今天爲你們介紹一款美團開源的 ID 生成框架 Leaf,在 Leaf 的基礎上稍微擴展下,增長 RPC 服務的暴露和調用,提升 ID 獲取的性能。redis
Leaf 介紹
Leaf 最先期需求是各個業務線的訂單 ID 生成需求。在美團早期,有的業務直接經過 DB 自增的方式生成 ID,有的業務經過 redis 緩存來生成 ID,也有的業務直接用 UUID 這種方式來生成 ID。以上的方式各自有各自的問題,所以咱們決定實現一套分佈式 ID 生成服務來知足需求。算法
具體 Leaf 設計文檔見:https://tech.meituan.com/2017/04/21/mt-leaf.html[1]spring
目前 Leaf 覆蓋了美團點評公司內部金融、餐飲、外賣、酒店旅遊、貓眼電影等衆多業務線。在 4C8G VM 基礎上,經過公司 RPC 方式調用,QPS 壓測結果近 5w/s,TP999 1ms。sql
snowflake 模式
snowflake 是 Twitter 開源的分佈式 ID 生成算法,被普遍應用於各類生成 ID 的場景。Leaf 中也支持這種方式去生成 ID。數據庫
使用步驟以下:apache
修改配置 leaf.snowflake.enable=true 開啓 snowflake 模式。bootstrap
修改配置 leaf.snowflake.zk.address 和 leaf.snowflake.port 爲你本身的 Zookeeper 地址和端口。
想必你們很好奇,爲何這裏依賴了 Zookeeper 呢?
那是由於 snowflake 的 ID 組成中有 10bit 的 workerId,以下圖:
snowflake組成
通常若是服務數量很少的話手動設置也沒問題,還有一些框架中會採用約定基於配置的方式,好比基於 IP 生成 wokerID,基於 hostname 最後幾位生成 wokerID,手動在機器上配置,手動在程序啓動時傳入等等方式。
Leaf 中爲了簡化 wokerID 的配置,因此採用了 Zookeeper 來生成 wokerID。就是用了 Zookeeper 持久順序節點的特性自動對 snowflake 節點配置 wokerID。
若是你公司沒有用 Zookeeper,又不想由於 Leaf 去單獨部署 Zookeeper 的話,你能夠將源碼中這塊的邏輯改掉,好比本身提供一個生成順序 ID 的服務來替代 Zookeeper。
segment 模式
segment 是 Leaf 基於數據庫實現的 ID 生成方案,若是調用量不大,徹底能夠用 Mysql 的自增 ID 來實現 ID 的遞增。
Leaf 雖然也是基於 Mysql,可是作了不少的優化,下面簡單的介紹下 segment 模式的原理。
首先咱們須要在數據庫中新增一張表用於存儲 ID 相關的信息。
CREATE TABLE `leaf_alloc` ( `biz_tag` varchar(128) NOT NULL DEFAULT '', `max_id` bigint(20) NOT NULL DEFAULT '1', `step` int(11) NOT NULL, `description` varchar(256) DEFAULT NULL, `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`biz_tag`) ) ENGINE=InnoDB;
biz_tag 用於區分業務類型,好比下單,支付等。若是之後有性能需求須要對數據庫擴容,只須要對 biz_tag 分庫分表就行。
max_id 表示該 biz_tag 目前所被分配的 ID 號段的最大值。
step 表示每次分配的號段長度。
下圖是 segment 的架構圖:
segment架構
從上圖咱們能夠看出,當多個服務同時對 Leaf 進行 ID 獲取時,會傳入對應的 biz_tag,biz_tag 之間是相互隔離的,互不影響。
好比 Leaf 有三個節點,當 test_tag 第一次請求到 Leaf1 的時候,此時 Leaf1 的 ID 範圍就是 1~1000。
當 test_tag 第二次請求到 Leaf2 的時候,此時 Leaf2 的 ID 範圍就是 1001~2000。
當 test_tag 第三次請求到 Leaf3 的時候,此時 Leaf3 的 ID 範圍就是 2001~3000。
好比 Leaf1 已經知道本身的 test_tag 的 ID 範圍是 1~1000,那麼後續請求過來獲取 test_tag 對應 ID 時候,就會從 1 開始依次遞增,這個過程是在內存中進行的,性能高。不用每次獲取 ID 都去訪問一次數據庫。
問題一
這個時候又有人說了,若是併發量很大的話,1000 的號段長度一下就被用完了啊,此時就得去申請下一個範圍,這期間進來的請求也會由於 DB 號段沒有取回來,致使線程阻塞。
放心,Leaf 中已經對這種狀況作了優化,不會等到 ID 消耗完了纔去從新申請,會在還沒用完以前就去申請下一個範圍段。併發量大的問題你能夠直接將 step 調大便可。
問題二
這個時候又有人說了,若是 Leaf 服務掛掉某個節點會不會有影響呢?
首先 Leaf 服務是集羣部署,通常都會註冊到註冊中心讓其餘服務發現。掛掉一個不要緊,還有其餘的 N 個服務。問題是對 ID 的獲取有問題嗎? 會不會出現重複的 ID 呢?
答案是沒問題的,若是 Leaf1 掛了的話,它的範圍是 1~1000,假如它當前正獲取到了 100 這個階段,而後服務掛了。服務重啓後,就會去申請下一個範圍段了,不會再使用 1~1000。因此不會有重複 ID 出現。
Leaf 改造支持 RPC
若是大家的調用量很大,爲了追求更高的性能,能夠本身擴展一下,將 Leaf 改形成 Rpc 協議暴露出去。
首先將 Leaf 的 Spring 版本升級到 5.1.8.RELEASE,修改父 pom.xml 便可。
<spring.version>5.1.8.RELEASE</spring.version>
而後將 Spring Boot 的版本升級到 2.1.6.RELEASE,修改 leaf-server 的 pom.xml。
<spring-boot-dependencies.version>2.1.6.RELEASE</spring-boot-dependencies.version>
還須要在 leaf-server 的 pom 中增長 nacos 相關的依賴,由於咱們 kitty-cloud 是用的 nacos。同時還須要依賴 dubbo,才能夠暴露 rpc 服務。
<dependency> <groupId>com.cxytiandi</groupId> <artifactId>kitty-spring-cloud-starter-nacos</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.cxytiandi</groupId> <artifactId>kitty-spring-cloud-starter-dubbo</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> </dependency>
在 resource 下建立 bootstrap.properties 文件,增長 nacos 相關的配置信息。
spring.application.name=LeafSnowflake dubbo.scan.base-packages=com.sankuai.inf.leaf.server.controller dubbo.protocol.name=dubbo dubbo.protocol.port=20086 dubbo.registry.address=spring-cloud://localhost spring.cloud.nacos.discovery.server-addr=47.105.66.210:8848 spring.cloud.nacos.config.server-addr=${spring.cloud.nacos.discovery.server-addr}
Leaf 默認暴露的 Rest 服務是 LeafController 中,如今的需求是既要暴露 Rest 又要暴露 RPC 服務,因此咱們抽出兩個接口。一個是 Segment 模式,一個是 Snowflake 模式。
Segment 模式調用客戶端
/** * 分佈式ID服務客戶端-Segment模式 * * @做者 尹吉歡 * @我的微信 jihuan900 * @微信公衆號 猿天地 * @GitHub https://github.com/yinjihuan * @做者介紹 http://cxytiandi.com/about * @時間 2020-04-06 16:20 */ @FeignClient("${kitty.id.segment.name:LeafSegment}") public interface DistributedIdLeafSegmentRemoteService { @RequestMapping(value = "/api/segment/get/{key}") String getSegmentId(@PathVariable("key") String key); }
Snowflake 模式調用客戶端
/** * 分佈式ID服務客戶端-Snowflake模式 * * @做者 尹吉歡 * @我的微信 jihuan900 * @微信公衆號 猿天地 * @GitHub https://github.com/yinjihuan * @做者介紹 http://cxytiandi.com/about * @時間 2020-04-06 16:20 */ @FeignClient("${kitty.id.snowflake.name:LeafSnowflake}") public interface DistributedIdLeafSnowflakeRemoteService { @RequestMapping(value = "/api/snowflake/get/{key}") String getSnowflakeId(@PathVariable("key") String key); }
使用方能夠根據使用場景來決定用 RPC 仍是 Http 進行調用,若是用 RPC 就@Reference 注入 Client,若是要用 Http 就用@Autowired 注入 Client。
最後改造 LeafController 同時暴露兩種協議便可。
@Service(version = "1.0.0", group = "default") @RestController public class LeafController implements DistributedIdLeafSnowflakeRemoteService, DistributedIdLeafSegmentRemoteService { private Logger logger = LoggerFactory.getLogger(LeafController.class); @Autowired private SegmentService segmentService; @Autowired private SnowflakeService snowflakeService; @Override public String getSegmentId(@PathVariable("key") String key) { return get(key, segmentService.getId(key)); } @Override 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()); } }
擴展後的源碼參考:https://github.com/yinjihuan/Leaf/tree/rpc_support[2]
感興趣的 Star 下唄:https://github.com/yinjihuan/kitty[3]
關於做者:尹吉歡,簡單的技術愛好者,《Spring Cloud 微服務-全棧技術與案例解析》, 《Spring Cloud 微服務 入門 實戰與進階》做者, 公衆號 猿天地 發起人。我的微信 jihuan900,歡迎勾搭。
參考資料
[1]
mt-leaf.html: https://tech.meituan.com/2017/04/21/mt-leaf.html
[2]
Leaf/tree/rpc_support: https://github.com/yinjihuan/Leaf/tree/rpc_support
[3]
kitty: https://github.com/yinjihuan/kitty
相關推薦
後臺回覆 學習資料 領取學習視頻
若有收穫,點個在看,誠摯感謝
尹吉歡我不差錢啊