Redis 分布式锁面试题
Redis 分布式锁的实现见 Redis(八):Redis分布式锁实现
1、Redis分布式锁如何保证只能被某一个线程持有
使用命令:setnx key value key 不存在时设置成功返回值 ok,key 存在设置失败;
也可以采用 If(redisUtil.get(key)set key valuel,不过这段代码需要采用 lua 脚本实现来保证原子性
2、如何保证不死锁
给锁设置一个合理的过期时间,业务执行过程中节点异常宕机,有个兜底终止跳出方案。
使用命令:setnx key value ex seconds 设置 key 和对应的过期时问,到了指定的ex 时间锁自动释放。
3、如何防止释放别的线程锁
是指线程A对固定key加锁后,因为各种原因,在锁过期后,线程A还没执行结束;这时候线程B 也对同样的固定key加锁,但是,此时 A 线程执行完业务逻辑之后,还是会去释放锁 (删除 key),这就导致B线程的锁被A线程给释放了。即线程A由于执行超时,却释放了线程B的锁。
答: 给锁设置一个当前线程 id 或 uuid 的值,释放锁的时候判断锁的值与当前的值是否相等相等才释放锁。
以下是伪代码实现:
// step1: 生成一个线程id 或 uuid
String threadld = Thread.getCurrentThread0.getld0;
// step2:加锁,设置 value 为线程id 或uuid
setnx key threadld ex seconds
//step3: 锁的值与当前的值是否相等
if(threadld.equals(redisUtil.get(key))) {
//step4: 释放锁
redisUtil.del(key);
}
4、锁到期,数据如何保持一致性
Redisson 是采用看门狗机制,但是不断的续期,在高并发场景下会影响性能,
其实可以在分布式锁上层,采用先jvm本地锁(ReentrantLock和synchronized)来拦截,jvm本地锁没有命中后,再采用redis分布式锁拦截,这种会大大的提升性能性能。
5、如何解决redis主从节点不同步导致锁失效
假设主节点没来的及把刚刚加锁的数据给从节点,主节点就挂了,导致加锁失效,虽然这种情况很少见,但是高可用,拼的就是99.999...% 中小数点后面的位数。
答: 采用红锁算法解决这个锁失效问题,红锁算法认为,只要(N/2) +1 个节点加锁成功,那么就认为获取了锁,解锁时将所有实例解锁
顺序向3个节点请求加锁
根据一定的超时时间来推断是不是跳过该节点
2个节点加锁成功并且花费时间小于锁的有效期
认定加锁成功
也就是说,假设锁 30 秒过期,2 个节点加锁花了 31 秒,自然是加锁失败了。
但是这种实现方式,生产上并不推荐使用。很简单,原本只需要对一个 Redis 加锁,设置成功返回即可,但是现在需要对多个 Redis 进行加锁,无形之中增加了好几次网络 IO,万一第一个 Redis 加锁成功后,后面几个 Redis 在锁过程中出现了类似网络异常的这种情况,那第一个 Redis 的数据可能就需要做数据回滚操作了,那为了解决一个极低概率发生的问题又引人了多个可能产生的新问题,很显然得不偿失。并且这里还有可能出现更多乱七八糟的问题,所以这种 Redis 分布式锁的实现方式极其不推荐生产使用,退一万说如果真的需要这种强一致性的分布式锁的话,那为什么不直接用 zookeeper实现的分布式锁呢,性能肯定也比这个 RedLock 的性能要好
6、如何实现可重入
可重入锁的要点是对于同一线程可以多次获取锁,不同线程之间同一把锁不能重复获取怎么保证线程可重入获取锁呢?
答: 通过维护当前持有锁的计数来实现可重入功能。
加锁的时候,第一次获取锁时保存锁的线程标识,后续再次获取锁,先看是否是同一个线程,如果是的话只对锁计数进行递增。
解锁时,对锁计数进行递减,同时刷新锁的过期时间。如果计数为 0,最终才释放锁
7、高并发下如何优化
与ConcurrentHashMap的设计思想类似,用分段锁来优化。
8 不同场景下如何选择分布式锁方案
redis 分布式锁(推荐) 互联网项目并发量高,对性能要求高,比较推荐 redis 常见操作,例如基本类型 string、hash、list、set 等等操作可以采用 redis 或 lettuce。 对于跟分布式锁相关的操作集成redission。
分布式锁百分百可靠可以选用 Zookeeper作为分布式锁。采用 cap 理论中的 cp模型保证高可靠性 一般的项目我们可以结合不同的场景,同时兼容两种分布锁的实现。
zk分布式锁实现见 Zookeeper基础(五):分布式锁