Java實現Redis分布式鎖的三種方案匯總
序言
setnx、Redisson、RedLock 都可以實現分布式鎖,從易到難得排序為:setnx < Redisson < RedLock。一般情況下,直接使用 Redisson 就可以啦,有很多邏輯框架的作者都已經考慮到了。
方案一:setnx
1.1、簡單實現
下面的鎖實現可以用在測試或者簡單場景,但是它存在以下問題,使其不適合用在正式環(huán)境。
- 鎖可能被誤刪: 在解鎖操作中,如果一個線程的鎖已經因為超時而被自動釋放,然后又被其他線程獲取到,這時原線程再來解鎖就會誤刪其他線程的鎖。
- 臨界區(qū)代碼不安全: 線程 A 還沒有執(zhí)行完臨界區(qū)代碼,鎖就過期釋放掉了。線程 B 此時又能獲取到鎖,進入臨界區(qū)代碼,導致了臨界區(qū)代碼非串行執(zhí)行,帶來了線程不安全的問題。
public class RedisLock {
?
@Autowired
private StringRedisTemplate redisTemplate;
?
/**
* 加鎖
*/
private boolean tryLock(String key) {
Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
return BooleanUtil.isTrue(flag);
}
?
/**
* 解鎖
*/
private void unlock(String key) {
redisTemplate.delete(key);
}
}
1.2、使用 lua 腳本加鎖、解鎖
lua 腳本是原子的,不管寫多少 lua 腳本代碼,redis 都是通過一條命令去執(zhí)行的。
下述代碼使用了 lua 腳本進行加鎖/解鎖,保證了加鎖和解鎖的時候都是原子性的,是一種相對較好的 Redis 分布式鎖的實現方式。
它支持獲得鎖的線程才能釋放鎖,如果線程 1 因為鎖過期而丟掉了鎖,然后線程 2 拿到了鎖。此時線程 1 的業(yè)務代碼執(zhí)行完以后,也無法釋放掉線程 2 的鎖,解決了誤刪除的問題。
public class RedisLock {
?
private final StringRedisTemplate redisTemplate;
?
public RedisDistributedLock(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
?
public boolean tryLock(String lockKey, String lockValue, long expireTimeInSeconds) {
try {
//加鎖成功返回 true,加鎖失敗返回 fasle。效果等同于 redisTemplate.opsForValue().setIfAbsent
String luaScript = "if redis.call('set', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2]) then return 1 else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), lockValue, String.valueOf(expireTimeInSeconds));
?
return result != null && result == 1;
} catch (Exception e) {
// Handle exceptions
return false;
}
}
?
public void unlock(String lockKey, String lockValue) {
try {
//拿到鎖的線程才可以釋放鎖,lockValue 可以設置為 uuid。
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
redisTemplate.execute(redisScript, Collections.singletonList(lockKey), lockValue);
} catch (Exception e) {
// Handle exceptions
}
}
}
方案二:Redisson
Redisson 是一個基于 Java 的客服端,通過 Redisson 我們可以快速安全的實現分布式鎖。Redisson 框架具有可重入鎖的支持、分布式鎖的實現、鎖的自動續(xù)期、紅鎖支持等多種特點,給我們開發(fā)過程中帶來了極大的便利。
@Component
public class RedisLock {
?
@Resource
private RedissonClient redissonClient;
?
/**
* lock(), 拿不到lock就不罷休,不然線程就一直block
*/
public RLock lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
return lock;
}
?
/**
* leaseTime為加鎖時間,單位為秒
*/
public RLock lock(String lockKey, long leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
return null;
}
?
/**
* timeout為加鎖時間,時間單位由unit確定
*/
public RLock lock(String lockKey, TimeUnit unit, long timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
return lock;
}
?
/**
* @param lockKey 鎖 key
* @param unit 單位
* @param waitTime 等待時間
* @param leaseTime 鎖有效時間
* @return 加鎖成功? true:成功 false: 失敗
*/
public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime) {
?
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
}
?
/**
* unlock
*/
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
?
/**
* unlock
* @param lock 鎖
*/
public void unlock(RLock lock) {
lock.unlock();
}
}
方案三:RedLock
RedLock 又叫做紅鎖,是 Redis 官方提出的一種分布式鎖的算法,紅鎖的提出是為了解決集群部署中 Redis 鎖相關的問題。
比如當線程 A 請求鎖成功了,這時候從節(jié)點還沒有復制鎖。此時主節(jié)點掛掉了,從節(jié)點成為了主節(jié)點。線程 B 請求加鎖,在原來的從節(jié)點(現在是主節(jié)點)上加鎖成功。這時候就會出現線程安全問題。
下圖是紅鎖的簡易思路。紅鎖認為 (N / 2) + 1 個節(jié)點加鎖成功后,那么就認為獲取到了鎖,通過這種算法減少線程安全問題。簡單流程為:
- 順序向五個節(jié)點請求加鎖
- 根據一定的超時時間判斷是否跳過該節(jié)點
- (N / 2) + 1 個節(jié)點加鎖成功并且小于鎖的有效期
- 認定加鎖成功

@Service
public class MyService {
?
private final RedissonClient redissonClient;
?
@Autowired
public MyService(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
?
public void doSomething() {
RLock lock1 = redissonClient.getLock("lock1");
RLock lock2 = redissonClient.getLock("lock2");
RLock lock3 = redissonClient.getLock("lock3");
?
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
redLock.lock();
try {
// 業(yè)務邏輯
} finally {
redLock.unlock();
}
}
}
總結
自己玩或者測試的時候使用方案一的簡單實現。
單機版 Redis 使用方案二。
Redis 集群使用方案三。
以上就是Java實現Redis分布式鎖的三種方案匯總的詳細內容,更多關于Redis分布式鎖的資料請關注腳本之家其它相關文章!
相關文章
使用Prometheus+Grafana的方法監(jiān)控Springboot應用教程詳解
這篇文章主要介紹了用Prometheus+Grafana的方法監(jiān)控Springboot應用,本文通過實例代碼詳解給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03
SpringBoot常用計量與bean屬性校驗和進制數據轉換規(guī)則全面分析
這篇文章主要介紹了SpringBoot常用計量、bean屬性校驗與進制數據轉換規(guī)則,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2022-10-10

