分布式ID


什么是分布式 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 有兩種持久化方式 RDBAOF,RDB 會定時打一個快照進行持久化,假如連續自增但 Redis 沒及時持久化,而這會 Redis 掛掉了,重啟 Redis 后會出現 ID 重復的情況,AOF 會對每條寫命令進行持久化,即使 Redis 掛掉了也不會出現 ID 重復的情況,但由於 incr 命令的特殊性,會導致 Redis 重啟恢復的數據時間過長

雪花算法

雪花算法(Snowflake),是 twitter 公司內部分布式項目采用的 ID 生成算法

美團(Leaf)

Leaf 由美團開發,支持號段模式和 snowflake 算法模式,可以切換使用

雪花算法

結構

image-20211002163042130

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

下載源碼

image-20211002222429225

下載后,進入目錄編譯

mvn clean install -Dmaven.test.skip=true

image-20211002222654968

創建 maven 工程

image-20211003093049339

添加依賴

<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')

image-20211003094700421

在 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 效果如下圖所示:

image-20211003100442404

雪花算法本分不演示,因博主沒有搭建 zk 環境,所以演示不了,雪花算法效果也是很簡單,訪問第二個接口即可生成

注意事項

  • zookeeper 要打開


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM