在Java中,关于锁我想大家都很熟悉。在并发编程中,我们通过锁,来避免由于竞争而造成的数据不一致问题。通常,我们以synchronized 、Lock来使用它。
但是Java中的锁,只能保证在同一个JVM进程内中执行。如果在分布式集群环境下呢?
一、分布式锁
分布式锁,是一种思想,它的实现方式有很多。比如,我们将沙滩当做分布式锁的组件,那么它看起来应该是这样的:
加锁
在沙滩上踩一脚,留下自己的脚印,就对应了加锁操作。其他进程或者线程,看到沙滩上已经有脚印,证明锁已被别人持有,则等待。
解锁
把脚印从沙滩上抹去,就是解锁的过程。
锁超时
为了避免死锁,我们可以设置一阵风,在单位时间后刮起,将脚印自动抹去。
分布式锁的实现有很多,比如基于数据库、memcached、Redis、系统文件、zookeeper等。它们的核心的理念跟上面的过程大致相同。
二、redis
我们先来看如何通过单节点Redis实现一个简单的分布式锁。
背景:在当应用服务启动的时候,需要进行数据初始化,如果部署至多容器节点的时候,节点在启动的时候会概率触发多次初始化逻辑;所以,需要通过锁来限制初始化操作之后执行一次。
RedisLock工具类
@Component
public class RedisLockUtil {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static long DEFALT_TIMEOUT = 1L;
/**
* @description 上锁
* @param key 锁标识
* @param value 线程标识
* @param timeout 超时时间,秒
* @updateTime 2021/9/14
*/
public boolean lock(String key, String value, long timeout) {
return stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
}
/**
* @description 上锁(排队等待)
* @param key 锁标识
* @param value 线程标识
* @updateTime 2021/9/14
*/
public boolean lock(String key, String value) {
while (true) {
// 执行set命令
Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent(key, value, DEFALT_TIMEOUT, TimeUnit.MINUTES);
// 是否成功获取锁
if (absent) {
return true;
}
}
}
/**
* @description 解锁
* @param key 锁标识
* @param value 线程标识
* @updateTime 2021/9/14
*/
public boolean unlock(String key, String value) {
String currentValue = stringRedisTemplate.opsForValue().get(key);
if(StrUtil.isNotBlank(currentValue) && currentValue.equals(value) ){
return stringRedisTemplate.opsForValue().getOperations().delete(key);
}
return false;
}
}
代码示例
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
//上锁
long time = System.currentTimeMillis();
boolean result = redisLock.lock(INIT_KEY, String.valueOf(time), TIMEOUT);
if (log.isInfoEnabled()) {
log.info("获得锁的结果:" + result + ";获得锁的时间戳:" + String.valueOf(time));
}
if(!result){
throw new IllegalArgumentException("已存在初始化!!!");
}
try {
...
初始化逻辑代码
...
} catch (Exception e) {
log.error("初始化失败!", e);
} finally {
//释放锁
redisLock.unlock(INIT_KEY, String.valueOf(time));
if (log.isInfoEnabled()) {
log.info("释放锁的时间戳" + String.valueOf(time));
}
}
}