分布式锁自己写,不用Redssion,结果翻车了…
mhr18 2025-05-02 19:44 14 浏览 0 评论
近日遇到了一个分布锁线上问题,导致用户获取锁一直失败,被阻拦提单近2H。 bug原因是Redis setnx获取锁超时,但实际写入成功,出现超时后并没有释放锁。由于锁维度是userId维度,导致用户再次提单一直无法获取锁,提单一直失败,客服同学说:用户情绪很激动,骂骂咧咧的。
为了避免锁超时导致并发问题,系统设置的超时时间过长——2小时。这导致用户2个小时都无法提单。由于系统日订单量在千万,即便超时比例非常低,每天也会有10单以上。用户找客服投诉的意愿十分强烈,并且态度很差。
在解决问题前,先回顾下我们分布式锁的技术实现方案
实现方案
- 加锁命令: redis setnx 命令在key存在时返回false,key不存在返回成功。由于redis命令的原子性,可保证多个只有1个客户端可成功。
- 解锁命令:del key 删除key,就是释放锁
锁漏释放问题
加锁或解锁异常的情况下,可能出现漏被漏释放。 如果未设置超时时间,那么就会导致锁一直未释放,像我们的提单的场景,如果用户一直无法申请锁,一直提单,用户绝对会有打人的冲动。
超时时间设置长短由业务场景决定,业务耗时短,超时就设置短一些,如果持锁时间长,锁超时设置长一些。
设置超时时间的命令是 EXPIRE KEY timeout 。由于redis并未提供原子命令同时 setnx, expire 。所以要使用lua脚本 改进setnx 命令。(建议公司Redis Client统一提供这个原子命令)
if (redis.call('setnx',KEYS[1],ARGV[1]) < 1) then
return 0;
end;
redis.call('EXPIRE',KEYS[1],tonumber(ARGV[2]));
return 1;
有朋友反馈目前Redis Set命令支持同时设置超时时间,无需 再写LUA脚本。我亲自试验了一下,确实如此。 实验Redis版本要求在2.6.12以后。命令和实验截图如下
set key1 value nx ex 10
锁误释放问题
为什么会出现锁误释放呢?
假如客户端1持锁时间超过了锁超时时间,redis key被过期删除。然后客户端2申请锁成功。而客户端1 在执行完成后,释放锁。此时会导致 客户端2 申请的锁被客户端1 释放。 如图
Client1RedisClient2Client3Client 1误释放了Client申请的锁,导致Client3 可申请锁成功,未起到防并发的目的setnx 申请锁执行业务逻辑超时锁过期被删除setnx 申请锁成功释放锁。setnx 抢占锁成功。
Client1RedisClient2Client3
Client 1误释放了Client2 申请的锁,导致Client3 可申请锁成功,未起到防并发的目的。由此可见,删除锁的时候不能盲目删除。删除前应该保证是自己申请的锁,才能释放。
如何实现呢?首先setnx 需要保存value。 value中记录了客户端信息,可以使用毫秒级时间戳、UUID或者 机器ip+线程名称等。(毫秒级时间戳足够用了,一般业务场景分布式锁争抢没那么严重)。
删除操作需要实现 Compare AND DELETE (CAD)原子命令。 redis没有提供,需要使用LUA脚本实现
if(redis.call('GET',KEYS[1]) ~= false ) then
local v = redis.call('GET', KEYS[1]);
if(v~= KEYS[2]) then
return -1;
end;
local res = redis.call('DEL', KEYS[1]);
if(res==1) then
return 1;
else
return -2;
end;
end;
return 0;
使用redis-cli加载lua脚本:
redis-cli script load "$(cat cad.lua)"
执行lua脚本,指定参数参数,传入KEY、Value
EVALSHA 304be2df131dda36b189882adef40856da7b0259 2 KEY VALUE
返回值说明:
1: 值相等,可以删除,且删除成功
0: 当 key1 不存在时
-1: value1 和key1对应的value不相等
-2: value1 和key1对应的value相等,但是DEL执行失败。因为redis lua原子化执行,之前已经判断key1存在,所以这种场景几乎不可能,但预留下
Value还有其他更重要的用途
分布式锁为什么会存Value,如果只看到持有锁超时场景,防止误删除是不够的。
回到一开始的问题,如果setnx阶段出现了超时,客户端是不清楚锁是否写入成功。需要重试一下,才能确认。(如果不重试就会出现锁漏释放,导致后续一直无法获取锁。)
超时后,可以get查询一下锁是否存在,如果存在就说明刚才加锁成功了吗?并不能,有可能是其他Client加的锁。此时就需要判断value是否一致,如果一致说明是自己获取成功的,否则说明是其他Client获取成功的。
Client1Redis防止超时,GET也要重试setnx key value1 获取锁获取锁超时GET KEYvalue一致则获取锁成功,否则获取锁失败。Client1Redis
总结
redis 加锁和释放锁都需要设置value,为了防止漏释放锁,更是为了保证加锁超时后,能查询确认上一次加锁是否成功。
到现在,我们线上的问题解决方案已经出来了,我们需要在加锁超时后,重新GET 确认是否加锁成功。
相关推荐
- redis 7.4.3更新!安全修复+性能优化全解析
-
一、Redis是什么?为什么选择它?Redis(RemoteDictionaryServer)是一款开源的高性能内存键值数据库,支持持久化、多数据结构(如字符串、哈希、列表等),广泛应用于缓存、消...
- C# 读写Redis数据库的简单例子
-
CSRedis是一个基于C#的Redis客户端库,它提供了与Redis服务器进行交互的功能。它是一个轻量级、高性能的库,易于使用和集成到C#应用程序中。您可以使用NuGet包管理器或使用以下命令行命令...
- 十年之重修Redis原理
-
弱小和无知并不是生存的障碍,傲慢才是。--------面试者总结Redis可能都用过,但是从来没有理解过,就像一个熟悉的陌生人,本文主要讲述了Redis基本类型的使用、数据结构、持久化、单线程模型...
- 高频L2行情数据Redis存储架构设计(含C++实现代码)
-
一、Redis核心设计原则内存高效:优化数据结构,减少内存占用低延迟访问:单次操作≤0.1ms响应时间数据完整性:完整存储所有L2字段实时订阅:支持多客户端实时数据推送持久化策略:RDB+AOF保障数...
- Magic-Boot开源引擎:零代码玩转企业级开发,效率暴涨!
-
一、项目介绍基于magic-api搭建的快速开发平台,前端采用Vue3+naive-ui最新版本搭建,依赖较少,运行速度快。对常用组件进行封装。利用Vue3的@vue/compiler-sfc单文...
- 项目不行简历拉胯?3招教你从面试陪跑逆袭大厂offer!
-
项目不行简历拉胯?3招教你从面试陪跑逆袭大厂offer!老铁们!是不是每次面试完都感觉自己像被大厂面试官婉拒的渣男?明明刷了三个月题库,背熟八股文,结果一被问项目就支支吾吾,简历写得像大学生课程设计?...
- 谷歌云平台:开发者部署超120个开源包
-
从国外相关报道了解,Google与Bitnami合作为Google云平台增加了一个新的功能,为了方便开发人员快捷部署程序,提供了120余款开源应用程序云平台的支持。这些应用程序其中包括了WordPre...
- 知名互联网公司和程序员都看好的数据库是什么?
-
2017年数据库领域的最大趋势是什么?什么是最热的数据处理技术?学什么数据库最有前途?程序员们普遍不喜欢的数据库是什么?本文都会一一揭秘。大数据时代,数据库的选择备受关注,此前本号就曾揭秘国内知名互联...
- 腾讯云发布云存储MongoDB服务
-
近日,著名安全专家兼Shodan搜索引擎的创建者JohnMatherly发现,目前至少有35000个受影响的MongoDB数据库暴露在互联网上,它们所包含的数据暴露在网络攻击风险之中。据估计,将近6...
- 已跪,Java全能笔记爆火,分布式/开源框架/微服务/性能调优全有
-
前言程序员,立之根本还是技术,一个程序员的好坏,虽然不能完全用技术强弱来判断,但是技术水平一定是基础,技术差的程序员只能CRUD,技术不深的程序员也成不了架构师。程序员对于技术的掌握,除了从了解-熟悉...
- 面试官:举个你解决冲突的例子?别怂!用这个套路……
-
面试官:举个你解决冲突的例子?别怂!用这个套路……最近收到粉丝私信,说被问到:团队技术方案有分歧怎么办?当场大脑宕机……兄弟!这不是送命题,是展示你情商+技术判断力的王炸题!今天教你们3招,用真实案例...
- 面试碰到MongoDB?莫慌,跟面试官这样吹MongoDB 复制集
-
推荐阅读:吊打MySQL:21性能优化实践+学习导图+55面试+笔记+20高频知识点阿里一线架构师分享的技术图谱,进阶加薪全靠它十面字节跳动,依旧空手而归,我该放弃吗?文末会分享一些MongoDB的学...
- SpringBoot集成扩展-访问NoSQL数据库之Redis和MongoDB!
-
与关系型数据库一样,SpringBoot也提供了对NoSQL数据库的集成扩展,如对Redis和MongoDB等数据库的操作。通过默认配置即可使用RedisTemplate和MongoTemplate...
- Java程序员找工作总卡项目关?
-
Java程序员找工作总卡项目关?3招教你用真实经历写出HR抢着要的简历!各位Java老哥,最近刷招聘软件是不是手都划酸了?简历投出去石沉大海,面试邀请却总在飞别人的简历?上周有个兄弟,13年经验投了5...
- Java多租户SaaS系统实现方案
-
嗯,用户问的是Java通过租户id实现的SaaS方案。首先,我需要理解用户的需求。SaaS,也就是软件即服务,通常是指多租户的架构,每个租户的数据需要隔离。用户可能想知道如何在Java中利用租户ID来...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)