分散式鎖最終解決方案是RedLock嗎?為什麼?
RedLock 是Redis 分散式鎖定的實作方案,由Redis 的作者Salvatore Sanfilippo 提出。
RedLock 演算法旨在解決單一Redis 實例作為分散式鎖定時可能出現的單點故障問題,透過在多個獨立運行的Redis 實例上同時取得鎖的方式來提高鎖服務的可用性和安全性。
1.實現思路
RedLock 是對叢集的每個節點進行加鎖,如果大多數節點(N/2+1)加鎖成功,則才會認為加鎖成功。這樣即使叢集中有某個節點掛掉了,因為大部分叢集節點都加鎖成功了,所以分散式鎖還是可以繼續使用的。
2、實現程式碼
在Java 開發中,可以使用Redisson 框架很方便的實作RedLock,具體操作程式碼如下:
import org.redisson.Redisson;
import org.redisson.api.RedisClient;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.redisson.RedissonRedLock;
public class RedLockDemo {
public static void main(String[] args) {
// 创建 Redisson 客户端配置
Config config = new Config();
config.useClusterServers()
.addNodeAddress("redis://127.0.0.1:6379",
"redis://127.0.0.1:6380",
"redis://127.0.0.1:6381"); // 假设有三个 Redis 节点
// 创建 Redisson 客户端实例
RedissonClient redissonClient = Redisson.create(config);
// 创建 RedLock 对象
RedissonRedLock redLock = redissonClient.getRedLock("resource");
try {
// 尝试获取分布式锁,最多尝试 5 秒获取锁,并且锁的有效期为 5000 毫秒
boolean lockAcquired = redLock.tryLock(5, 5000, TimeUnit.MILLISECONDS);
if (lockAcquired) {
// 加锁成功,执行业务代码...
} else {
System.out.println("Failed to acquire the lock!");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Interrupted while acquiring the lock");
} finally {
// 无论是否成功获取到锁,在业务逻辑结束后都要释放锁
if (redLock.isLocked()) {
redLock.unlock();
}
// 关闭 Redisson 客户端连接
redissonClient.shutdown();
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
3、實現原理
Redisson 中的RedLock 是基於RedissonMultiLock(連鎖)實現的。
RedissonMultiLock 是Redisson 提供的分散式鎖定類型,它可以同時操作多個鎖,以達到對多個鎖進行統一管理的目的。聯鎖的操作是原子性的,即要么全部鎖住,要么全部解鎖。這樣可以保證多個鎖的一致性。
RedissonMultiLock 使用範例如下:
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.multi.MultiLock;
public class RedissonMultiLockDemo {
public static void main(String[] args) throws InterruptedException {
// 创建 Redisson 客户端
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// 创建多个分布式锁实例
RLock lock1 = redisson.getLock("lock1");
RLock lock2 = redisson.getLock("lock2");
RLock lock3 = redisson.getLock("lock3");
// 创建 RedissonMultiLock 对象
MultiLock multiLock = new MultiLock(lock1, lock2, lock3);
// 加锁
multiLock.lock();
try {
// 执行任务
System.out.println("Lock acquired. Task started.");
Thread.sleep(3000);
System.out.println("Task finished. Releasing the lock.");
} finally {
// 释放锁
multiLock.unlock();
}
// 关闭客户端连接
redisson.shutdown();
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
在範例中,我們首先建立了一個Redisson 用戶端並連接到Redis 伺服器。然後,我們使用redisson.getLock 方法建立了多個分散式鎖定實例。接下來,我們透過傳入這些鎖定實例來建立了RedissonMultiLock 物件。
說回正題,RedissonRedLock 是基於RedissonMultiLock 實現的這一點,可以從繼承關係看出。
RedissonRedLock 繼承自RedissonMultiLock,核心實作原始碼如下:
public class RedissonRedLock extends RedissonMultiLock {
public RedissonRedLock(RLock... locks) {
super(locks);
}
/**
* 锁可以失败的次数,锁的数量-锁成功客户端最小的数量
*/
@Override
protected int failedLocksLimit() {
return locks.size() - minLocksAmount(locks);
}
/**
* 锁的数量 / 2 + 1,例如有3个客户端加锁,那么最少需要2个客户端加锁成功
*/
protected int minLocksAmount(final List<RLock> locks) {
return locks.size()/2 + 1;
}
/**
* 计算多个客户端一起加锁的超时时间,每个客户端的等待时间
*/
@Override
protected long calcLockWaitTime(long remainTime) {
return Math.max(remainTime / locks.size(), 1);
}
@Override
public void unlock() {
unlockInner(locks);
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
從上述原始碼可以看出,Redisson 中的RedLock 是基於RedissonMultiLock(連鎖)實現的,當RedLock 是對叢集的每個節點進行加鎖,如果大多數節點,也就是N/2+1 個節點加鎖成功,則認為RedLock 加鎖成功。
4.存在問題
RedLock 主要存在以下兩個問題:
- 效能問題:RedLock 要等待大多數節點返回之後,才能加鎖成功,而這個過程中可能會因為網路問題,或是節點逾時的問題,影響加鎖的效能。
- 並發安全性問題:當客戶端加鎖時,如果遇到GC 可能會導致加鎖失效,但GC 後誤認為加鎖成功的安全事故,例如以下流程:
- 客戶端A 請求3 個節點進行加鎖。
- 在節點回復處理之前,客戶端A 進入GC 階段(存在STW,全域停頓)。
- 之後因為加鎖時間的原因,鎖已經失效了。
- 客戶端B 請求加鎖(和客戶端A 是同一把鎖),加鎖成功。
- 客戶端A GC 完成,繼續處理前面節點的訊息,誤以為加鎖成功。
- 此時客戶端B 和客戶端A 同時加鎖成功,出現並發安全性問題。
5.已廢棄RedLock
因為RedLock 存在的問題爭議較大,且沒有完美的解決方案,所以Redisson 中已經廢棄了RedLock,這一點在Redisson 官方文件中能找到,如下圖所示:
6.廢棄RedLock 後的解決方案
雖然Redisson 中已經廢棄了RedLock,但是你可以直接使用Redisson 中的普通的加鎖即可,因為它的普通鎖會基於wait 機制,等待鎖將資訊同步到從節點,從而保證資料一致性的,雖然無法完全避免資料一致性問題,但也能最大限度的保證資料的一致性。
課後思考
既然普通的分散式鎖存在單點問題?而RedLock 又不是最完美的解決方案,那麼在分散式鎖定領域,誰才是最終的解決方案呢?請在評論區留下您的解決方案,以及對應的原因?