什么是分布式 ID
在分布式系統中,經常需要一些全局唯一的 ID 對數據、消息、http 請求等進行唯一標識。那么這個全局唯一 ID 就叫分布式 ID
為什么需要分布式 ID
如果 id 我們使用的是數據庫的自增長類型,在分布式系統中需要分庫和分表時,會有兩個相同的表,有可能產生主鍵沖突,電商訂單號,采用自增方式,是最簡單的生成規則。但是!這種與流水號相同的訂單號很容易就被競爭對手看出你公司真實的運營信息
分布式 ID 特點
- 全局唯一
- 高性能
- 高可用
常見分布式 ID 解決方案
時間戳
在高並發時,可能會產生沖突
UUID
生成足夠簡單,本地生成無網絡消耗,具有唯一性,缺點:無序的字符串,不具備趨勢自增特性,沒有具體的業務含義,長度過長 16 字節 128 位,36 位長度的字符串,存儲以及查詢對 MySQL 的性能消耗較大,MySQL 官方明確建議主鍵要盡量越短越好,作為數據庫主鍵 UUID 的無序性會導致數據位置頻繁變動,嚴重影響性能
數據庫自增 ID
實現簡單,ID 單調自增,數值類型查詢速度快,缺點:DB 單點存在宕機風險,無法扛住高並發場景
數據庫的號段模式
號段模式是當下分布式 ID 生成器的主流實現方式之一,號段模式可以理解為從數據庫批量的獲取自增 ID,每次從數據庫取出一個號段范圍,例如(1,1000),代表 1000 個 ID,具體的業務服務將本號段,生成 1 ~ 1000 的自增 ID 並加載到內存,由於多業務端可能同時操作,所以采用版本號 version 樂觀鎖方式更新,這種分布式 ID 生成方式不強依賴於數據庫,不會頻繁的訪問數據庫,對數據庫的壓力小很多
基於 Redis 模式
利用 Redis 的 incr
命令實現 ID 的原子性自增,缺點:要考慮到 Redis 持久化的問題。Redis 有兩種持久化方式 RDB
和 AOF
,RDB 會定時打一個快照進行持久化,假如連續自增但 Redis 沒及時持久化,而這會 Redis 掛掉了,重啟 Redis 后會出現 ID 重復的情況,AOF 會對每條寫命令進行持久化,即使 Redis 掛掉了也不會出現 ID 重復的情況,但由於 incr
命令的特殊性,會導致 Redis 重啟恢復的數據時間過長
雪花算法
雪花算法(Snowflake),是 twitter
公司內部分布式項目采用的 ID 生成算法
美團(Leaf)
Leaf 由美團開發,支持號段模式和 snowflake 算法模式,可以切換使用
雪花算法
結構
snowflake 生成的是 Long 類型的 ID,一個 Long 類型占 8 個字節,每個字節占 8 比特,也就是說一個 Long 類型占 64 個比特
Snowflake ID 組成結構
- 正數位(占 1 比特):第一個 bit 位(1bit):Java 中 Long 的最高位是符號位代表正負,正數是 0,負數是 1,一般生成 ID 都為正數,所以默認為 0
- 時間戳(占 41 比特):時間戳部分(41bit):毫秒級的時間,不建議存當前時間戳,而是用(當前時間戳 - 固定開始時間戳)的差值,可以使產生的 ID 從更小的值開始,41 位的時間戳可以使用 69 年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69 年
- 機器ID(占 5 比特):工作機器 id(10bit):也被叫做 workId,這個可以靈活配置,機房或者機器號組合都可以
- 數據中心(占 5 比特):工作機器 id(10bit):也被叫做 workId,這個可以靈活配置,機房或者機器號組合都可以
- 自增值(占 12 比特):序列號部分(12bit),自增值支持同一毫秒內同一個節點可以生成 4096 個 ID
存在的問題
雪花算法目前存在時間回撥問題,而且不同的機器也無法完全保證時間一樣,所以可能會出現重復問題
美團(Leaf)
GitHub 地址:https://github.com/Meituan-Dianping/Leaf
下載源碼
下載后,進入目錄編譯
mvn clean install -Dmaven.test.skip=true
創建 maven 工程
添加依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.18.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<artifactId>leaf-boot-starter</artifactId>
<groupId>com.sankuai.inf.leaf</groupId>
<version>1.0.1-RELEASE</version>
</dependency>
<!--zk-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.6.0</version>
<exclusions>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
創建 leaf
數據庫,然后 SQL 腳本如下:
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;
insert into leaf_alloc(biz_tag, max_id, step, description) values('leaf-segment-test', 1, 2000, 'Test leaf Segment Mode Get Id')
在 resource 當中添加 leaf.properties
配置文件內容如下:
leaf.name=com.sankuai.leaf.opensource.test
leaf.segment.enable=true
leaf.segment.url=jdbc:mysql://127.0.0.1:3306/leaf?serverTimezone=UTC
leaf.segment.username=root
leaf.segment.password=1234
leaf.snowflake.enable=true
leaf.snowflake.address=192.168.101.88
leaf.snowflake.port=2181
創建控制器:
TestController.java
/**
* @author BNTang
* @version 1.0
* @project distributedId
* @description 美團leaf
* @since Created in 2021/10/3 003 9:48
**/
@RestController
public class TestController {
@Resource
private SegmentService segmentService;
@Resource
private SnowflakeService snowflakeService;
/**
* 分段式
*
* @return Id
*/
@GetMapping("/segment")
public Long segment() {
return segmentService.getId("leaf-segment-test").getId();
}
/**
* 雪花算法
*
* @return Id
*/
@GetMapping("/snowflake")
public Result snowflake() {
return snowflakeService.getId("imooc");
}
}
創建啟動類
SpringMainApp.java
/**
* @author BNTang
* @version 1.0
* @project distributedId
* @description
* @since Created in 2021/10/3 003 9:56
**/
@SpringBootApplication
@EnableLeafServer
public class SpringMainApp {
public static void main(String[] args) {
SpringApplication.run(SpringMainApp.class, args);
}
}
啟動工程,訪問 http://localhost:8080/segment 效果如下圖所示:
雪花算法本分不演示,因博主沒有搭建 zk 環境,所以演示不了,雪花算法效果也是很簡單,訪問第二個接口即可生成
注意事項
- zookeeper 要打開