当前位置: 首页 > article >正文

基于 Redis 实现分布式锁:原理及注意事项

文章目录

  • 基于 Redis 实现分布式锁:原理及注意事项
    • 基于 Redis 实现分布式锁的原理
    • Redis 分布式锁的过期时间和锁续期机制
    • 如何防止锁被其他 goroutine 删除?
    • Redis 分布式锁存在的单点故障问题:基于 RedLock 的解决方案
    • 高并发场景中 Redis 分布式锁的优化
      • 分段锁(Shared Lock)
      • 锁的细粒度化
    • 如何确定 Redis 分布式锁的过期时间?
      • 分级设置策略
      • 动态调整

基于 Redis 实现分布式锁:原理及注意事项

在这里插入图片描述

基于 Redis 实现分布式锁的原理

画板

重点在于:通过 SETNX来确保 Key 的不可重入。SETNX确保了GETSET方法的原子性。

Redis 分布式锁的过期时间和锁续期机制

第二个问题在于,如果服务实例在获取锁之后挂掉了,会导致删除锁的逻辑执行不到,这个时候应该怎么办?答案是可以为 Redis 分布式锁设置一个过期时间,过期之后锁自动删除,可以解决死锁问题

由此引发的另一个问题是,如果业务逻辑还没有执行完毕,锁就过期了,其他服务实例就可以获取锁了,这个时候该怎么办?解决办法是:

  • 在锁过期之前,刷新一下锁的过期时间。具体实现上,Go 当中的 Redsync 源码会使用 Lua 脚本原子性地判断当前 Key 在 Redis 缓存中的 Value 是否和当前的服务实例设置的 Value 一致(这个 Value 应该是一个标识服务实例的 Unique_ID)。使用 Lua 可以确保原子性,使得锁续期的过程不会被中断。
  • 需要自己去启动一个 goroutine 完成锁续期的工作。为什么不自动续期呢?原因是锁续期的接口可能会带来负面影响:比如其中的一个服务 hung 住了(一直没有执行完,可能是因为硬件的原因),但是一直进行锁续期,会导致其他服务实例永远获取不到锁。所以说锁续期不是默认的,由用户自行选择。

如何防止锁被其他 goroutine 删除?

Redis 分布式锁需要解决的问题:

  1. 互斥性:通过 SETNX 来解决;
  2. 死锁:通过设置过期时间 PX timeout 来解决;
  3. 安全性:锁只应该由持有这把锁的实例删除,而不能被其他实例删除

如何确保 Redis 分布式锁的安全性?通过服务实例加锁时设置的 Value 值来确定。这个 Value 应该只有设置了锁的那个服务实例(比如 goroutine)知道。Go 的 redsync 分布式锁库在加锁时会自动生成一个 unique_id。

Redis 分布式锁存在的单点故障问题:基于 RedLock 的解决方案

即使确保了互斥性与安全性,并通过设置过期时间解决了死锁问题,Redis 分布式锁仍然存在单点故障问题。

一个典型的场景就是:在 Redis 主从集群当中,仅有 Master 节点可以处理写请求,其他若干个 Slave 节点只能处理读请求,写操作依赖 Master 节点的同步(为了确保性能,很有可能是异步的)。存在的问题是,如果某个服务实例向该 Redis 集群申请加分布式锁,加锁之后 Master 节点突然宕机了,此时 Sentinel 节点需要推选 Slave 节点成为新的 Master,假如此时 Master 节点加锁的信息还没有同步到 Slave 节点,就会导致新推选出的 Master 节点(之前的 Slave 节点)不知道服务实例的加锁信息,导致锁可被重入。

解决上述问题的方法是通过 RedLock 来为服务实例提供分布式锁服务。具体来说,RedLock 由 N(奇数,确保「多数派」的产生)个 Redis 主节点(可以是 Redis 主从集群,可以是 Redis 单机实例,比如有 5 个 Redis 主节点,那么可以是 3 个 Redis 主从集群 + 2 个 Redis 单机实例。需要特别注意的是,Redis 分片集群不能用于 RedLock)组成,客户端请求加锁时,需要同时向所以 Redis 主节点并发地请求加锁,当多数(N / 2 + 1)Redis 主节点同意加锁请求时,客户端才算加锁成功,否则加锁失败。如果加锁失败,客户端需要同时向所以 Redis 主节点发送锁撤销的请求。

高并发场景中 Redis 分布式锁的优化

现在有一个「商品秒杀」的业务场景,针对单一商品,多个服务实例在下单时,通常需要使用分布式锁来保证商品不被「超卖」。但是「加锁」仍然是强行将「并发改为串行」,在高 QPS 场景下性能仍然不够好,请问在这种情况下,应该对分布式锁进行哪些优化?

分段锁(Shared Lock)

可以将单一商品库存分成多个段,每个段的 Key 各不相同,也就是 Redis 分布式锁在加锁时加锁的对象各不相同。通过某种调度算法均匀地将加锁请求分散到不同的 Key 上,每个 Key 使用独立的锁,可以提高并发度。

# 假设将库存分成10段
segments = 10
segment_id = user_id % segments  # 根据用户ID或其他策略分配段
lock_key = f"product_{product_id}_segment_{segment_id}"# 获取分段锁
if acquire_lock(lock_key):try:# 处理分段库存finally:release_lock(lock_key)

锁的细粒度化

根据业务场景设计更加细粒度的锁:

  • 按用户 ID 加锁(防止同一用户的重复请求);
  • 按商品 ID + 库存分段加锁;
  • 按区域/服务器加锁。

如何确定 Redis 分布式锁的过期时间?

分级设置策略

对于不同类型的操作,应该考虑设置不同的锁过期时间,比如对于内存操作,可以设置为500ms,而对于数据库事务,则应该设置为2s并加入锁续期机制以确保长事务的执行。

动态调整

可以根据系统的实时负载情况动态调整锁过期时间,比如在系统高负载时降低锁过期时间。

http://www.lryc.cn/news/2392566.html

相关文章:

  • 手机设备多?怎样设置IP保证不关联
  • Linux 中常见的安全与权限机制
  • Golang|单例模式
  • 哈尔滨工业大学计算机系统大作业程序人生-Hello’s P2P
  • 小程序定制开发:从需求到落地,打造企业专属数字化入口
  • 【C/C++】基于 Docker 容器运行的 Kafka + C++ 练手项目
  • Linux系统管理与编程24:基础条件准备-混搭“本地+阿里云”yum源
  • 新一代Python管理UV完全使用指南|附实际体验与效果对比
  • 如何在 Windows 10 PC 上获取 iPhone短信
  • STM32程序运行不了,仿真功能也异常,连断点和复位都异常了
  • Linux 系统中的软链接与硬链接
  • Python爬虫第22节- 结合Selenium识别滑动验证码实战
  • 【C/C++】chrono简单使用场景
  • Escrcpy(安卓手机投屏软件) v1.29.6 中文绿色版
  • Oracle MOVE ONLINE 实现原理
  • Linux:深入理解网络层
  • 【设计模式】简单工厂模式,工厂模式,抽象工厂模式,单例,代理,go案例区分总结
  • Linux_编辑器Vim基本使用
  • vue展示修改前后对比,并显示修改标注diff
  • LiveWallpaperMacOS:让你的 Mac 桌面动起来
  • [预训练]Encoder-only架构的预训练任务核心机制
  • 07-后端Web实战(部门管理)
  • mysql ACID 原理
  • [Rust_1] 环境配置 | vs golang | 程序运行 | 包管理
  • 二十五、面向对象底层逻辑-SpringMVC九大组件之HandlerMapping接口设计
  • 构建安全高效的邮件网关ngx_mail_ssl_module
  • HUAWEI交换机配置镜像口验证(eNSP)
  • 前端vue3实现图片懒加载
  • 网站每天几点更新,更新频率是否影响网站收录
  • 主流Markdown编辑器的综合评测与推荐