新闻详情

新闻详情

首页 / 资讯中心 / 详情

分布式锁方案选型-元一软件

发布时间:2026/9/3 15:19:29来源:尧图网络
分布式锁方案选型-元一软件
当同时操作共享资源时需要做并发控制。在单机上用synchronized或ReentrantLock能解决的互斥问题到了多机部署的分布式环境就行不通了因为它们只在单个JVM进程内生效跨多机就需要分布式锁了。先说结论最常用的分布式锁方案是Redis锁、DB乐观锁、或DB逻辑锁。高并发、能容忍极端情况下短暂的弱一致选Redis锁并发不高、强一致要求高选DB乐观锁、或DB逻辑锁不推荐DB行锁、ZK锁。下面展开介绍。一把合格的分布式锁要满足什么先对齐一下标准后续每种方案的好坏都可以对照互斥最基本的要求任意时刻只能有一个客户端持有同一把锁防死锁持有锁的客户端如果宕机了锁能自动释放防止其它客户端永远加锁失败可重入同一个客户端在持锁期间能再次拿到这把锁避免自己阻塞自己性能加锁、解锁开销要小吞吐要高悲观锁和乐观锁悲观锁觉得并发冲突大概率会发生所以操作前先加锁把资源独占起来操作完再释放。典型实现DB行锁select ... for update、DB逻辑锁通过新增lock_status状态字段实现、Redis的set nx px命令优点严格互斥不用像乐观锁那样反复重试缺点持锁期间其它线程只能等、有死锁风险、吞吐被锁的粒度和持有时间卡住适用场景适合写冲突频繁的场景乐观锁觉得冲突很少所以一开始不加锁等更新时再判断中途是否被其它线程修改了。典型实现version字段、CAS先比较再修改优点无锁实现开销较低缺点并发冲突较多时需要不断重试可能有ABA问题但用递增的version能解决仅CAS比较值则不行适用场景适合读多写少的场景如果写并发很高但误用了乐观锁的话会导致线程不停地重试最终线程池耗尽的严重问题两个并发更新请求的时序图如下业务入口服务无存储存储服务持有数据线程1先到达请求查询数据线程1返回业务数据、versionv1两个并发请求都基于 v1 计算后回写线程2后到达请求查询数据线程2返回业务数据、versionv1线程2更新业务数据带 v1先到达存储version匹配更新成功version升到v2线程2操作成功线程1更新业务数据带 v1因网络延迟后到达存储version不匹配当前版本v2≠v1更新失败线程1操作失败需上游重试线程1先发起请求但因网络问题后到达version校验阻止了旧数据覆盖新数据业务入口服务无存储存储服务持有数据上图展示了乐观锁的流程业务入口服务本身无存储、数据都在下游存储服务上查询时一并拿到业务数据、系统version回写时带上这个version做校验。这样能解决一个典型问题两个并发请求因网络抖动乱序到达存储服务先到的请求反而后处理导致旧数据覆盖新数据。有了乐观锁校验version不匹配的更新会失败从而避免旧覆盖新。主流方案选型1. DB行锁不推荐原理借助InnoDB的排他锁在事务里执行select ... for update对查到的行加行锁事务提交或回滚后释放。别的事务再尝试上DB行锁时会阻塞住。实现细节必须在数据库事务里用否则for update一执行完就释放了起不到持锁的作用where条件必须命中主键或唯一索引否则会锁表、或者产生大段间隙锁数据库事务内不要做耗时操作比如调外部RPC接口设置合理的锁等待超时innodb_lock_wait_timeout别让被阻塞的请求一直干等最佳场景低并发、强一致、已有DB不想引入新中间件比如公司内部系统的定时任务多个实例都可能触发该定时任务需要保证同一时间只有一个实例在跑。2. DB乐观锁原理不靠数据库自带的行锁而是在已有业务表里新增一个version字段依靠版本号、乐观锁来实现。实现细节先查询得到版本号v1然后更新时再判断version是否为版本号v1即update ... where biz_id? and version?最后判断影响行数如果大于0则说明修改成功如果是0则说明中途被别的线程修改了需要重试-- 通用更新带上version条件影响行数为0则说明被并发修改需重试 UPDATE biz_record SET name#{name}, status #{status}, version version 1WHERE biz_id#{bizId} AND version #{version};对应的Java示例// 业务代码查询版本号 -带version更新 -判断影响行数失败则重试 public boolean updateBiz(String bizId, String name, int status){int maxRetry3;for(int i0;imaxRetry;i){//1. 先查询得到版本号v1 Biz bizbizMapper.selectByBizId(bizId);//2. 更新时带上version条件即 wherebiz_id? andversionv1 int affectedbizMapper.updateByBizId(bizId, name, status, biz.getVersion());//3. 影响行数大于0说明成功为0说明被别的线程改过version变了重试if(affected0){returntrue;}}throw new BizException(并发冲突更新失败);}最佳场景中等并发、读多写少、能接受DB的性能上限比如库存扣减、余额扣减、业务状态流转并发冲突不算特别频繁乐观锁足够。又比如核心链路上减少不必要的外部依赖不用Redis锁、只用DB乐观锁毕竟DB的稳定性要高于Redis。3. DB逻辑锁原理不靠数据库自带的行锁而是在业务表里新增lock_status状态字段用一条带条件的UPDATE抢占锁抢到就置为占用状态释放再置回空闲状态。靠DB写操作的行级一致性保证同一时刻只有一个客户端抢成功是悲观锁。实现细节加锁update ... set lock_status1 where biz_id? and (lock_status0 or lock_expire_atnow())影响行数大于0即加锁成功解锁时带上lock_owner校验防止误删其它线程上的锁防死锁持有者宕机没正常释放状态会一直占用所以要加lock_expire_at过期时间靠定时任务清理过期锁、或持有者续期-- 加锁状态空闲、或已过期才能抢到同时记下持有者和过期时间 UPDATE biz_record SET lock_status1, lock_owner#{owner}, lock_expire_at #{expireAt}WHERE biz_id#{bizId} AND (lock_status 0 OR lock_expire_at NOW());-- 解锁带上 lock_owner 校验防止误删别人的锁 UPDATE biz_record SET lock_status0, lock_ownerNULL, lock_expire_atNULL WHERE biz_id#{bizId} AND lock_owner #{owner};-- 兜底定时任务清理持有者宕机导致的过期锁 UPDATE biz_record SET lock_status0, lock_ownerNULL WHERE lock_status1AND lock_expire_atNOW();对应的Java示例// 加锁影响行数大于0即成功 public boolean tryLock(String bizId, String owner, long leaseMs){Date expireAtnew Date(System.currentTimeMillis() leaseMs);returnbizMapper.acquireLock(bizId, owner, expireAt)0;}// 释放锁带上 owner 校验影响行数大于0即释放成功 public boolean unlock(String bizId, String owner){returnbizMapper.releaseLock(bizId, owner)0;}最佳场景中等并发、需要持久化锁状态比如跨多个服务、多个步骤的长流程需要持锁一段时间、且锁状态要落库重启不丢、可审计追溯。DB逻辑锁持锁不绑事务、状态可持久化比DB行锁灵活。4. Redis锁单节点redis锁原理依靠Redis单线程串行执行的特点用SET key value NX PX timeout一条命令原子完成“键不存在才设置 设置过期时间”。设置成功就算拿到锁过期时间一到自动释放、防死锁。实现细节value必须是唯一标识比如UUID释放锁时先比对再删防止误删其它线程上的锁释放锁需要保证原子性比对value、与删除是两步要用lua脚本包成原子操作锁续期业务执行时长不可控过期时间设短了业务没跑完锁就释放了设长了宕机后锁仍然占用。Redisson用看门狗机制解决后台线程每隔一段时间自动检查、并续期锁的TTL直到业务跑完、并主动释放锁可重入用Hash结构记下持有者和重入次数同一客户端就能多次获取举例手写Redis锁// 加锁NX保证互斥PX设置过期时间value用唯一标识防误删 public boolean tryLock(String lockKey, String requestId, long expireMs){returnOK.equals(jedis.set(lockKey, requestId,NX,PX, expireMs));}// 解锁用lua脚本保证“比对删除”的原子性 public boolean unlock(String lockKey, String requestId){String luaif redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end;returnjedis.eval(lua, Collections.singletonList(lockKey), Collections.singletonList(requestId)).equals(1L);}生产环境考虑直接用Redisson框架可重入、锁自动续期、lua脚本都封装好了RLock lockredisson.getLock(distributedLock: bizId);try{// 尝试加锁最多等待10s锁自动续期if(lock.tryLock(10, TimeUnit.SECONDS)){doBusiness();}}finally{if(lock.isHeldByCurrentThread()){lock.unlock();}}最佳场景高并发写场景、能容忍极端情况下短暂的弱一致比如秒杀场景防重复提交、消息消费的幂等处理。并发大要求加锁解锁快能接受Redis主从切换那一瞬间极小概率的锁失效必须要做兜底的业务幂等校验。多节点RedLock锁不推荐Redis的高可用是靠主从异步复制来实现的。有个经典问题客户端A在master加锁成功锁还未同步到slave此时master宕机了slave升成新master客户端B加锁也能成功。于是A、B同时持锁产生并发冲突。针对这个Redis作者提出了RedLock向n个一般5个互相独立的Redis节点申请锁多数n/21成功才算加锁成功少数节点挂了不影响。但这个方案引入了复杂度、牺牲了性能在GC暂停或网络延迟下仍然有边界问题就不推荐了。单节点Redis锁配合业务幂等兜底已经能解决核心问题了兼顾了性能和正确性。5. ZK锁不推荐原理靠ZK的临时顺序节点。客户端在/lock路径下创建一个临时顺序节点/lock/seq-00000001然后取出/lock下所有子节点排个序看自己是不是序号最小的是最小的拿到锁不是最小的就监听前一个节点的删除事件前一个节点一删自己被唤醒再判断一次释放锁就是删掉自己创建的节点。客户端宕机导致session失效ZK会自动删掉它建的临时节点锁跟着释放。最佳场景对一致性要求极高、能接受性能和运维代价比如集群选主节点宁可慢也不能错。在业务场景中就不用考虑了更适合用于集群运维场景比如Hadoop、Kafka那套大数据生态。总结方案一致性性能实现复杂度防死锁最适合的场景DB行锁悲观锁不推荐强一致低低需配置超时低并发强一致、已有 DB内部系统的定时任务DB乐观锁乐观锁强一致中高低无死锁中等并发、读多写少库存扣减、余额扣减、业务状态流转等DB逻辑锁悲观锁强一致中中靠过期兜底中等并发、需持久化锁状态Redis锁悲观锁弱一致高中靠过期续期高并发写场景、可容忍极端弱一致秒杀、幂等ZK锁不推荐强一致低高自动释放集群选主不要在业务场景使用对照上面的表格就能快速找到合适的方案。优先考虑DB乐观锁、DB逻辑锁、Redis锁不推荐DB行锁、ZK锁。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

进程问题相关2(AI回答) 2026/9/3 16:46:55

进程问题相关2(AI回答)

软件工程里有时一个任务会开启另一个任务,有时还会开启另一个进程?你的观察非常敏锐!在软件工程中,这种“套娃”式的操作非常普遍。这是因为现代软件需要同时处理多件事情(并发或并行),而根据任…

阅读更多 →
机器人关节电机硬件工程师:从电路设计到智能执行系统架构 2026/9/3 16:46:55

机器人关节电机硬件工程师:从电路设计到智能执行系统架构

机器人关节电机,这个看似传统的硬件领域,其实正经历着从"机械传动"到"智能执行"的关键转变。如果你以为硬件工程师只是画电路板、选型电机,那可能错过了这个岗位真正的价值所在。 在机器人技术快速发展的今天&#xff0…

阅读更多 →
我以为一条 Docker 命令就够了:飞牛 NAS 部署 Memos,这几个坑最好提前避开 2026/9/3 16:46:55

我以为一条 Docker 命令就够了:飞牛 NAS 部署 Memos,这几个坑最好提前避开

前言 我最开始看 Memos 的时候,觉得这东西应该属于 NAS 里最好搭的一类应用。 一条 Docker 命令,映射一个 5230 端口,再把数据目录挂载出去,浏览器打开页面就能开始记东西。没有复杂的文件夹,没有庞大的知识库结构&…

阅读更多 →
Simulink仿真:变速恒频风力发电并网模型搭建与调试指南 2026/9/3 16:46:55

Simulink仿真:变速恒频风力发电并网模型搭建与调试指南

简介:本资源是一套面向新能源电力系统研究者、高校师生及风电控制工程师的变速恒频风力发电系统Simulink仿真模型集合,聚焦风力发电并网建模、MPPT控制策略验证与系统动态特性分析等核心问题。压缩包共38个文件,含3个经典.mdl模型&#xff08…

阅读更多 →
STM32H743实时FIR滤波器设计与优化实战 2026/9/3 16:46:55

STM32H743实时FIR滤波器设计与优化实战

简介:本资源是一套面向嵌入式DSP开发者的STM32H743平台FIR低通滤波器实战工程,专为需要在高性能Cortex-M7内核上实现逐点实时信号滤波的工程师与高年级本科生设计,适用于音频预处理、传感器信号降噪、工业数据采集等对实时性与精度要求较高的…

阅读更多 →
基于GGUF和ggml的本地语音识别:transcribe.cpp完整实践指南 2026/9/3 16:43:54

基于GGUF和ggml的本地语音识别:transcribe.cpp完整实践指南

在语音识别技术快速发展的今天,如何在本地高效、准确地实现音频转文本成为许多开发者和研究者的关注焦点。transcribe.cpp 项目正是这样一个基于 C/C 的本地语音识别解决方案,它利用先进的 GGUF 模型格式和 ggml 推理引擎,为开发者提供了轻量…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞