[本文转自](https://blog.csdn.net/SHU15121856/article/details/117373966)
1 Redis主从架构的分布式锁失效问题
1.1 问题描述
在Redis主从架构中,写入都是写入主Redis实例,主实例会向从实例同步key。
一个业务线程A通过向主Redis实例中写入来实现加分布式锁,加锁后开始执行业务代码。这时如果主Redis实例挂掉了,会选举出一个从Redis实例成为主的,如果刚刚加锁的key还没有来得及同步到从Redis中,那么选举出来的新的主Redis实例中就没有这个key,这个时候业务线程B就能加锁来获取分布式锁,执行业务代码了,而这个时候A还没有执行结束,所以就会出现并发安全问题,这就是Redis主从架构的分布式锁失效问题。
1.2 使用Zookeeper代替Redis解决分布式锁失效问题
Zookeeper(以后简称ZK)也是一种k-v形式的存储中间件,只是它的内部结构是树形的,在ZK的集群里也有主从的概念,主结点叫Leader,从结点叫Follower,如果使用ZK就能解决这种主从同步引起的分布式锁失效问题。
这是因为CAP理论说明,一致性、可用性和分区容错性最多只能保持三个中的两个(其中分区容错性一定要保持),Redis集群倾向AP(也就是更关注可用性),ZP集群则倾向CP(也就是更关注一致性)。
在向Redis集群里的主结点写入数据时,写入主节点就立刻告诉客户端写入成功。而在向ZK的主结点写入数据时,并不是立刻告诉客户端写入成功,而是先同步给从结点,至少半数的节点同步成功才能返回“写入成功”给客户端。
这个时候如果ZK的主节点挂了,ZK的ZAB分布式一致性协议能保证一定是数据同步完成的结点被选举为主节点,所以就不会发生分布式锁的失效问题。
1.3 使用RedLock解决分布式锁失效问题
如果不改用ZK,就是要用Redis来解决主从架构的分布式锁失效问题,那么可以使用RedLock,RedLock底层逻辑和ZK很类似。
首先要有多个(最好是奇数个)对等的(没有主从关系)Redis结点。当进行加锁时(比如是用SETNX命令),则这个设置key-value的命令会发给每个Redis结点执行,当且仅当客户端收到超过半数的结点写成功的消息时,才认为加锁成功,才开始执行后面的业务代码。
下图中,Client 1向Redis 1/2/3三个结点去写key-value,假设当前处在Redis 1和Redis 2写入成功了,Redis 3还没有写入成功的状态,这个时候Client 1就已经认为加锁成功了,实际上已经可以执行业务代码了。
此时,假设有一个Redis结点挂了(最坏的情况就是已经写入了key的一个结点挂了,如下图所示Redis 1挂了),这个时候假设Client 2也要尝试加锁,此时Redis 2由于已经被Client 1写过了,没法写入成功,但是Redis 3可以写入成功。此时只有1个结点能写入成功,所以认为加锁不成功,这样Client 2就不会开始错误的执行业务代码,也就不会出现并发安全问题。
RedLock目前还有一些争议,所以很多时候可能不会用这种解决方案。下面是老师上课时展示的一块用Redisson实现RedLock的伪代码。在使用的时候,大体就是对不同的Redis实例,用Redisson获取RLock对象,然后用这些RLock对象来构造RedissonRedLock对象,然后用它来实现加锁解锁的逻辑即可。
2 Redis分布式锁高并发量下性能优化
2.1 问题描述
“分布式锁”这个东西从概念上就和“高并发”是背道而驰的,只是为了解决高并发量下的程序并发安全问题,用一把锁实际上把所有控制的代码(线程)变成了顺序执行,这样其实会损失很多性能,但是在并发量不是太高的场景下一般也就直接这样用就够了。
2.2 分段锁思想提升并发量
ConcurrentHashMap是并发安全的集合,而且在高并发的场景下性能表现也很不错,可以参考下它的底层实现——分段锁。利用这种思想就可以从业务角度出发,对操作的业务实体进行分段,来优化分布式锁。
比如在秒杀场景下,iPhone这个商品有200个,要做秒杀,那么可以每20个分成一段,让它变成10个物品:
iPhone_1(20个), iPhone_2(20个), ..., iPhone_10(20个)
1
这个时候如果要做秒杀,则不同段的“商品”iPhone_x可以并行执行,在上面这种方式下并发量就提升到了10倍。
如果某个段的“商品”库存不够减了,比如iPhone_2只剩1个了,但是这个时候要一次性减掉3个,这个时候就可以合并其他段位的“商品”,然后再去减库存。