书接上文 — > 从零开发短视频电商 分布式锁-基于数据库实现
实现
相关文档:https://redis.io/docs/manual/patterns/distributed-locks/
加锁
在Redis中加锁非常简便,直接使用SET命令即可。示例及关键选项说明如下:
SET resource_1 random_value NX EX 5
参数/选项 | 说明 |
---|---|
resource_1 | 分布式锁的key,只要这个key存在,相应的资源就处于加锁状态,无法被其它客户端访问。 |
random_value | 一个随机字符串,不同客户端设置的值不能相同。 uuid:线程id 参考 redisson |
EX | 设置过期时间,单位为秒。您也可以使用PX选项设置单位为毫秒的过期时间。 |
NX | 如果需要设置的key在Redis中已存在,则取消设置。 |
示例代码为resource_1这个key设置了5秒的过期时间,如果客户端不释放这个key,5秒后key将过期,锁就会被系统回收,此时其它客户端就能够再次为资源加锁并访问资源了。
Spring可以直接使用
redisTemplate.opsForValue().setIfAbsent(K key, V value, Duration timeout)
解锁
解锁一般使用DEL命令,一个客户端设置的锁,必须由自己解开。因此客户端需要先使用GET命令确认锁是不是自己设置的,然后再使用DEL解锁。在Redis中通常需要用Lua脚本来实现自锁自解:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
续租
当客户端发现在锁的租期内无法完成操作时,就需要延长锁的持有时间,进行续租(renew)。同解锁一样,客户端应该只能续租自己持有的锁。在Redis中可使用如下Lua脚本来实现续租:
续租 需要每个锁后台定时任务 时间建议是锁的1/3 去续租
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("expire",KEYS[1], ARGV[2])
else
return 0
end
集群或主从一致性问题
Redis的主从同步(replication)是异步进行的,如果向master发送请求修改了数据后master突然出现异常,发生高可用切换,缓冲区的数据可能无法同步到新的master(原replica)上,导致数据不一致。如果丢失的数据跟分布式锁有关,则会导致锁的机制出现问题,从而引起业务异常。下文介绍两种保障一致性的方法。
使用红锁(RedLock)
红锁是Redis作者提出的一致性解决方案。红锁的本质是一个概率问题:如果一个主从架构的Redis在高可用切换期间丢失锁的概率是k%
,那么相互独立的N个Redis同时丢失锁的概率是多少?如果用红锁来实现分布式锁,那么丢锁的概率是(k%)^N
。Redis节点越多则一致性越强,鉴于Redis极高的稳定性,此时的概率已经完全能满足产品的需求。
说明 红锁的实现并非这样严格,一般保证M(1<M<=N)
个同时锁上即可,但通常仍旧可以满足需求。
红锁的问题在于:
- 加锁和解锁的延迟较大。
- 难以在集群版或者标准版(主从架构)的Redis实例中实现。
- 占用的资源过多,为了实现红锁,需要创建多个互不相关的云Redis实例或者自建Redis。
使用WAIT命令
Redis的WAIT命令会阻塞当前客户端,直到这条命令之前的所有写入命令都成功从master同步到指定数量的replica,命令中可以设置单位为毫秒的等待超时时间,实现成本低。在云Redis版中使用WAIT命令提高分布式锁一致性的示例如下:
SET resource_1 random_value NX EX 5
WAIT 1 5000
使用以上代码,客户端在加锁后会等待数据成功同步到replica才继续进行其它操作,最大等待时间为5000毫秒。执行WAIT命令后如果返回结果是1则表示同步成功,无需担心数据不一致。相比红锁,这种实现方法极大地降低了成本。
需要注意的是:
- WAIT只会阻塞发送它的客户端,不影响其它客户端。
- WAIT返回正确的值表示设置的锁成功同步到了replica,但如果在正常返回前发生高可用切换,数据还是可能丢失,此时WAIT只能用来提示同步可能失败,无法保证数据不丢失。您可以在WAIT返回异常值后重新加锁或者进行数据校验。
- 解锁不一定需要使用WAIT,因为锁只要存在就能保持互斥,延迟删除不会导致逻辑问题。
最后
说了这么多,我觉得不要造轮子的话,可以使用redisson的分布式锁,这个开源项目内置很多分布式对象,很多开源的分布式锁都是基于他来做的。它也内置了看门狗,但是如果加锁的时候指定了过期时间,那么 Redission 不会给你开启看门狗的机制。
参考:
-
https://github.com/redisson/redisson/wiki/8.-Distributed-locks-and-synchronizers
-
https://help.aliyun.com/document_detail/146758.html