应用场景:
分布式锁是为了保证同一时刻只有一台机器的一个线程执行某段代码。分布式锁的目的如下
-
解决业务层幂等性
-
解决 MQ 消费端多次接受同一消息
-
确保串行|隔离级别
-
多台机器同时执行定时任务
最近在工作中遇到了一些问题,上游重复调用下游接口下发数据导致数据重复,需要用redis锁防重,线程获取不到锁时直接提示给上游已经下发过数据。
redis分布式锁:
主要实现是调用redis的SETNX命令(set if not exists),如果返回1,则获得资源并加锁;如果返回0,则没有获得锁,锁被其他资源占用。需要注意的是:
- 必须设置过期时间,防止获得锁的服务器宕机后产生死锁
- 添加redis数据必须与设置过期时间在同一个命令中,不能分开写,因为redis只保证单条命令的原子性,防止set完数据后还没设置过期时间就宕机产生死锁
- value应该是客户端生成的唯一的字符串,解锁时进行判断,防止释放了别人的锁。(例:客户端1在执行释放锁之前,锁已经过期自动删除,此时客户端2拿到了锁,客户端1执行del操作就会释放客户端2的锁)
- 释放锁的操作必须使用Lua脚本来实现。释放锁其实包含三步操作:GET、判断和DEL,用Lua脚本来实现能保证这三步的原子性
-
获得锁:
SET lock_key random_value NX PX 5000 (SET key value [NX|XX] [EX|PX] seconds )
- NX – 只有键key不存在的时候才会设置key的值
- XX – 只有键key存在的时候才会设置key的值
- EX seconds – 设置键key的过期时间,单位时秒
- PX milliseconds – 设置键key的过期时间,单位时毫秒
-
释放锁:
if redis.call('get',KEYS[1]) == ARGV[1] then
return redis.call('del',KEYS[1])
else
return 0
end
redis分布式锁存在一些问题:1、锁时间的设置困难,太短可能过早的释放锁,造成数据安全问题。太长的话,如果客户端挂掉会长时间无法释放锁,导致其他客户端锁请求阻塞或者失败,因此需要程序员有丰富的经验;2、为了保证redis的高可用必然要搭建集群,但redis主从同步会有时间间隔,如果一个客户端已经从主节点获得了锁,一旦主节点挂掉或者网络抖动导致切换到从节点后,就可能有另一个客户端重复获得锁。那为什么还广泛使用redis分布式锁呢?我们常用 N 个9 来量化可用性,只要能保证高可用性效果就达到了。
参考文章:高可用的分布式锁如何设计