这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战
前言
锁的机制,大家或多或少都接触过,在并发编程中,我们通过锁,来避免由于竞争而造成的数据不一致问题(类似于synchronized等同步机制);数据库也有乐观锁、悲观锁等,但是有一部分是jvm里面的锁,在分布式集群环境下的话,有部分锁就不能胜任了。本篇简单介绍一下平时常用的Redis分布式锁(本人愚见)
Redis分布式锁
首先介绍两个方法
-
setIfAbsent()方法
这个方法其实就是 get() + set()的结合体。
如果为空就set值,并返回1;
如果存在(不为空)不进行操作,并返回0
对标Redis里面的方法就是setnx() -
getAndSet()方法
该方法属并发方法,作用:如果key不存在则返回空并插入value;反之存在key则覆盖value并返回旧value。
这个方法对标Redis里面的方法就是Getset()
实现
对于这个方法,我一般是作为一个工具类去实现,直接调用上锁、解锁方法即可
逻辑思路
- key为业务上的标识符,value一般用当前时间+设定的超时时间(例如5分钟什么的的)
- 加锁:
- 判断是否存在该key,不存在即直接加锁并返回;
- 存在则获取已存在的key值得value,判断非空+是否超时(当前时间>设定的超时时间(value=当前时间+超时时间))
- 未超时则锁未失效,退出方法;若超时则调用getAndSet()方法,覆盖value并获取旧值,并与刚才获取的value比对,若一致则说明上一把锁失效了,覆盖并上新锁(防止死锁)。
@Component
@Slf4j
public class RedisLockUtil {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 加锁
* @param key
* @param value 当前时间+超时时间
* @return
*/
public boolean lock(String key, String value) {
if(redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
//currentValue=A 这两个线程的value都是B 其中一个线程拿到锁
String currentValue = redisTemplate.opsForValue().get(key);
//如果锁过期
if (!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()) {
//获取上一个锁的时间
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
return false;
}
/**
* 解锁
* @param key
* @param value
*/
public void unlock(String key, String value) {
try {
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
redisTemplate.opsForValue().getOperations().delete(key);
}
}catch (Exception e) {
log.error("【redis分布式锁】解锁异常, {}", e);
}
}
}
总结
简单实现了Redis的分布式锁。为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
- 互斥性。在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
- 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。