Post

分布式锁选型 Redis vs Zookeeper

分布式锁作为分布式环境下并发控制利器,使用场景广泛。分布式锁通常可利用中间件RedisZookeeper来实现, 例如针对Java语言Redis有Redisson组件, Zk有Curator组件。

  • Redis是一款内存数据库,通常可用来做缓存,由于其执行命令使用单线程,也可以用来实现分布式锁, 在集群模式下,Redis提供主从复制和哨兵机制实现高可用性;
  • Zookeeper是一款分布式协调中间件,集群模式下,Zk基于ZooKeeper Atomic Broadcast(ZAB)自实现了共识机制

从其中间件属性就可以看出Redis是偏AP,而Zk是偏CP的。

Distributed Locks with Redis

使用命令SET key value NX PX 30000对某个不存在的key赋值,如果设置成功则过期时间设为30000ms,由于Redis串行执行命令,且该命令的执行是原子的, 可以达到分布式锁抢锁的效果,同时在释放锁时使用Lua脚本判断当前线程是否持有该锁,持有则释放锁:

1
2
3
4
5
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

使用Redis单机节点部署时存在单点故障,Redis官方推荐使用红锁算法(RedLock Algorithm)方式确保分布式锁的可用性:

红锁算法下,客户端获取锁的骤入如下:

  1. 获取当前初始时间戳
  2. 获取所有Redis的锁,获取锁的timeout可根据锁本身的过期时长来确定,比如锁10s过期,那么获取锁如果Redis 50ms没响应就算获取失败了,如果获取失败则忽略;
  3. 当全部获取完后,如果客户端获取到锁的数量超过Redis数量的半数 当前时间戳距离初始时间戳的时长小于锁的过期时长,则获取锁成功,成功后锁的有效时长即为之前的时间戳差值;
  4. 如果第3步判定获取锁失败,则把获取成功的锁给主动释放掉。

使用红锁算法仍然会存在如下问题:

比如有3台Redis server,分别为r1,r2,r3,客户端A获取到其中两台的锁(r1,r2),超过半数,获取锁成功,此时r2重启,内存数据丢失,客户端B又来获取同一把锁, 此时可以获取到(r2,r3)的锁,超过半数,获取锁成功,这样客户端A和B就同时持有同一把锁。可以打开AOF,当机器重启后也会保留之前的数据,但是AOF的fsync策略默认为1s一次, 如果Redis直接宕机,最多会丢失2s的数据,但如果fsync策略该为always也会影响写入性能。一种解决办法是宕机后不立即重启,而是等待一个过期时间后再重启,这样就不会有A和B同时持有同一把锁的情况发生。

除此之外,Martin Kleppman(DDIA作者)对RedLock算法提出反对,之后 Salvatore Sanfilippo 反对了反对

Martin Kleppman提出RedLock会自动释放锁,当客户端发生类似GC等阻塞的耗时操作时无法保证锁的安全性,须添加fencing token(其实就是类似版本号的乐观锁)保证安全性。 而Salvatore Sanfilippo说Martin Kleppman的case是yy出来的,且分布式锁只是确保资源独占的一种高效的方式(just a weak lock to avoid, most of the times, concurrent accesses for performances reasons),并不应该依赖分布式锁完全确保资源独占。 同时有评论指出:但是如果只是为了提升性能,而不是完全依赖分布式锁保证安全性,那其实单机Redis节点即可,没必要上RedLock。

Distributed Locks with Zookeeper

不同于RedLock,Zk集群内部自实现了共识算法,所以客户端直接与其中一台机器交互即可。

Zk基于ZAB协议实现共识机制,集群所有的写操作集中由Leader处理,Leader会广播消息到所有Follow,当ack超过半数时(包含自己的一票)会提交写并通知Follower提交。

Zk使用全局递增的zxid来记录消息进度,如果Leader宕机会在最新消息进度的节点中投票产生新Leader。

可以看到Zk一次写操作涉及多次交互,故写性能没有Redis高。

Conclusion

如果有其他方式兜底保证资源获取的互斥(如mysql更新时的version乐观锁),借助分布式锁是为了提升性能,用单机Redis即可;

如果完全依赖分布式锁保证资源获取的互斥(也不应该完全依赖,还是得加兜底),用Zookeeper。

References

This post is licensed under CC BY 4.0 by the author.