基于redis实现的分布式锁

来源:互联网 发布:计算机编程与维护 编辑:程序博客网 时间:2024/05/30 04:31

什么是分布式锁

先从字面理解,分布式锁(Distributed Lock)就是分布式部署的锁。

那么什么是锁呢?这个大家应该比较熟悉了,和我们经常遇到的“在多线程写同一个变量时需要加锁”、“多个客户端在更新数据库同一条记录时需要加锁”等等这些场景中提到的“锁”是一样的,是一种同步方式。

再来看维基百科给出的解释。

分布式锁,是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

个人觉得该解释没有说清楚的一点是:这个锁必须是分布式部署的。比如,只起一个redis服务(一个进程),我们同样可以实现系统间同步,但对于这种情况,我们能称之为分布式锁吗?个人觉得不能。当然,从另一角度看,把锁当成分布式系统的一部分,默认是分布式部署的,就解释得通了。

大家可能没有我这么纠结吧,竟然花了这么多篇幅来解释概念。。。(我就是抠字眼,20多年了,改不了了,-_-!! )

好了,进入正题。

如何实现

先说下本文所要实现的分布式锁是怎样的,也就是这样的分布式锁需要满足哪些条件:
a. 任何时刻,只能有一个客户端持有锁
b. 不能出现死锁的情况
c. 高可用性,即只要大部分redis节点正常工作,客户端便能正常地获取锁、释放锁

基于redis实现的分布式锁其实是单机redis锁的一种推广,所以在说明如何实现分布式锁之前,有必要先说下单机redis如何实现

单机redis实现锁

单机redis锁的核心其实就是命令SETNX:

SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作。
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
返回值:
设置成功,返回 1 。
设置失败,返回 0 。

使用SETNX可以这样实现锁:

if SETNX(lock_name, value) == 1:    // got lock and do something    do_something()     ...    // release lock    DEL lock_name

如果do_something()异常导致进程退出,则 DEL lock_name 执行不到,就会出现非常讨厌的死锁情况, 违反了前文提到的条件b.

如何避免死锁呢?

lock_name 自动删除,自然地,redis的 expire 命令派上了用场

if SETNX(lock_name, value) == 1:    // 让lock_name这个key自动过期,避免死锁    EXPIRE(lock_name, timeout)    // got lock and do something    do_something()     ...    // release lock    DEL lock_name

这样就够了吗?

timeout 怎样设置呢?设置的太大,则不利于其他进程快速获取锁;设置的太小,很可能 do_something() 还没完成,锁就被强行释放,这时若其他进程获得锁,则产生了数据竞争(data races)问题

暂时不管效率的问题,可以将 timeout 设为一个较大的数保证在 do_something() 结束之前当前线程一直持有这把锁,ok,单机版这个方案通过。

将这个方案推广到集群部署的redis环境怎样呢?

redis集群环境下的锁

主从(master/slave)部署方式

不考虑master宕机的情况,可以等同于单机redis考虑,上述方案没有问题

若master宕机,此时其中一个slave被选为master(可以是被动地,也可以自主选择),假设此时master的数据还没有同步到slave(完全有可能,比如master需要同步的数据较多,网络环境又恰好很差的情况):

if SETNX(lock_name, value) == 1:    // 让lock_name这个key自动过期,避免死锁    EXPIRE(lock_name, timeout)    // got lock and do something    do_something()     ...    // release lock    DEL lock_name

第一行仍然可以执行成功,导致数据竞争,违反了前文提到的条件a.

怎么破?

多主(multi-master)部署方式

这是redis的作者提供的一种算法——redlock

redlock的核心思想是:

  1. 多个redis节点完全独立,不存在主从关系;
  2. 客户端并发请求多个redis节点获取锁, 只有大部分节点都成功获取锁,才算获取锁成功,否则,认为获取锁失败,并释放所有已经成功获取的锁;
  3. 若其中一个节点宕机,则采用延迟启动技术,避免宕机节点重启后导致多个客户端获取锁。所谓延迟启动就是让节点延迟key 的TTL时间启动,以保证lock key过期

关于上文提到的 timeout 设置太差导致的效率问题,redlock建议对于有一系列小的动作组成任务可以将 timeout 设置的短一些,在临近过期时作续锁操作。

鉴于篇幅限制,redlock的深入分析另起文章。

弊端

通常情况下,我们的redis集群是主从部署的。若要使用redlock算法,需要自己维护multi-master redis集群,使用成本相对较高。

适用场景

单独维护redis multi-master集群的情况,可以考虑使用redlock

当然,如果可以容忍偶尔出现多个client同时持有锁,可以考虑使用master/slave redis集群环境下的锁