27.Redis的原子操作

使用Redis的时候,可能会遇到并发操作,对于并发操作,我们将分别说常见的两种解决方案

一种是对Redis的锁操作,

一种是Redis的原子操作

我们这一次说的是Redis的原子操作,Redis中的原子操作可以利用两种实现

一种是Redis提供的原生原子操作

一种是将多个操作写到一个Lua脚本中,以原子性方式执行单个Lua脚本

首先是原生的原子操作

对于一些数据的增删改,最好能做到读取和修改放在一个操作里面,Redis提供了诸如INCR/DECR命令,将三个操作转换为一个原子操作,进行对应的增值和减值的操作,因为其本身就是个单原子操作,所以执行的时候本身就具有互斥性

然后是Lua脚本,Redis会将执行Lua脚本作为一个整体执行,因为Redis的单线程模型,具有原子性,但是因为Redis的单线程模型,导致执行的时候会降低Redis的并发性能,所以最好只将有并发控制的操作放在脚本中

对于Redis中的Lua脚本,可以使用EVAL命令执行

比如,我们由一个限流需求,要求每分钟访问次数不能超过20,第一次访问的时候,需要在value初始化后,设置过期时间,简单的代码如下

//获取ip对应的访问次数

current = GET(ip)

//如果超过访问次数超过20次,则报错

IF current != NULL AND current > 20 THEN

ERROR “exceed 20 accesses per second”

ELSE

//如果访问次数不足20次,增加一次访问计数

value = INCR(ip)

//如果是第一次访问,将键值对的过期时间设置为60s后

IF value == 1 THEN

EXPIRE(ip,60)

END

//执行其他操作

DO THINGS

END

即根据ip获取次数,然后增加一个值,如果第一次访问,则设置过期时间

虽然是INCR是原子操作,但是如果value是一个全局变量

那么会导致并发情况下,可能出现value无法为1的情况,以致于无法过期,导致用户达到20次访问后一直锁定

上面是使用INCR实现的版本,那么Lua实现版本如下

local current

current = redis.call(“incr”,KEYS[1])

if tonumber(current) == 1 then

redis.call(“expire”,KEYS[1],60)

end

对应的执行命令如下

redis-cli –eval lua.script keys,args

这样,就是原子性的执行了,及时是多线程的发送执行命令,同一时间也只有一个脚本执行

小结一下,本章我们主要讲解了单原子操作适用范围,分别说明了单命令操作和Lua脚本,

分别说了两者的局限性

最后一个小问题,Lua脚本中,有必要将读取ip访问数,也就是GET(ip)以及判断访问次数是否超过20加进去?

上面已经说了,需要避免将不需要做并发控制的操作写入脚本中

所以不应该放进去

而且在上面incr的代码中,很明显,value是一个线程内的变量就行,设置为全局变量是一个编程上的纰漏

发表评论

邮箱地址不会被公开。 必填项已用*标注