Redis 分布式锁

redis · 浏览次数 : 0

小编点评

Redis 分布式锁是一种基于 Redis 的方式来保证多个节点之间的资源同步访问,避免并发操作导致的数据不一致问题。分布式锁的实现方式有多种,包括基于 setnx 的方式、基于 Lua 脚本的方式等。下面对分布式锁的演变和本地锁(单机用)利用 Redis 进行分布式锁的使用进行归纳总结。 一、本地锁(单机用) 本地锁是指在单个节点上实现的锁,通常使用 Redis 的 set 命令来实现。其基本思路是:当一个节点想要获取锁时,先使用 setnx命令尝试设置锁,如果返回 1,则表示成功获取锁;否则表示锁已被其他节点占用。为了避免因锁过期而导致的死锁问题,可以在设置锁的时候加上过期时间,同时在使用锁的过程中定期续期,以保证锁的有效性。 二、分布式锁 分布式锁是指在多个节点之间实现的锁,以保证多个节点之间的资源同步访问。其基本思路是:在每个节点上创建一个锁,通过 setnx 命令尝试获取锁,如果返回 1,则表示成功获取锁;否则表示锁已被其他节点占用。由于多个节点之间可能同时有多个请求争抢锁,因此需要考虑死锁和重入性问题。一种解决方法是使用 Lua 脚本进行加锁和解锁操作,以保证操作的原子性。另外,为了避免业务执行时间过长导致锁过期,还需要在锁的过期时间到期之前进行续期操作。 三、Redis 分布式锁的优势 相比于其他分布式锁的实现方式,Redis 分布式锁具有以下优势: 1. 实现简单,只需要使用 Redis 的 setnx 和 del 命令即可实现。 2. 性能较高,因为 Redis 是基于内存的数据库,读写速度非常快。 3. 容易实现,只需要在每个节点上部署 Redis 即可。 4. 可以使用 Lua 脚本进行加锁和解锁操作,保证操作的原子性。 5. 可以避免死锁和重入性问题,因为设置了锁的过期时间,并且每次加锁都需要使用 Lua 脚本进行原子操作。 四、Redis 分布式锁的应用场景 Redis 分布式锁广泛应用于需要多个节点之间同步访问资源的场景,例如: 1. 大型网站的高并发访问,如电商网站的购物车、订单结算等功能。 2. 分布式系统的协同计算,如大数据处理、人工智能等领域。 3. 微服务架构中的服务注册与发现,需要保证同一服务实例之间不会发生资源竞争。 4. 实时消息队列的消息订阅与发布,需要保证同一主题的消息能够被同一节点的消费。 总之,Redis 分布式锁是一种非常实用且高效的分布式锁实现方式,可以广泛应用于各种需要保证多节点之间资源同步访问的场景。

正文

Redis 分布式锁

分布式锁的演变

  1. 本地锁(单机用)
  2. 利用redis进行分布式锁 使用 set
  3. 防止死锁 加过期时间 使用 setnx
  4. 防止A请求未执行完 锁过期删除 B请求加锁后 A完成后误删该锁 使用 Hash结构, 规定每个请求只能删除自己的锁
  5. 保证并发安全,申请锁和加过期时间需要 原子性,用 lua脚本 加锁或解锁
  6. 考虑到 重入性 (每个请求只拿到一个锁后,可以多个函数或线程共用) 使用 Hash结构进行加减(hincrby) 操作
  7. 为了保证业务执行过长,锁不会过期。需要对锁进行 续期

1. setNX

  • 注意死锁
  • 注意lock过期时间和业务执行时间
  • 一般情况下完全够用

2. 考虑look重入性

  • 当一个锁被创建出来之后再同一个请求中不需要再额外申请其他锁,一个锁可以被重复使用,所以使用到 Hash数据结构的锁
  • 如果业务上需要应用到多个函数的锁的情况下,申请一个锁之后固定当前请求的uuid。入一个函数(或者线程)需要用到时 向当前锁的uuid +1
  • 直到请求结束后,检查线程锁是否归0 如果是释放该锁,如果不是说明其他线程未能执行完毕。
锁的操作保证原子性应对高并发
  • 当一个锁(Hash结构) 创建锁和增加过期时间,两步需要lua脚本进行执行保证原子性。
// lua 脚本编写 
// 加锁操作  如果锁不存在或者当前请求锁的uuid字段存在 则进行加一 (重入性)

KEYS[1]  //  -- 分布式锁的key
ARGV[1]  //  -- 锁的唯一标识,通常是线程ID或调用者标识
ARGV[2]  //  -- 锁的过期时间,单位为毫秒

if redis.call('exists', KEYS[1]) == 0 //锁不存在
or redis.call('hexists', KEYS[1], ARGV[1]) == 1 // 当前请求有锁 进入函数(或新开线程)无需额外申请锁
then
  redis.call('hincrby',KEYS[1], ARGV[1], 1)
  redis.call('expire',KEYS[1], ARGV[2])
  return true
else
  return false
end

//解锁操作
  if redis.call("hexists",KEYS[1], ARGV[1]) == 0 then // 无锁  无需解锁
      return false
  else if redis.call("hincrby",KEYS[1], ARGV[1], -1) == 0 then  // 锁存在且是自己请求的锁 进行减一操作,如果减为0 则解锁(删key)
      retrun redis.call("DEL",KEYS[1])
  else
      return 0
  • 当lock 创建时,需要同步启动一个定时续期任务,锁存在并过该定时时间进行续期,防止业务未完直接释放锁。
  • 当主线程执行完业务流程 并释放锁之后,续期机制同时结束。

与Redis 分布式锁相似的内容: