redis中如何保证原子性操作
需求:两个客户端同时对[key1]执行自增操作,不会相互影响
操作:下面两个客户端并发操作会导致[key1]输出结果与预期不一致
- [客户端一]读取[key1],值为[1]
- [客户端二]读取[key1],值为[1]
- [客户端一]将[key1]自增1,值为[2]
- [客户端二]将[key1]自增1,值为[2]
- [客户端一]输出[key1],值为[2]
- [客户端二]输出[key2],值为[2]
解决思路
- [客户端一]、[客户端二]的R(读)、M(自增)、W(写)三个操作作为一个原子操作执行
- [客户端]对RMW整个操作过程加锁,加锁期间其它客户端不能对[key1]执行写操作
- Lua脚本
思路一:单命令操作
1. 概念
Redis 提供了 INCR/DECR/SETNX 命令,把RMW三个操作转变为一个原子操作 Redis 是使用单线程串行处理客户端的请求来操作命令,所以当 Redis 执行某个命令操作时,其他命令是无法执行的,这相当于命令操作是互斥执行的
思路二:使用锁
1. 概念
加锁主要是将多客户端线程调用相同业务方法转换为串行化处理,比如多个客户端调用同一个方法对某个键自增(这里不考虑其它方法或业务会对该键同时执行自增操作)
调用SETNX命令对某个键进行加锁(如果获取锁则执行后续RMW操作,否则直接返回未获取锁提示) 执行RMW业务操作 调用DEL命令删除锁
2. 加锁风险一
1 2 3 | 假如某个客户端在执行了SETNX命令加锁之后,在后面操作业务逻辑时发生了异常,没有执行 DEL 命令释放锁。 该锁就会一直被这个客户端持有,其它客户端无法拿到锁,导致其它客户端无法执行后续操作。 解决思路:给锁变量设置一个过期时间,到期自动释放锁 SET key value [EX seconds | PX milliseconds] [NX] |
3. 加锁风险二
1 2 3 4 5 | 如果客户端 A 执行了 SETNX 命令加锁后,客户端 B 执行 DEL 命令释放锁,此时,客户端 A 的锁就被误释放了。如果客户端 C 正好也在申请加锁,则可以成功获得锁。 解决思路:加锁操作时给每个客户端设置一个唯一值(比如UUID),唯一值可以用来标识当前操作的客户端。在释放锁操作时,客户端判断当前锁变量的值是否和唯一标识相等,只有在相等的情况下,才能释放锁。(同一客户端线程中加锁、释放锁) SET lock_key unique_value NX PX 10000 |
思路三:Lua脚本
1. 概念
多个操作写到一个 Lua 脚本中(Redis 会把整个 Lua 脚本作为一个整体执行,在执行的过程中不会被其他命令打断,从而保证了 Lua 脚本中操作的原子性)
2. 需求
限制所有客户端在一定时间范围内对某个方法(键)的访问次数。客户端 IP 作为 key,某个方法(键)的访问次数作为 value
3. 脚本
1 2 3 4 5 | local current current = redis.call( "incr" ,KEYS[ 1 ]) if tonumber(current) = = 1 then redis.call( "expire" ,KEYS[ 1 ], 60 ) end |
4. 调用执行
1 | redis - cli - - eval lua.script keys , args |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义