歡迎來到啾啾的博客🐱。
記錄學習點滴。分享工作思考和實用技巧,偶爾也分享一些雜談💬。
有很多很多不足的地方,歡迎評論交流,感謝您的閱讀和評論😄。
目錄
- 引言
- 1 分布式ID
- 2 問題
- 2.1 時鐘回撥
- 2.1.1 毫秒級時鐘回撥則進行等待
- 2.1.2 引入時鐘回撥位
- 2.1.3 基于外部
- 2.1.4 混合時間戳
- 2.2 序列號耗盡
- 2.2.1 等待時間戳
- 2.2.2 調整結構,增加序列號位數
- 2.2.3 多ID生成器
引言
現在典型的八股都是凝練了問題,背后有著對問題的認知和解決思路。
一直想寫一個八股系列,從問題到原理。從這篇開始吧。
面試題:分布式ID時鐘回撥怎么處理?序列號耗盡了怎么辦?
1 分布式ID
分布式ID均會有一些需求。
高性能:有序
可維護:包含時間
可用:唯一
結構經常是多段結構,以UUIDv7為例
[ 48位毫秒時間戳 ] [ 4位版本號'7' ] [ 76位隨機數 ]
基于此,經常面臨的問題如下。
2 問題
2.1 時鐘回撥
2.1.1 毫秒級時鐘回撥則進行等待
毫秒級時鐘回撥,且對延遲不敏感場景。讓ID生成器等待系統時鐘追趕上上一次時間。
long generateId() {long currentTimestamp = getCurrentTimeMillis();while (currentTimestamp < lastTimestamp) {// 檢測到時鐘回撥,等待Thread.sleep(1);currentTimestamp = getCurrentTimeMillis();}// 繼續生成IDif (currentTimestamp == lastTimestamp) {sequence = (sequence + 1) & 4095; // 序列號遞增if (sequence == 0) {// 序列號用盡,等待下一毫秒currentTimestamp = waitNextMillis(currentTimestamp);}} else {sequence = 0; // 新毫秒,重置序列號}lastTimestamp = currentTimestamp;return (currentTimestamp << 22) | (machineId << 12) | sequence;
}
2.1.2 引入時鐘回撥位
引入時鐘回撥位。在ID結構中預留幾位作為“時鐘回撥位”(rollback bits),當檢測到時鐘回撥時,遞增回撥位以區分ID,避免重復。
2.1.3 基于外部
即給予外部服務的時鐘,避免依賴本地時鐘,如使用Redis生成遞增ID。
- 在Redis中每天生成一個Key(如date:20250615),通過INCR操作生成遞增序列號。
- ID格式:日期 + 機器ID + Redis遞增序列號。
2.1.4 混合時間戳
結合物理時鐘和邏輯時鐘,生成混合時間戳,確保即使發生回撥,ID仍保持唯一性和遞增性。
使用Google的TrueTime或類似機制,維護一個時間區間([min, max]),確保時間戳在安全范圍內。
檢測到回撥時,使用邏輯計數器遞增,確保ID唯一。
2.2 序列號耗盡
在分布式ID生成中(如Snowflake算法),序列號耗盡是指在同一時間戳(通常是毫秒級)內,序列號部分達到了最大值(例如,12位序列號的最大值是4095),無法繼續生成新的唯一ID。這是一個常見問題,尤其在高并發場景下,同一毫秒內可能需要生成大量ID。
2.2.1 等待時間戳
毫秒級的方案等待下一毫秒,納秒級的方案等待下一納秒。
long generateId() {long currentTimestamp = getCurrentTimeMillis();if (currentTimestamp < lastTimestamp) {// 處理時鐘回撥(參考上一回答)handleClockRollback();}if (currentTimestamp == lastTimestamp) {sequence = (sequence + 1) & 4095; // 遞增序列號if (sequence == 0) {// 序列號耗盡,等待下一毫秒currentTimestamp = waitNextMillis(currentTimestamp);}} else {sequence = 0; // 新時間戳,重置序列號}lastTimestamp = currentTimestamp;return (currentTimestamp << 22) | (machineId << 12) | sequence;
}long waitNextMillis(long lastTimestamp) {long current = getCurrentTimeMillis();while (current <= lastTimestamp) {current = getCurrentTimeMillis();}return current;
}
2.2.2 調整結構,增加序列號位數
通過調整ID結構,增加序列號的位數(例如從12位增加到14位),從而支持每毫秒生成更多ID。
2.2.3 多ID生成器
通過引入多個ID生成器實例或分片,分散ID生成壓力,降低單個實例的序列號耗盡概率。
比如雪花ID的Snowflake,可以為每個節點分配多個機器ID(例如,節點A使用機器ID 1-4,節點B使用5-8)。
當序列號耗盡時,切換到另一個實例生成ID。