前言
之前的文章中,我们分享了Redis内存管理和数据淘汰机制:
(三)Redis全体系:基础、高级特性与性能调优,从菜鸟到老鸟的秘籍!
今天,我们接着分享Redis的知识:执行批量命令和事务!(文末查看福利)
Pipelining
Redis提供许多批量操作的命令,如MSET/MGET/HMSET/HMGET等等,这些命令存在的意义是减少维护网络连接和传输数据所消耗的资源和时间。
例如连续使用5次SET命令设置5个不同的key,比起使用一次MSET命令设置5个不同的key,效果是一样的,但前者会消耗更多的RTT(Round Trip Time)时长,永远应优先使用后者。
然而,如果客户端要连续执行的多次操作无法通过Redis命令组合在一起,例如:
代码语言:javascript复制SET a "abc"
INCR b
HSET c name "hi"
此时便可以使用Redis提供的pipelining功能来实现在一次交互中执行多条命令。
使用pipelining时,只需要从客户端一次向Redis发送多条命令(以rn)分隔,Redis就会依次执行这些命令,并且把每个命令的返回按顺序组装在一起一次返回,比如:
代码语言:javascript复制$ (printf "PINGrnPINGrnPINGrn"; sleep 1) | nc localhost 6379
PONG
PONG
PONG
大部分的Redis客户端都对Pipelining提供支持,所以开发者通常并不需要自己手工拼装命令列表。
Pipelining的局限性
Pipelining只能用于执行连续且无相关性的命令,当某个命令的生成需要依赖于前一个命令的返回时,就无法使用Pipelining了。
通过Scripting功能,可以规避这一局限性
事务与Scripting
Pipelining能够让Redis在一次交互中处理多条命令,然而在一些场景下,我们可能需要在此基础上确保这一组命令是连续执行的。
比如获取当前累计的PV数并将其清0
代码语言:javascript复制> GET vCount
12384
> SET vCount 0
OK
如果在GET和SET命令之间插进来一个INCR vCount,就会使客户端拿到的vCount不准确。
Redis的事务可以确保复数命令执行时的原子性。也就是说Redis能够保证:一个事务中的一组命令是绝对连续执行的,在这些命令执行完成之前,绝对不会有来自于其他连接的其他命令插进去执行。
通过MULTI和EXEC命令来把这两个命令加入一个事务中:
代码语言:javascript复制> MULTI
OK
> GET vCount
QUEUED
> SET vCount 0
QUEUED
> EXEC
1) 12384
2) OK
Redis在接收到MULTI命令后便会开启一个事务,这之后的所有读写命令都会保存在队列中但并不执行,直到接收到EXEC命令后,Redis会把队列中的所有命令连续顺序执行,并以数组形式返回每个命令的返回结果。
可以使用DISCARD命令放弃当前的事务,将保存的命令队列清空。
需要注意的是,Redis事务不支持回滚。
如果一个事务中的命令出现了语法错误,大部分客户端驱动会返回错误,2.6.5版本以上的Redis也会在执行EXEC时检查队列中的命令是否存在语法错误,如果存在,则会自动放弃事务并返回错误。
但如果一个事务中的命令有非语法类的错误(比如对String执行HSET操作),无论客户端驱动还是Redis都无法在真正执行这条命令之前发现,所以事务中的所有命令仍然会被依次执行。在这种情况下,会出现一个事务中部分命令成功部分命令失败的情况,然而与RDBMS不同,Redis不提供事务回滚的功能,所以只能通过其他方法进行数据的回滚。
通过事务实现CAS
Redis提供了WATCH命令与事务搭配使用,实现CAS乐观锁的机制。
假设要实现将某个商品的状态改为已售:
代码语言:javascript复制if(exec(HGET stock:1001 state) == "in stock")
exec(HSET stock:1001 state "sold");
这一伪代码执行时,无法确保并发安全性,有可能多个客户端都获取到了"in stock"的状态,导致一个库存被售卖多次。
使用WATCH命令和事务可以解决这一问题:
代码语言:javascript复制exec(WATCH stock:1001);
if(exec(HGET stock:1001 state) == "in stock") {
exec(MULTI);
exec(HSET stock:1001 state "sold");
exec(EXEC);
}
WATCH的机制是:在事务EXEC命令执行时,Redis会检查被WATCH的key,只有被WATCH的key从WATCH起始时至今没有发生过变更,EXEC才会被执行。如果WATCH的key在WATCH命令到EXEC命令之间发生过变化,则EXEC命令会返回失败。
Scripting
通过EVAL与EVALSHA命令,可以让Redis执行LUA脚本。
这就类似于RDBMS的存储过程一样,可以把客户端与Redis之间密集的读/写交互放在服务端进行,避免过多的数据交互,提升性能。
Scripting功能是作为事务功能的替代者诞生的,事务提供的所有能力Scripting都可以做到。Redis官方推荐使用LUA Script来代替事务,前者的效率和便利性都超过了事务。
关于Scripting的具体使用,本文不做详细介绍,请参考官方文档 https://redis.io/commands/eval