【Redis】如何保证原子操作

需求:两个客户端同时对[key1]执行自增操作,不会相互影响

操作:下面两个客户端并发操作会导致[key1]输出结果与预期不一致

  1. [客户端一]读取[key1],值为[1]
  2. [客户端二]读取[key1],值为[1]
  3. [客户端一]将[key1]自增1,值为[2]
  4. [客户端二]将[key1]自增1,值为[2]
  5. [客户端一]输出[key1],值为[2]
  6. [客户端二]输出[key2],值为[2]

解决思路

  1. [客户端一]、[客户端二]的R(读)、M(自增)、W(写)三个操作作为一个原子操作执行
  2. [客户端]对RMW整个操作过程加锁,加锁期间其它客户端不能对[key1]执行写操作
  3. Lua脚本

思路一:单命令操作

1. 概念

  •     Redis 提供了 INCR/DECR/SETNX 命令,把RMW三个操作转变为一个原子操作
  •     Redis 是使用单线程串行处理客户端的请求来操作命令,所以当 Redis 执行某个命令操作时,其他命令是无法执行的,这相当于命令操作是互斥执行的

思路二:加锁

1. 概念

     加锁主要是将多客户端线程调用相同业务方法转换为串行化处理,比如多个客户端调用同一个方法对某个键自增(这里不考虑其它方法或业务会对该键同时执行自增操作)   

  •      调用SETNX命令对某个键进行加锁(如果获取锁则执行后续RMW操作,否则直接返回未获取锁提示)
  •      执行RMW业务操作
  •      调用DEL命令删除锁

2. 加锁风险一

  •      假如某个客户端在执行了SETNX命令加锁之后,在后面操作业务逻辑时发生了异常,没有执行 DEL 命令释放锁。
  •      该锁就会一直被这个客户端持有,其它客户端无法拿到锁,导致其它客户端无法执行后续操作。

     解决思路:给锁变量设置一个过期时间,到期自动释放锁

SET key value [EX seconds | PX milliseconds] [NX]

3. 加锁风险二

     如果客户端 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. 脚本

local current current = redis.call("incr",KEYS[1]) 
if tonumber(current) == 1 
then redis.call("expire",KEYS[1],60) 
end

4. 调用执行

redis-cli --eval lua.script keys , args

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值