前言
这段时间看到挺多人使用redis作为分布式锁来进行资源的控制,但是这种写法有挺多问题的,所以才特意写一篇文章让大家讨论一下。
锁的特性
- 安全性:当一个资源被占用后,其他线程不能占用
- 容错性:当一个资源被占用后,使用资源的线程或者项目蹦了,而这个资源会自动解锁
- 阻塞锁(可选):当前资源已被加锁,其他线程/进程来加锁是否阻塞等待,还是立即返回。
- 可重入性(可选):当前锁的持有者是否能再次进入。
- 公平性(可选):加锁顺序和请求加锁顺序是否一致,还是随机抢锁。
错误用法
@Override
public void afterPropertiesSet() throws Exception {
ValueOperations<String, Object> stringObjectValueOperations = redisTemplate.opsForValue();
// 错误用法1
if (Boolean.TRUE.equals(redisTemplate.hasKey("a"))) {
stringObjectValueOperations.set("a", "b");
try {
//执行业务逻辑
} finally {
redisTemplate.delete("a");
}
}
// 错误用法2
if (Boolean.TRUE.equals(stringObjectValueOperations.setIfAbsent("a", "b"))) {
try {
//执行业务逻辑
} finally {
redisTemplate.delete("a");
}
}
// 错误用法3
if (Boolean.TRUE.equals(stringObjectValueOperations.setIfAbsent("a", "b", 30L, TimeUnit.SECONDS))) {
try {
//执行业务逻辑
} finally {
redisTemplate.delete("a");
}
}
}
- 从第一个错误用法显而易见就是很有可能多个线程当时都没检测到有key,所有会有多个线程同时到setKey的这个方法,违背了安全性性质
- 从第二个错误用法可见,如果当执行业务逻辑的时候,当前节点宕机了,那么就会变成死锁,违背了容错性性质
- 从第三个错误用法可以解决30秒内的安全性和容错性,但是有一个地方就是如果执行逻辑超过了30s,那就会有新的线程进入,其实也违背了锁的安全性。
- 基于以上3点使用方法,笔者均不建议使用。
分布式锁
- 使用数据库当分布式锁
- 使用redis当分布式锁
- 使用Zk当分布式锁
- 以上三种都是比较常见的,对于笔者而言使用ZK当分布式锁最安全,因为Zk当master宕机后会选举后才会继续工作,而redis使用单实例是没问题的,而笔者使用比较多的使用redis官方推荐的redisson作为DML(分布式锁管理器)管理比较多。
小结
其实以上几个错误用法,笔者都看到很多人这么使用,那么从刚刚笔者说的Redisson后面会有一期文章讲述一下Redisson的实现原理和用法。