分佈式系統之全局唯一ID生成策略 (UUID,基於mysql的replace into基於redis生成全局唯一id,Twitter的分佈式自增id雪花算法 )

通過在 生產生活中,分佈式的系統以及大數據量的存儲和讀取都離不開 ID的 唯一性,例如訂單號,快遞單號,商品編號等等。通常我們都會採用 uuid ,mysql replace into ,

一、 UUID 

直接使用 java 代碼 本地生成,沒有網絡消。 但是會存在以下的缺點:

1. 無序,無法預測他的生成順序,不能生成遞增有序的數字。

首先分佈式id 一般都會作爲主鍵,但是mysql官方推薦主鍵要儘量越短越好,uuid 每一個都很長,所以不是很推薦。

2. uuid 作爲 主鍵,在特定的環境下會存在一些問題

比如做 DB主鍵的場景下,uuid 就非常不適用 mysql,官方有明確的建議主鍵要儘量越短越好,36個字符長度的uuid不符合要求。

3.索引,b+樹索引的分裂

既然分佈式id 是 主鍵,然後主鍵是包含索引的,然後 mysql 的 索引是通過b+樹 來實現的,每一次新的uuid 的數據插入,爲了查詢的優化,都會對索引底層的b+樹進行修改,因爲 uuid 是無序的,所以每一次的uuid 數據的插入都會對主鍵底層的b+樹進行很大的修改,這一點很不好,插入完全無序,不但會導致一些中間節點產生分裂,也會自創造出很多不飽和的節點,這樣大大降低了數據庫插入的性能。

二、mysql 的 replace into 生成唯一id

replcae into 首先嚐試 插入數據列表中,如果發現表中已經有此行數據,(根據主鍵或者唯一索引進行判斷)則先刪除,後插入。

實現方式: 

 create  table  `t_test` (

    id  bigint(20)  unsigned  not null auto_increment  primary key,

    stub char(1) not null default '',

    unique key stub(stub)

);

select * from t_test;

replcae into  t_test (stub) values ('b') ;

select LAST_INSERT_ID();

這種方式生成的 id 很顯然在高併發的場景 下仍然是不太合適作爲分佈式id 的,一秒內mysql 的性能達不到 10萬。

會存在以下缺點:

1.系統水平擴展比較困難,比如定義好了步長和機器臺數之後,如果要添加機器該怎麼做? 假設現在只有一臺機器,發號是1,2,3,4,5(步長是1),這個時候需要擴容機器一臺,可以這樣做: 把第二臺機器的初始值設置的比第一臺機器超過很多,貌似還好,現在想象一下,如果我們線上有100臺機器,這個時候要 擴容該怎麼做?簡直是噩夢,所以系統水平擴展複雜難以實現。

2. 數據庫壓力還是很大,每次獲取id 都得讀寫一次數據庫,非常影響性能,不符合分佈式id裏面的低延遲和高qps的規則

 

三 、基於 redis生成 全局唯一id 的策略

因爲redis是單線程的天生 保證原子性,可以使用原子操作incr 和 incrby 來實現。

在 redis 集羣的情況下, 和 mysql 一樣要設置 不同的 增長步長,同時key一定要設置有效期。可以使用redis 集羣來獲得更高的吞吐量,假如一個集羣中有5臺redis,可以初始化每臺redis 的值分別爲1,2,3,4,5,然後步長都是5.

 

以上 三個生成 全局唯一id 的方式均有優缺點。

四、 Twitter 的分佈式自增id 算法snowflake  雪花算法

 

 

 Snowflake 可以保證:

所有生成的id 按照時間趨勢遞增,整個分佈式系統內不會產生重複id ,因爲有 datacenterid 和 workerid來做區分。

項目地址 :https://github.com/twitter-archive/snowflake

hutool 工具包: https://www.hutool.cn/  , https://github.com/looly/hutool

spring boot 整合 雪花算法 

(1)pom 文件 添加依賴 

<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-captcha -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-captcha</artifactId>
    <version>5.3.3</version>
</dependency>

(2)

@Component
@Slf4j
public class IdGeneratorSnowflake{
   private  long  workerId = 0 ;
   private  long  datacenterId = 1; 
   private  Snowflake snowflake = IdUtil.createSnowflake(workerId,datacenterId);

   @PostConstruct
   public void init(){
      try{
        workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
        log.info("當前機器的workerId:{}",workerId);
      }catch(Exception e){
        e.printStackTrace();
        log.warn("當前機器的workerId獲取失敗",e);
        workerId = NetUtil.getLocalhostStr().hashCode();
      } 
   }

   public  synchronized long  snowflake(){
        return snowflake.nextId();
   }

   public  synchronized long  snowflake(long workerId,long datacenterId){
        Snowflake snowflake = IdUtil.createSnowflake(workerId,datacenterId);
        return snowflake.nextId();
   }

   public static void main(String[] args){
      System.out.println(new IdGeneratorSnowflake().snowflake());
   }
}

如果想 徹底解決 機器時鐘回撥導致重複id 生成的問題,則可以參考 百度開源的分佈式id 生成器 UidGenerator 和 美團點評的分佈式id 生成系統 Leaf 。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章