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

【软考架构】案例分析-分布式锁

来源2024年5月第4题

问题一

分值(9分)
基于数据库实现分布式锁的缺点。

基于数据库实现分布式锁常见的实现方式主要有以下两种:

  1. 利用唯一索引插入
    基本原理是 唯一性约束充当“互斥机制”,确保同一时间只有一个客户端能获得锁。
  • 创建一张锁表,如 distributed_locks (lock_key VARCHAR PRIMARY KEY, owner_id VARCHAR, expire_time DATETIME)
  • 某客户端尝试通过插入一条唯一的 lock_key 记录来加锁:
INSERT INTO distributed_locks (lock_key, owner_id, expire_time)
VALUES ('my_lock', 'instance_1', NOW() + INTERVAL 10 SECOND);
  • 如果插入成功,则加锁成功;如果因主键冲突插入失败,说明锁已被持有。

  • 由 owner_id 判断是否是当前客户端持有锁,只有持有者才可删除:

DELETE FROM distributed_locks WHERE lock_key = 'my_lock' AND owner_id = 'instance_1';
  1. 利用数据库的 SELECT … FOR UPDATE 加悲观锁
  • 基本原理是通过数据库行锁实现互斥。

  • 在锁表中先插入一条标志性记录。
    使用事务和 SELECT … FOR UPDATE 对该记录加锁。

BEGIN;
SELECT * FROM distributed_locks WHERE lock_key = 'my_lock' FOR UPDATE;
-- 执行临界区代码
COMMIT;

这种方法的主要的问题是

竞争激烈时插入失败频繁。在高并发场景中,大量节点同时尝试 INSERT 锁记录会频繁触发主键冲突,导致性能下降,数据库压力剧增。
锁重入难实现。默认情况下无法支持可重入(同一线程/服务可多次加锁),需要额外存储 owner_id 和 lock_count 等字段,并设计相应逻辑,增加实现难度。
数据库成为瓶颈。分布式锁操作本质是对数据库的高频写入和查询操作,容易将数据库打满,拖慢主业务性能。

这两种方式虽然直观,但在高并发、高可用的生产环境中存在显著缺陷。其核心问题在于数据库是为数据持久化设计的,而非高频率的锁状态协调

两种实现方式简述

  1. 基于唯一索引插入
    • 原理:创建一张锁表,通过资源名(如method_name)的唯一索引来实现。加锁时,尝试插入一条代表该资源的记录,成功则获锁;释放锁时则删除该记录。
  2. 基于SELECT ... FOR UPDATE悲观锁
    • 原理:同样有一张锁表。加锁时,查询目标资源记录并使用FOR UPDATE对其加上行锁,成功则获锁;释放锁时提交事务即可。

基于数据库实现分布式锁的共性缺点

无论采用上述哪种方式,都会面临以下一个或多个核心问题:

1. 性能与可伸缩性差
  • 数据库压力大:每次加锁、释放锁都是一次或多次数据库事务操作(INSERT/DELETE 或 SELECT FOR UPDATE)。在高并发场景下,大量锁竞争会给数据库带来巨大的IO和CPU压力,使其成为系统瓶颈。
  • 响应延迟高:与基于内存的Redis等方案相比,数据库操作的网络和磁盘IO延迟要高出一个数量级,严重影响业务的响应速度。
2. 可靠性风险与单点故障
  • 单点问题:如果使用单机数据库,一旦数据库宕机,整个分布式锁服务将完全不可用,导致系统崩溃。
  • 主从切换数据丢失:为解决单点问题而采用数据库主从复制时,在主从切换的瞬间,可能因数据同步延迟导致锁状态丢失。例如,客户端在主库上加锁成功,但锁记录还未同步到从库,此时主库宕机,从库升级为新主库,但新主库上没有这条锁记录,导致另一个客户端也能成功加锁,破坏了互斥性。
3. 死锁与锁清理难题
  • 客户端崩溃导致死锁:这是最致命的问题之一。如果客户端在持有锁期间崩溃,无法正常执行DELETE或提交事务来释放锁,那么这个锁将被永久占用,其他客户端永远无法获得该锁。
  • 需要复杂的清理机制:为解决上述问题,必须引入超时机制。这需要在锁记录中增加一个过期时间字段,并配套一个独立的定时任务来扫描和清理过期的锁。这大大增加了系统的复杂度和维护成本。
4. 锁的公平性与“惊群效应”
  • 非公平锁:当锁被释放时,所有等待的客户端会同时发起下一轮抢锁请求(例如,不断重试INSERT),这会导致数据库在瞬间承受巨大的压力,即“惊群效应”。并且无法保证等待时间最长的客户端能优先获得锁。
5. 实现复杂性高(针对特定场景)
  • 可重入性实现困难:可重入锁是指同一个线程可以多次获取同一把锁。在数据库层面实现这一点非常复杂,需要在表中记录持有者信息和重入计数,并在获取和释放时进行判断,完全失去了其简单的初衷。
  • 阻塞等待实现低效:实现阻塞等待通常需要应用层通过“循环重试 + 睡眠”来模拟,这既低效又消耗资源。

两种方式的特有缺点

  • 对唯一索引插入方式:无法简单实现锁的自动超时释放,除非额外增加时间字段和定时任务。在并发插入时,大量冲突会导致大量的数据库异常,需要应用层捕获并处理。
  • SELECT ... FOR UPDATE方式:严重依赖数据库的行级锁。如果表设计或查询不当,可能升级为表锁,造成灾难性后果。同时,它必须在一个数据库事务中完成,长时间持有事务连接会耗尽数据库连接池。

总结

数据库实现分布式锁的核心缺陷在于:它将一个需要高性能、低延迟、高可用的协调服务,强加在了一个为持久化存储设计的关系型数据库上。

因此,它通常只适用于:

  • 并发量极低的简单场景。
  • 已经重度依赖数据库且不允许引入新组件的遗留系统。
  • 作为学习原型或演示

对于现代分布式系统,Redis(通过SETNX + Lua脚本)或 ZooKeeper/etcd(通过临时有序节点)是实现生产级分布式锁的更优选择,它们在设计之初就充分考虑了对这些问题的解决方案。

问题二

分值(10分)
举一个产生Redis分布式锁死锁的场景。

Redis 分布式锁本身通过「超时自动释放」机制(设置 expire 时间)降低了死锁风险,但在 锁超时、锁释放逻辑异常、主从切换 等场景下,仍可能出现「伪死锁」或「真死锁」(虽 Redis 无传统数据库的死锁检测,但会导致资源长期无法释放)。以下是一个 最典型、生产环境高频出现的死锁场景

死锁场景:真死锁,业务阻塞

死锁的本质是

锁未及时释放 + 锁还未自动过期
⇒ 所有其他节点都被“锁”住了,无法进入临界区 ⇒ 死锁

系统使用 Redis 实现分布式锁,每个节点在执行关键业务逻辑前通过 SET key value NX PX 加锁。
加锁成功后执行临界区操作,最后显式调用 DEL key 解锁。

服务 A 加锁成功,执行如下命令:

  1. 服务 A 加锁成功,执行如下命令:
SET lock:order:123 "nodeA-uuid" NX PX 30000

表示锁住订单 123,锁有效期为 30 秒。
3. 服务 A 开始执行下单逻辑。
4. 执行过程中服务 A 崩溃(进程退出、机器重启、OOM 等)。
5. 因为服务 A 没来得及执行 DEL 解锁命令,Redis 上的锁 lock:order:123 仍然存在。
6. 此时锁还没过期(30 秒未到),其他服务节点(如服务 B)尝试加锁失败,始终得不到锁,业务阻塞。

死锁场景:锁超时 + 业务执行超时 + 错误释放他人锁

场景前提
  • 业务:秒杀系统的库存扣减,用 Redis 分布式锁保证同一商品不会超卖(锁 key 为 lock:goods:1001,对应商品 ID=1001);
  • 锁配置:设置锁超时时间 30 秒(认为业务最多 30 秒完成),采用「非原子操作」释放锁(先判断是否自己的锁,再删除);
  • 并发情况:节点 A、节点 B 同时竞争该锁。
场景流程(一步步触发死锁)
  1. 节点 A 成功加锁
    节点 A 执行 SET lock:goods:1001 "nodeA:thread123" EX 30 NX,成功获取锁,开始执行库存扣减业务(如查询库存、调用支付接口、写入订单表)。

  2. 节点 A 业务执行超时
    因支付接口响应缓慢(或网络波动、服务器 CPU 飙升),节点 A 的业务执行了 35 秒,超过了锁的超时时间 30 秒。此时 Redis 会自动释放该锁(删除 lock:goods:1001 键)。

  3. 节点 B 抢占释放的锁
    节点 B 一直等待锁,Redis 释放节点 A 的锁后,节点 B 立即执行 SET 命令成功获取锁,开始执行自己的库存扣减业务。

  4. 节点 A 错误释放节点 B 的锁
    节点 A 的业务终于执行完成,开始执行「释放锁」逻辑。但此时节点 A 持有的锁已被 Redis 超时释放,且锁已被节点 B 抢占。
    若释放锁时未做「原子校验」(仅通过 GET 命令判断锁值是否为自己的标识,再执行 DEL),会出现以下问题:

    // 节点 A 的错误释放锁逻辑(非原子操作)
    String lockValue = redis.get("lock:goods:1001");
    if ("nodeA:thread123".equals(lockValue)) { // 此时锁值已变成 "nodeB:thread456",判断失败?不!// 关键问题:GET 和 DEL 之间存在时间差,可能出现「幻读」// 假设节点 A 执行 GET 时,节点 B 的锁刚好超时释放(极端情况),或判断逻辑被绕过redis.del("lock:goods:1001"); // 错误删除了节点 B 持有的锁!
    }
    

    (注:即使判断逻辑看似没问题,高并发下 GETDEL 之间的时间窗口仍可能被利用,导致误删他人锁)

  5. 节点 C 抢占节点 B 被释放的锁
    节点 A 错误删除节点 B 的锁后,节点 C (另一台服务器)立即抢占锁,开始执行库存扣减业务。

  6. 死锁/资源混乱形成

    • 节点 B 仍在执行业务,但锁已被节点 A 误删,节点 C 同时持有锁执行相同业务,导致「锁失效」—— 两个节点同时操作库存,出现超卖;
    • 若节点 B 后续执行释放锁逻辑,又可能删除节点 C 的锁,形成「连锁误删」,最终导致锁完全失控,所有节点都能同时操作资源,相当于「分布式锁失效」,本质是一种「伪死锁」(锁无法有效保护资源,等同于资源被死锁占用)。
更极端的「真死锁」变种

若节点 A 加锁时 未设置超时时间EX 参数遗漏),且节点 A 执行业务时突然宕机(如服务器断电、JVM 崩溃),则节点 A 持有的锁会永久保存在 Redis 中,没有任何机制能自动释放。后续所有节点(B、C、D)都无法获取该锁,导致商品 1001 的库存扣减业务完全阻塞,形成「真死锁」。

场景核心问题分析

  1. 锁超时与业务超时不匹配:锁超时时间设置过短,业务未执行完锁就被释放,是后续所有问题的根源;
  2. 非原子释放锁GET + DEL 是两步操作,存在时间窗口,导致「误删他人锁」;
  3. 未设置锁超时时间(变种场景):遗漏 EX/PX 参数,节点宕机后锁无法自动释放,直接形成永久死锁;
  4. 无锁持有者校验(若释放时不判断锁值):会直接删除任意节点的锁,加速死锁触发。

如何避免该场景的死锁?

  1. 锁超时时间合理设置:基于业务最大执行时间上浮 50%-100%(如业务最多 30 秒,设置锁超时 60 秒),并结合「锁续期」(如用 Redisson 的 watch dog 机制,业务未完成时自动延长锁超时);
  2. 原子释放锁:用 Lua 脚本执行「校验 + 删除」原子操作,避免时间窗口:
    if redis.call('get', KEYS[1]) == ARGV[1] thenreturn redis.call('del', KEYS[1])
    elsereturn 0
    end
    
  3. 强制设置锁超时:禁止不设置 EX/PX 的加锁操作,防止节点宕机后锁永久残留;
  4. 使用成熟客户端:优先用 Redisson、Jedis 等封装好的分布式锁工具,内置锁续期、原子释放、死锁防护机制,避免手写逻辑出错。

总结

该场景的核心是「锁超时与业务超时不匹配」引发的连锁反应:锁被自动释放 → 他人抢占锁 → 原持有者误删他人锁 → 锁失控/资源混乱。这是 Redis 分布式锁最典型的死锁场景,也是生产环境中最容易踩坑的情况,关键解决思路是「合理设置超时 + 原子释放 + 锁续期」。

http://www.dtcms.com/a/560733.html

相关文章:

  • 15.5.手机设备信息
  • Mysql基础1
  • 集团网站网页模板网站建设超速云免费
  • HTTPS:现代网站运营的安全基石与价值引擎
  • 老鹰网网站建设外贸是做什么的工作
  • [N_083]基于springboot毕业设计管理系统
  • kotlin学习 lambda编程
  • 如何写好汇报材料经验总结
  • 百度收录的网站标题 --专业做公司网站的机构
  • 视频时间戳PTS和DTS的区别
  • 09-神经网络的结构:描述神经网络的层次化组成和设计
  • 【ComfyUI】Stable Audio 文本生成音频
  • 音视频入门核心概念:容器、编码、流与时间戳
  • 网站的域名每年都要续费建个什么网站赚钱
  • 建站之星破解版在下列软件中
  • RocketMQ之长轮训机制
  • 论文阅读-EfficientAD
  • 跟der包学习java_day6「面向对象编程(OOP)」
  • 好的企业管理网站深圳市中心
  • 阿克苏建设局网站wordpress app
  • 使用 Ksycopg2 驱动实现 Kingbase 数据库增删改查系统
  • released信号,windowIcon/setWindowIcon(QIcon),qrc机制
  • 等价多米诺骨牌对的数量(C语言)
  • Python pandas数据透视表(pivot_table)详解:从入门到实战,多维数据分析利器
  • 江西新余网站建设网站建设页面设计规格
  • ATPrompt:基于属性的视觉提示
  • 手机如何制作网站教程网站双线选择
  • upload文件上传漏洞浅析
  • GitHub 热榜项目 - 日榜(2025-11-02)
  • 网站稿件管理发布系统中山网站建设半江红