一、Redis分布式鎖基礎實現
public class RedisDistributedLock {private JedisPool jedisPool;private String lockKey;private String clientId;private int expireTime = 30; // 默認30秒public boolean tryLock() {try (Jedis jedis = jedisPool.getResource()) {// NX表示不存在時設置,PX設置過期時間(毫秒)String result = jedis.set(lockKey, clientId, SetParams.setParams().nx().px(expireTime * 1000));return "OK".equals(result);}}public void unlock() {try (Jedis jedis = jedisPool.getResource()) {// 使用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, Collections.singletonList(lockKey), Collections.singletonList(clientId));}}
}
關鍵點:
- 使用SET NX PX命令保證原子性
- 單命令原子性:Redis服務器單線程順序執行命令
- 僅包含SET操作:僅完成鍵值設置和過期時間配置
- 無邏輯判斷:僅判斷鍵是否存在(NX特性)
- 客戶端唯一標識(clientId)防止誤刪
- Lua腳本保證解鎖操作的原子性
- 多命令原子性:組合GET、DEL等命令的復合操作
- 包含業務邏輯:實現"比較后刪除"的CAS(Compare And Set)操作
- 支持復雜流程:可包含條件判斷、循環等邏輯
缺陷:
如果鎖存在A redis節點,然后B是A的從庫,服務先獲取A節點的redis key鎖,如果A網絡波動的時候的時候,主從切換,B節點升級為主節點,這個時候另一個服務獲取B節點的相同的redis key,這種情況就發生腦裂了。
二、RedLock算法核心思想
RedLock算法由Redis作者提出,主要解決單點故障問題:
- 多節點部署:使用5個(奇數)獨立的Redis節點
- 順序獲取:客戶端依次向所有節點申請鎖
- 成功條件:獲得超過半數的鎖(3個)
- 耗時計算:總耗時應小于鎖的TTL時間
- 失敗釋放:失敗時需要釋放所有已獲得的鎖
三、Redisson看門狗機制原理
public class RedissonWatchdogExample {public static void main(String[] args) {Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379");RedissonClient redisson = Redisson.create(config);RLock lock = redisson.getLock("myLock");try {// 默認30秒過期,看門狗自動續期lock.lock();// 業務邏輯執行時間可能超過30秒Thread.sleep(40000); } finally {lock.unlock();}}
}
集群防止腦裂
Redisson 實現分布式鎖的核心機制和集群腦裂防護原理如下:
1. 基礎鎖實現原理:
-- Redis 原子操作腳本
if redis.call('exists', KEYS[1]) == 0 thenredis.call('hset', KEYS[1], ARGV[2], 1)redis.call('pexpire', KEYS[1], ARGV[1])return nil
end
2. 集群模式防護機制:
// RedissonMultiLock 集群鎖實現
List<RLock> locks = new ArrayList<>();
locks.add(redissonClient1.getLock("lock1"));
locks.add(redissonClient2.getLock("lock2"));
RLock multiLock = new RedissonMultiLock(locks.toArray(new RLock[0]));
關鍵實現要點:
1. 多節點提交機制:
- 需要 N/2+1 個節點成功獲取鎖才算有效
- 使用異步線程維持鎖心跳(Watchdog 機制)
// Watchdog 線程實現(偽代碼)
private void scheduleExpirationRenewal() {if (expirationRenewalMap.putIfAbsent(lockName, newTimeout) == null) {// 每 10 秒續期一次internalLockLeaseTime / 3 周期執行}
}
2. 腦裂防護策略:
- 同步延遲控制:主從同步超時時間 > 鎖過期時間
- 多數派原則:成功獲取鎖的節點數 > 集群半數節點
- 故障轉移阻斷:當節點失聯時自動啟動鎖失效倒計時
3. 異常處理機制:
// 鎖釋放時的集群同步
public void unlock() {for (RLock lock : locks) {if (lock.isHeldByCurrentThread()) {lock.unlockAsync();}}
}
集群模式下重要參數配置建議:
# redisson.yaml
clusterServersConfig:nodeAddresses:- "redis://127.0.0.1:7000"- "redis://127.0.0.1:7001"scanInterval: 1000retryAttempts: 3retryInterval: 500slaveConnectionMinimumIdleSize: 8failedSlaveReconnectionInterval: 30000
該實現通過以下方式保證腦裂場景下的數據一致性:
- 使用 Raft 式多數派提交協議
- 網絡分區時自動降級為只讀模式
- 主節點切換后新主節點會等待舊主節點鎖超時
- 客戶端自動檢測集群拓撲變化并重建連接
看門狗機制關鍵點: - 后臺線程:每10秒檢查鎖狀態
- 自動續期:當業務未完成時,將過期時間重置為30秒
- 客戶端存活判斷:只有客戶端保持活躍才會續期
- 默認配置:lockWatchdogTimeout=30秒
四、完整方案對比
方案 | 優點 | 缺點 |
---|---|---|
基礎Redis鎖 | 實現簡單 | 單點故障風險 |
RedLock算法 | 高可用性 | 實現復雜、性能損耗 |
Redisson實現 | 自動續期、可重入鎖、多種鎖類型 | 需要維護客戶端連接 |
實際建議:
- 單節點場景使用Redisson基礎鎖
- 高可用場景使用Redisson+Redis Cluster
- 極端可靠性需求使用RedLock算法
生產環境注意事項:
- 合理設置超時時間(業務平均耗時 * 2)
- 監控鎖等待時間和獲取次數
- 為不同業務使用不同的鎖前綴
- 做好鎖等待超時的異常處理
競品分析
分布式鎖實現方案對比及優劣勢分析
一、ZooKeeper 實現方案
// 使用Curator框架示例
public class ZkDistributedLock {private CuratorFramework client;private InterProcessMutex lock;public boolean tryLock(String lockPath) throws Exception {lock = new InterProcessMutex(client, lockPath);return lock.acquire(3, TimeUnit.SECONDS); // 3秒獲取超時}public void unlock() throws Exception {if (lock != null) {lock.release();}}
}
核心原理:
通過創建臨時順序節點實現,獲取鎖的客戶端會生成有序節點,只有序號最小的節點持有鎖
優勢:
- 自動釋放(會話失效時自動刪除節點)
- 公平鎖機制(順序節點)
- 強一致性保證
劣勢:
- 寫操作性能低于Redis
- 需要維護ZooKeeper集群
- 客戶端實現相對復雜
二、Etcd 實現方案
// 使用jetcd客戶端示例
public class EtcdDistributedLock {private Client client;private Lease lease;public boolean tryLock(String lockKey) throws Exception {lease = client.getLeaseClient().grant(30).get(); // 30秒租約Txn txn = client.getKVClient().txn();txn.If(new Cmp(lockKey, Cmp.Op.EQUAL, CmpTarget.version(0))).Then(Op.put(lockKey, "locked", PutOption.newBuilder().withLeaseId(lease.getID()).build())).Else(Op.get(lockKey));return txn.commit().get().isSucceeded();}
}
核心原理:
基于租約(Lease)機制,利用事務操作實現原子性鎖獲取
優勢:
- 強一致性(Raft協議)
- 自動續期機制
- 支持公平鎖/非公平鎖
劣勢:
- 運維復雜度較高
- 客戶端生態不如Redis完善
- 性能低于Redis
三、數據庫實現方案
// 基于MySQL的樂觀鎖實現
public class DbDistributedLock {@Transactionalpublic boolean tryLock(String lockName) {// 使用唯一索引約束int result = jdbcTemplate.update("INSERT INTO distributed_lock(lock_name,owner) VALUES (?,?) ON DUPLICATE KEY UPDATE owner=IF(expire_time < NOW(), VALUES(owner), owner)",lockName, UUID.randomUUID().toString());return result > 0;}
}
核心原理:
基于數據庫唯一約束或排他鎖(SELECT FOR UPDATE)
優勢:
- 無需額外中間件
- 實現簡單快速
劣勢:
- 性能差(高并發場景容易成為瓶頸)
- 無自動釋放機制
- 死鎖風險較高
方案對比總結表
方案 | 一致性 | 性能 | 自動釋放 | 實現復雜度 | 適用場景 |
---|---|---|---|---|---|
Redis | 最終一致 | 高 | 支持 | 簡單 | 高并發、允許短暫不一致 |
ZooKeeper | 強一致 | 中 | 支持 | 復雜 | 強一致性要求、公平鎖場景 |
Etcd | 強一致 | 中 | 支持 | 較復雜 | 強一致性且需要自動續期 |
數據庫 | 強一致 | 低 | 不支持 | 簡單 | 低頻訪問、無中間件環境的應急方案 |
選型建議:
- 追求性能 ? Redis(Redisson實現)
- 強一致性要求 ? ZooKeeper/Etcd
- 無中間件環境 ? 數據庫方案(需謹慎處理超時)
- 混合使用場景 ? 可組合使用(如Redis做主鎖,數據庫做備用鎖)