redis管道和事务

关于管道和事务的使用时机

背景

有这样一段代码

1
2
3
4
5
6
pipe = redis.pipeline();
pipe.multi();
pipe.incr("books");
pipe.excute();
pipe.close();

这里同时应用了管道事务,但是我认为有些多余。

管道

管道的作用是在于把多个命令合并成一次网络请求,交给redis执行,可以有效减少请求时间。

我做过测试,同样次数的请求,一条一条发送,需要35秒左右。5条合并发送一次,就只需要1.5秒左右。

由于redis响应请求是单线程的,所以管道内的请求不会被其他请求插队,可以保证其原子性

事务

redis事务很简单,使用multi开启,使用exec 提交,同时不会阻塞请求。按照书中的说法,其保证了当前执行的事务有着不被其他事务打断的权利。

关于redis事务是否具有原子性的问题上,我看到各个地方说法不太一致

  • 《redis深度历险:核心原理和应用实践》中通过一个例子证明事务没有原子性
  • 《redis使用手册》直接说redis具有ACID,没有佐证
  • 菜鸟教程的说法又体现了其没有原子性

我使用docker的redis:6.2.3-alpine 测试以下脚本

1
2
3
4
5
multi
set key1 22
incr notExistKey
exec

结果是

(error) EXECABORT Transaction discarded because of previous errors.

再执行

1
get key1

没有对应的值,说明至少6.2.3这个版本的事务是有原子性的

4.0.14-alpine中执行以下脚本

1
2
3
4
5
6
7
8
set key1 1
multi
set key2 2
hgetall key1
exec

get key2

exec结果如下

1
2
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value

get 能够拿到值,可见这个版本不具有原子性


为了控制变量,我又把这个命令在6.2.3上执行了一下,结果两个结果居然一样,key2 同样是有值的

这。。。我就不知道该说什么了。

只好求助网络了。


网上给了一个体现原子性的例子,就是在事务中执行一个不存在的命令(随便输入点什么)。

这样exec就是报错,而不执行。

这和我的理解不太一样,我原本理解是,redis事务命令入队时会检测命令是否合法,合法则入队,也就是返回QUEUED

现在看可能是命令不合法,会将事务置为出错,入不入队都不重要了,exec时会直接提示错误。

而我在4.x中执行的错误是一种使用错误,必须执行才能得知的。redis认为这种错误是程序员的错,不会为此大费周章,去做回滚之类的操作。这就是一些人说redis没有原子性的原因


再测试一下其他特性,按以下流程执行命令

clientAclientB
set key1 1
multi
get key1
set key1 2
get key1
set key1 3
get key1
exec
get key1

结果如下

C6288E27-C711-4DDD-A494-0D84267F9CA5.png

可以看出,事务的隔离级别类似于未提交读,

问题

如果按照以上说法,管道同样拥有不会被别的请求打断的功能,那事务还有必要吗?

回答

后来我在实现限流时,尝试在管道close之前,获取某条命令的执行结果作判断,结果报了异常。

细想一下,管道只会在close之后才发送请求,那么肯定是无法获取命令结果的。这就是管道的局限性

所以后来我使用脚本实现,就能保证操作的原子性,同时不过分浪费网络请求

当然也可以使用事务实现

还是没看出来事务有多大意义


顺便提一句,网上好多关于redis的博客其实就是把官方文档给翻译了一遍,还搞得跟自己研究出来的一样


redis管道和事务
http://blog.inkroom.cn/2021/06/02/2SE8EY3.html
作者
inkbox
发布于
2021年6月2日
许可协议