1. 程式人生 > 實用技巧 >2020杭電多校第一場 hdu6759 Leading Robots

2020杭電多校第一場 hdu6759 Leading Robots

目錄

  1. 闡述背景
  2. Leaf snowflake 模式介紹
  3. Leaf segment 模式介紹
  4. Leaf 改造支援 RPC

闡述背景

不吹噓,不誇張,專案中用到 ID 生成的場景確實挺多。比如業務要做冪等的時候,如果沒有合適的業務欄位去做唯一標識,那就需要單獨生成一個唯一的標識,這個場景相信大家不陌生。

很多時候為了圖方便可能就是寫一個簡單的 ID 生成工具類,直接開用。做的好點的可能單獨出一個 Jar 包讓其他專案依賴,做的不好的很有可能就是 Copy 了 N 份一樣的程式碼。

單獨搞一個獨立的 ID 生成服務非常有必要,當然我們也沒必要自己做造輪子,有現成開源的直接用就是了。如果人手夠,不差錢,自研也可以。

今天為大家介紹一款美團開源的 ID 生成框架 Leaf,在 Leaf 的基礎上稍微擴充套件下,增加 RPC 服務的暴露和呼叫,提高 ID 獲取的效能。

Leaf 介紹

Leaf 最早期需求是各個業務線的訂單 ID 生成需求。在美團早期,有的業務直接通過 DB 自增的方式生成 ID,有的業務通過 redis 快取來生成 ID,也有的業務直接用 UUID 這種方式來生成 ID。以上的方式各自有各自的問題,因此我們決定實現一套分散式 ID 生成服務來滿足需求。

具體 Leaf 設計文件見:https://tech.meituan.com/2017/04/21/mt-leaf.html[1]

目前 Leaf 覆蓋了美團點評公司內部金融、餐飲、外賣、酒店旅遊、貓眼電影等眾多業務線。在 4C8G VM 基礎上,通過公司 RPC 方式呼叫,QPS 壓測結果近 5w/s,TP999 1ms。

snowflake 模式

snowflake 是 Twitter 開源的分散式 ID 生成演算法,被廣泛應用於各種生成 ID 的場景。Leaf 中也支援這種方式去生成 ID。

使用步驟如下:

修改配置 leaf.snowflake.enable=true 開啟 snowflake 模式。

修改配置 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]

參考資料

[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

轉自:https://mp.weixin.qq.com/s?__biz=MzIwMDY0Nzk2Mw==&mid=2650322125&idx=1&sn=2e87ef9e16c6a4757b38f67332272ebd&chksm=8ef5e0b9b98269af5d65fcbb70c2ada3b463b26125793661dfe78dac5675b172ca91102bf748&scene=158#rd