分布式ID與冪等性面試題整理
文章目錄
- 分布式ID與冪等性面試題整理
- 一、分布式ID
- 1. 為什么需要分布式ID?
- 2. 分布式ID的核心要求
- 3. 常見分布式ID方案
- (1) UUID
- (2) 數據庫自增
- (3) Redis自增
- (4) 雪花算法(Snowflake)
- (5) 美團Leaf/百度UidGenerator
- 4. 雪花算法詳解
- 二、冪等性
- 1. 什么是冪等性?
- 2. 為什么需要冪等性?
- 3. 實現冪等性的常見方案
- (1) 唯一索引
- (2) 樂觀鎖
- (3) 狀態機
- (4) Token機制
- (5) 去重表
- (6) 全局ID
- 4. 不同場景的冪等實踐
- HTTP接口冪等
- 消息隊列冪等
- 定時任務冪等
- 5. 冪等性與并發控制
- 三、綜合實戰
- 1. 設計一個分布式ID生成服務
- 2. 支付系統的冪等設計
- 3. 分布式事務與冪等
一、分布式ID
1. 為什么需要分布式ID?
問題:在分布式系統中,為什么不能直接使用數據庫自增ID?
答案:
- 單點故障:自增ID依賴單數據庫,數據庫掛了整個系統就癱瘓
- 性能瓶頸:所有ID生成請求都打到同一個數據庫,壓力大
- 擴展困難:分庫分表時,自增ID會導致重復或需要復雜協調
- 安全問題:連續的數字ID容易暴露業務量,可能被爬取數據
2. 分布式ID的核心要求
問題:一個好的分布式ID生成方案需要滿足哪些要求?
答案:
- 全局唯一:整個系統內絕對不能重復
- 趨勢遞增:最好是有序的,方便數據庫索引
- 高可用:ID生成服務不能成為單點故障
- 高性能:每秒至少能生成幾萬ID
- 安全:不能暴露業務信息(如訂單量)
3. 常見分布式ID方案
問題:常見的分布式ID生成方案有哪些?各自原理是什么?
答案:
(1) UUID
- 原理:隨機生成128位數字,格式如
550e8400-e29b-41d4-a716-446655440000
- 優點:簡單,本地生成無網絡開銷
- 缺點:無序導致索引性能差,字符串存儲空間大
(2) 數據庫自增
- 原理:單獨數據庫表,通過
REPLACE INTO
或事務獲取ID - 優點:實現簡單,ID有序
- 缺點:數據庫單點風險,性能有限
(3) Redis自增
- 原理:利用Redis的
INCR
命令生成ID - 優點:性能比數據庫好
- 缺點:需維護Redis集群,持久化問題可能導致ID重復
(4) 雪花算法(Snowflake)
- 原理:64位ID = 1位符號位(0) + 41位時間戳 + 10位機器ID + 12位序列號
- 優點:本地生成性能高,ID有序
- 缺點:依賴機器時鐘,時鐘回撥會導致ID重復
(5) 美團Leaf/百度UidGenerator
- 原理:改進版雪花算法,解決時鐘回撥問題,引入ZK協調
- 優點:解決了原生雪花算法的問題
- 缺點:系統復雜度高
4. 雪花算法詳解
問題:詳細解釋雪花算法的實現原理?
答案:
0 | 0001100 10100010 10111110 10001001 01011100 | 00 | 00001 | 000000000000
- 第1位:符號位,始終為0
- 中間41位:毫秒級時間戳,可用69年
- 接著10位:5位數據中心ID + 5位機器ID(最多1024個節點)
- 最后12位:序列號,每毫秒可生成4096個ID
時鐘回撥問題處理:
- 輕微回撥:等待時間追上
- 嚴重回撥:報警人工介入
- 美團Leaf方案:使用ZK記錄最大時間戳
二、冪等性
1. 什么是冪等性?
問題:用通俗語言解釋什么是冪等性?
答案:
- 通俗理解:同樣的操作執行一次和執行N次,效果一樣
- 舉例:
- 支付:同一筆訂單只扣一次錢
- 短信:同一條通知只發一次
- 狀態更新:最終狀態一致
2. 為什么需要冪等性?
問題:分布式系統中為什么特別關注冪等性?
答案:
- 網絡問題:請求超時可能導致客戶端重試
- 微服務調用:服務調用失敗會觸發重試機制
- 消息隊列:消息可能被重復消費
- 用戶行為:用戶可能多次點擊提交按鈕
3. 實現冪等性的常見方案
問題:有哪些實現冪等性的方案?各自適用場景?
答案:
(1) 唯一索引
- 原理:數據庫唯一索引防止重復數據
- 場景:創建訂單等插入操作
- 實現:訂單ID、業務編號等加唯一索引
(2) 樂觀鎖
-
原理:通過版本號控制更新
-
場景:更新操作如賬戶余額變更
-
實現:
UPDATE account SET balance=balance-100, version=version+1 WHERE id=123 AND version=5
(3) 狀態機
-
原理:業務狀態流轉控制
-
場景:訂單狀態等有明確流轉的業務
-
實現:
UPDATE order SET status='paid' WHERE id=456 AND status='unpaid'
(4) Token機制
- 原理:客戶端先獲取令牌,服務端校驗后刪除
- 場景:防止表單重復提交
- 實現:
- 服務端生成token存入Redis
- 提交時攜帶token
- 校驗后刪除token
(5) 去重表
-
原理:記錄已處理請求ID
-
場景:消息隊列消費等
-
實現:
INSERT INTO request_log(request_id, biz_type) VALUES('req123', 'order') -- 先查是否存在再處理
(6) 全局ID
- 原理:利用分布式ID的唯一性
- 場景:所有需要冪等的場景
- 實現:結合雪花算法等生成唯一業務ID
4. 不同場景的冪等實踐
問題:針對以下場景如何保證冪等性?
- HTTP接口
- 消息隊列消費
- 定時任務
答案:
HTTP接口冪等
- GET:天然冪等
- POST:
- 前端:提交按鈕禁用+loading
- 后端:Token機制+唯一索引
- PUT/DELETE:天然冪等(需正確實現)
消息隊列冪等
- RabbitMQ:
- 消息唯一ID+去重表
- 手動ack確保處理完成
- Kafka:
- 利用offset控制
- 消費者組+分區保證順序
定時任務冪等
- 加鎖:分布式鎖(Redis/ZK)
- 狀態檢查:記錄上次執行結果
- 時間窗口:允許短時間重復但結果一致
5. 冪等性與并發控制
問題:冪等性和并發控制有什么區別?
答案:
- 冪等性:關注多次操作的結果一致性
- 并發控制:關注同時操作的順序和正確性
- 聯系:
- 都需要唯一標識
- 都可能導致數據不一致
- 區別:
- 冪等解決重復問題
- 并發控制解決競爭問題
三、綜合實戰
1. 設計一個分布式ID生成服務
問題:如何設計一個類似美團Leaf的分布式ID服務?
答案:
架構設計:
- 服務層:無狀態服務,可水平擴展
- 存儲層:
- ZK:協調worker節點分配
- Redis:緩存號段,提高性能
- ID生成:
- 號段模式:每次獲取一批ID(如1-1000)
- 雙Buffer:一個用完前預加載下一個
核心流程:
- 啟動時向ZK注冊獲取workerID
- 從DB獲取號段范圍(UPDATE max_id=max_id+step)
- 內存中分配ID,快用完時異步加載下一號段
- 定期持久化當前分配位置
2. 支付系統的冪等設計
問題:支付系統如何防止重復扣款?
答案:
全鏈路設計:
-
訂單創建:
- 訂單ID使用雪花算法
- 訂單表order_id唯一索引
-
支付請求:
- 前端:支付按鈕防重
- 生成支付流水號(支付系統唯一)
-
支付核心:
// 偽代碼 public Result pay(String orderId, BigDecimal amount) {// 1. 檢查訂單狀態Order order = orderDao.get(orderId);if (order.isPaid()) {return Result.success("已支付");}// 2. 樂觀鎖更新int updated = orderDao.updateStatus(orderId, "unpaid", "paying");if (updated == 0) {return Result.fail("并發操作");}// 3. 實際扣款boolean success = accountService.debit(order.getUserId(), amount);// 4. 最終狀態if (success) {orderDao.updateStatus(orderId, "paying", "paid");} else {orderDao.updateStatus(orderId, "paying", "failed");} }
-
對賬補救:定時核對訂單與支付記錄
3. 分布式事務與冪等
問題:分布式事務場景下如何保證冪等性?
答案:
結合方案:
- TCC模式:
- Try階段:生成事務ID,記錄預備狀態
- Confirm/Cancel:通過事務ID保證冪等
- 本地消息表:
- 業務與消息表在同一個事務
- 消息ID作為去重依據
- Saga模式:
- 每個步驟有補償操作
- 通過業務ID保證補償冪等
關鍵點:
- 事務ID全局唯一
- 操作前檢查狀態
- 補償操作也要冪等