Redis实现分布式锁

前言

现在行业中,任何一个团队只要涉及到分布式系统,都会面临一个问题,如何确保同一时间只有一个服务(或者说进程等),可以访问共享资源。就是在多个服务等环境下对共享资源要进行互斥访问。

我们在平时实现互斥访问的时候常规解决办法就是加互斥锁,有互斥锁,就必须把锁的竞争结果告诉所有服务。所以我们需要思考是否可以将锁的信息存储到所有服务都可以轻松访问的地方。这里可以是Redis、Zookeeper等。本文主要讲解Redis实现的分布式锁。


单机版Redis实现

这边我们需要知道任何想要去获取资源的服务一定是Redis客户端,这样这些服务才能通过Redis去实现互斥访问。任何一个服务可以使用Set命令去发送它要锁定的资源。

1
SET 'resource_name' 'random_value' NX PX 10000

NX:代表这条命令会在reource_name不存在的情况下才会设置该Key
PX:代表键值信息会在指定超时毫秒时间之后自动删除
EX:代表键值信息会在指定超时时间之后自动删除

1
127.0.0.1:6379> set LOCK 1002 NX EX 1000

redis-setnx

如上图,所有申请锁的客户端同时发送该命令,返回成功者即成功获得锁,获得锁后该服务可以独占使用共享资源并在使用完成后发送释放锁的命令,或者该锁会在指定时间之后自动删除。之后,所有服务可以再次竞争该资源。

注意:因为所有服务都在申请同一个资源,所以他们LOCK的Key相同,但是他们的Value应该为一个随机数。

Q1:为什么Value不能是固定值?
A1:因为我们想要的是只有设置该锁的客户端才有权利去释放该锁,只有知道随机值的客户端才能去释放锁。

Q2:什么时候出现自己设置的锁被其他服务删除的情况?
A2:如下图的超时问题,由于A任务没有在超时时间内执行完,导致B任务SETNX成功,当A任务执行完毕后删除锁信息的时候,其实是把B的锁信息给删除了

超时问题

我们在删除的时候可以执行一段lua脚本,先判断锁信息的Value值是不是客户端设置的随机值

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发生故障就会导致整个分布式服务不可用。

这时候会想为Redis实例添加一个从节点,以便实现故障转移,虽然添加从节点之后,该实例会成为Master,会进行主从复制,但是这个复制过程是异步的,也就是说主节点的锁信息还没有同步到从节点那边发生故障后,重新选举出来的主节点不一定包含锁信息,这样就会导致其他服务可以重新获得该锁,出现了多个服务获得同一把锁的情况。因此实现Redis的HA我们会采用Redis的集群部署方案,采用多个Master节点。


集群版Redis实现

这边我们使用Redis官方推荐的RedLock算法

红锁算法

红锁有很多性能问题:

  1. N次加锁带来的性能问题,考虑采用多路复用技术优化。
  2. 全部Master节点重启导致锁信息丢失,需要配合AOF是实时刷盘进行持久话,实时刷盘也会带来性能问题。