Redis分布式鎖中8個常見面試題小結
?? 1. 為什么需要分布式鎖?
場景:雙11秒殺,10000人搶100個商品
- 單機鎖不行:秒殺系統(tǒng)有10臺服務器,每臺都有自己的內(nèi)存,鎖不住其他服務器
- 需要共享的鎖:所有服務器都能訪問的鎖 → Redis分布式鎖
?? 2. 最簡單的分布式鎖怎么寫?(錯誤示范)
// ? 新手最容易寫的錯誤代碼
public boolean lock(String key) {
String result = jedis.setnx(key, "1"); // 嘗試加鎖
return result == 1; // 1表示加鎖成功
}
public void unlock(String key) {
jedis.del(key); // 刪除鎖
}
?? 問題在哪里?
- 死鎖風險:如果程序崩潰,鎖永遠不釋放
- 誤刪別人鎖:A的鎖超時釋放,B獲得鎖,A醒來刪了B的鎖
?? 3. 怎么設置過期時間?(還是不對)
// ? 這個也有問題
public boolean lock(String key, int seconds) {
// 兩步操作:1.加鎖 2.設置過期時間
Long result = jedis.setnx(key, "1");
if (result == 1) {
jedis.expire(key, seconds); // 設置過期
return true;
}
return false;
}
?? 問題:兩步不是原子的!
- 如果
setnx成功,但expire前程序崩潰 → 鎖變永久的!
?? 4. 正確寫法是怎樣的?
// ? 正確的加鎖(一步完成)
public boolean lock(String key, String value, int seconds) {
// 一條命令完成:加鎖+設置過期時間
String result = jedis.set(key, value, "NX", "EX", seconds);
return "OK".equals(result);
}
// ? 安全的解鎖
public void unlock(String key, String value) {
// 用Lua腳本保證原子操作:檢查值再刪除
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
jedis.eval(script, 1, key, value);
}
? 關鍵點:
- 一條命令:
SET key value NX EX seconds - value用唯一標識:UUID或線程ID
- 原子刪除:用Lua腳本檢查再刪除
?? 5. 鎖的value為什么不能用"1"?
// ? 錯誤:大家value都一樣
jedis.set("lock", "1", "NX", "EX", 10);
// ? 正確:每人一個唯一標識
String myId = UUID.randomUUID().toString();
jedis.set("lock", myId, "NX", "EX", 10);
場景演示:
線程A:獲得鎖,value="A123",超時10秒
線程A:執(zhí)行了15秒(鎖在第10秒已過期)
線程B:獲得鎖,value="B456"
線程A:終于執(zhí)行完,要刪除鎖 → 刪了線程B的鎖!?
?? 6. 業(yè)務沒執(zhí)行完,鎖過期了怎么辦?
方案1:設置合理的過期時間
// 評估業(yè)務時間,設置更長過期
jedis.set("lock", uuid, "NX", "EX", 30); // 設置30秒
方案2:自動續(xù)期(看門狗)
// 啟動一個線程,定期續(xù)期
new Thread(() -> {
while (業(yè)務沒執(zhí)行完) {
Thread.sleep(8000); // 8秒續(xù)一次
// 如果是自己的鎖,就延長過期時間
jedis.expire("lock", 10);
}
}).start();
?? 7. Redis主從切換會丟鎖嗎?
會!這是Redis分布式鎖的最大問題
場景:
1. 線程A在主節(jié)點獲得鎖
2. 主節(jié)點宕機(鎖數(shù)據(jù)還沒同步到從節(jié)點)
3. 從節(jié)點變成新主節(jié)點
4. 線程B在新主節(jié)點獲得"相同"的鎖
結果:A和B同時持有了鎖!
解決方案:
- 用RedLock算法:在多個Redis實例上加鎖
- 用ZooKeeper:更適合分布式鎖
- 接受風險:業(yè)務上做冪等處理
?? 8. 實際開發(fā)用什么?(不要造輪子?。?/h2>
// 使用Redisson框架(最省心)
RedissonClient redisson = Redisson.create();
RLock lock = redisson.getLock("myLock");
try {
lock.lock(); // 加鎖(自動續(xù)期)
// 執(zhí)行業(yè)務...
} finally {
lock.unlock(); // 解鎖
}
// 使用Redisson框架(最省心)
RedissonClient redisson = Redisson.create();
RLock lock = redisson.getLock("myLock");
try {
lock.lock(); // 加鎖(自動續(xù)期)
// 執(zhí)行業(yè)務...
} finally {
lock.unlock(); // 解鎖
}
?? 面試對比表
| 方案 | 優(yōu)點 | 缺點 | 適用場景 |
|---|---|---|---|
| Redis單節(jié)點 | 簡單、快 | 主從切換丟鎖 | 測試環(huán)境、不重要的鎖 |
| RedLock | 相對可靠 | 實現(xiàn)復雜、性能差 | 重要的業(yè)務鎖 |
| Redisson | 功能全、自動續(xù)期 | 依賴框架 | 推薦的生產(chǎn)方案 |
| ZooKeeper | 最可靠 | 性能較差 | 強一致性的場景 |
? 面試常見問題
Q1: "說一下Redis分布式鎖的實現(xiàn)原理"
答:"用SET命令的NX和EX參數(shù),NX保證只有一個能設置成功,EX設置過期時間防止死鎖。刪除時用Lua腳本原子操作,避免刪別人鎖。"
Q2: "Redis鎖和ZooKeeper鎖的區(qū)別?"
答:
- Redis:AP系統(tǒng),性能好,但可能丟鎖
- ZooKeeper:CP系統(tǒng),可靠但性能差
- 選擇:要高性能用Redis,要可靠性用ZooKeeper
Q3: "怎么實現(xiàn)可重入鎖?"
答:"在value里記錄線程ID和重入次數(shù)。加鎖時如果是同一線程,計數(shù)+1;解鎖時計數(shù)-1,計數(shù)為0才真正刪除鎖。"
?? 記住三句話
- 加鎖要原子:
SET key uuid NX EX seconds - 解鎖要安全:用Lua腳本先檢查再刪除
- 生產(chǎn)別造輪子:直接用Redisson
?? 快速檢查清單
你的分布式鎖:
- 設置過期時間了嗎?
- value是唯一的嗎?
- 刪除前檢查value了嗎?
- 考慮鎖續(xù)期了嗎?
- 知道主從切換會丟鎖嗎?
滿足這5條,面試官就難不倒你了!
到此這篇關于Redis分布式鎖中8個常見面試題小結的文章就介紹到這了,更多相關Redis分布式鎖面試題內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
內(nèi)存型數(shù)據(jù)庫Redis持久化小結
redis是一個支持持久化的內(nèi)存數(shù)據(jù)庫,也就是說redis需要經(jīng)常將內(nèi)存中的數(shù)據(jù)同步到磁盤來保證持久化.redis支持四種持久化方式,一是 Snapshotting(快照)也是默認方式,二是Append-only file(縮寫aof)的方式,三是虛擬內(nèi)存方式,四是diskstore方式.今天我們總結下前2種。2017-09-09
redisTemplate.opsForValue().get()獲取值失敗的解決方案
文章討論了在使用RedisTemplate時遇到get()方法返回null的問題,并分析了原因,作者建議使用@Autowired注解進行依賴注入,特別是推薦通過構造函數(shù)注入,以避免類型無法分辨的問題,文章最后總結了這些經(jīng)驗,并鼓勵讀者參考和使用2026-03-03
在ssm項目中使用redis緩存查詢數(shù)據(jù)的方法
本文主要簡單的使用Java代碼進行redis緩存,即在查詢的時候先在service層從redis緩存中獲取數(shù)據(jù)。如果大家對在ssm項目中使用redis緩存查詢數(shù)據(jù)的相關知識感興趣的朋友跟隨腳本之家小編一起看看吧2018-03-03

