通過在 生產生活中,分佈式的系統以及大數據量的存儲和讀取都離不開 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 。