【初】Redis分布式锁进阶之事物分布式锁
大约 3 分钟
视频地址 https://www.bilibili.com/video/BV1et4y1P73Q
redis分布式锁,大家肯定不陌生,也应该都用过,主要作用是防止并发产生的数据问题,下面是一段redis锁的伪代码
public void fun() {
try {
String key = "";
// 获取锁
if (redisUtils.get(key) == null) {
// 上锁
redisUtils.set(key,"",expireTime);
// 业务逻辑处理
}else {
// 未获得锁
throw new RuntimeException("未获得锁");
}
}finally {
// 释放锁
redisUtils.remove(key);
}
}
在实际使用分布式锁还存在一个问题,我们很多方法是有事物的,如果在上述方法加了一个事物,事物是要在方法执行完之后才提交的,也就是会先释放锁,后提交事务,如果在释放锁和提交事务之间有一个线程访问了,这时候它获取到了锁,但是去查询数据库的时候还无法读取前一个事物没有提交的数据,这时候就会造成数据错乱。
我们可以使用事物钩子,可以确保在事物提交之后再去执行释放锁
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
redisUtils.remove(key);
}
});
改造后面的伪代码如下
@Transactional(rollbackFor = Exception.class)
public void fun() {
try {
String key = "";
// 获取锁
if (redisUtils.get(key) == null) {
// 上锁
redisUtils.set(key,"",expireTime);
// 业务逻辑处理
}else {
// 未获得锁
throw new RuntimeException("未获得锁");
}
}finally {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// 释放锁
redisUtils.remove(key);
}
});
}
}
上面的代码看似天衣无缝,但在实际使用场景中又出问题了,在执行业务代码的时候出现了各种异常(这是很正常的事情),如果出现了异常,事物是会回滚的,这时候就不会走下面的释放锁逻辑,只有等锁过期,这将是致命的。
做一点修改就可以完美的解决这个问题
@Transactional(rollbackFor = Exception.class)
public void fun() {
try {
String key = "";
// 获取锁
if (redisUtils.get(key) == null) {
// 上锁
redisUtils.set(key,"",expireTime);
// 业务逻辑处理
}else {
// 未获得锁
throw new RuntimeException("未获得锁");
}
}catch (Exception e){
// 释放锁,抛出异常
redisUtils.remove(key);
throw e;
}finally {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// 释放锁
redisUtils.remove(key);
}
});
}
}
PS:最开始出现这个问题的时候,我总在想是不是事物的传播行为导致事物失效了,或者是开启了新的事物,尝试了很多次,最后发现是事物回滚了。
事物钩子里全部的方法
// 挂起时出发
@Override
public void suspend() {
}
// 挂起事物抛出异常的时候会触发
@Override
public void resume() {
}
@Override
public void flush() {
}
// 在事物提交之前触发
@Override
public void beforeCommit(boolean readOnly) {
}
// 在事物完成之前触发(回滚/提交)
@Override
public void beforeCompletion() {
}
// 在事物提交之后触发
@Override
public void afterCommit() {
}
// 在事物完成之后触发 (回滚/提交 可以用status来判断)
@Override
public void afterCompletion(int status) {
}
上面我们的问题,可以有更优雅的解决办法,把 afterCommit 方法换成 afterCompletion 即可。