1.管道(Pipelining)
Redis管道可以将多条命令所需的网络通信次数从N此降低为1次,大幅度了减少了客户端在与Redis服务器网络通信耗费的时间,使程序执行效率得到大幅度提升。一般情况下,客户端每执行一次命令都会向Redis服务器发起一次网络请求,服务器接收命令并执行请求过来的命令,然后产生相应的命令执行结果,服务器再向客户端返回命令的执行结果,客户端接收命令执行结果并向用户展示。当执行的命令越来越多时,所需的网络通信次数也会越来越多,从而影响程序的执行效率。Redis提供了管道技术(又称流水线),流水线特性允许客户端把任意多条Redis命令请求打包在一起,然后一次性地将所有命令全部发送给服务器,服务器则会在流水线包含的所有命令请求执行结束后,一次性将它们的执行结果全部返回给客户端。
注意:虽然Redis不会限制客户端在流水线中包含的命令数量,但是却会为客户端的输入缓冲区设置默认值为1GB的体积上限,当客户端发送的数据量超过这一上限时,Redis服务器则强制关闭该客户端。建议使用流水线时不要把大量命令或体积非常大的命令放到同一个流水线中执行。另外其他客户端也带有隐含的缓冲区大小限制,如果在使用流水线过程中,发现流水线命令没有被执行,很有可能是因为程序触碰到了客户端内置的缓冲区大小限制,建议分割流水线包含的命令再执行。
# 进入redis的src目录启动redis-cli
redis-cli
# 先存储两个数据
mset name 'zxp' sex 'man'
exit
# 执行多个命令
(printf 'get name\n get sex\n') | nc localhost 6379
# $3
# zxp
# $3
# man
2.事务
MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。事务可以一次执行多个命令, 并且带有以下两个重要的保证:
- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
EXEC 命令负责触发并执行事务中的所有命令:
- 如果客户端在使用 MULTI 开启了一个事务之后,却因为断线而没有成功执行 EXEC ,那么事务中的所有命令都不会被执行。
- 另一方面,如果客户端成功在开启事务之后执行 EXEC ,那么事务中的所有命令都会被执行。
当使用 AOF 方式做持久化的时候, Redis 会使用单个 write(2) 命令将事务写入到磁盘中。
然而,如果 Redis 服务器因为某些原因被管理员杀死,或者遇上某种硬件故障,那么可能只有部分事务命令会被成功写入到磁盘中。
如果 Redis 在重新启动时发现 AOF 文件出了这样的问题,那么它会退出,并汇报一个错误。
使用redis-check-aof
程序可以修复这一问题:它会移除 AOF 文件中不完整事务的信息,确保服务器可以顺利启动。
从 2.2 版本开始,Redis 还可以通过乐观锁(optimistic lock)实现 CAS (check-and-set)操作。
2.1 事务相关的命令
- MUlTI命令用于开启事务,它总是返回OK,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当 EXEC命令被调用时,所有队列中的命令才会被执行。
- EXEC命令用于执行事务。EXEC 命令的回复是一个数组,数组中的每个元素都是执行事务中的命令所产生的回复。 其中,回复元素的先后顺序和命令发送的先后顺序一致。当客户端处于事务状态时, 所有传入的命令都会返回一个内容为
QUEUED
的状态回复(status reply),这些被入队的命令将在 EXEC 命令被调用时执行。 - 当执行 DISCARD 命令时,事务会被放弃,事务队列会被清空,并且客户端会从事务状态中退出。 ```shell multi # 打印OK multi # 打印err,ERR MULTI calls can not be nested(multi不能多层嵌套),但是可以使用discard命令取消执行事务
例子1:开启事务并执行事务
multi # 打印:OK set name “zxp” # 打印:QUEUED set sex “man” # 打印:QUEUED exec
打印结果:
1) OK
2) OK
例子2:开启事务取消事务,multi命令和discard命令必须成套使用
multi # 打印:OK get name # 打印:QUEUED get sex # 打印:QUEUED discard # 打印:OK get name # 打印:”zxp”
<a name="D2WEV"></a>
#### 2.2 事务中的错误
使用事务过程可能会出现以下两种错误:<br />1.事务在执行EXEC命令之前入队的命令发生错误,比如说,命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用 `maxmemory` 设置了最大内存限制的话)。<br />2.命令可能在 [EXEC](http://www.redis.cn/commands/exec.html) 调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。
```shell
# 情况1:执行exec命令之前入队错误
multi # 打印:OK,开启事务
get name "zxp" # 故意写错get命令,打印:ERR wrong number of arguments for 'get' command
exec # 打印:EXECABORT Transaction discarded because of previous errors(由于之前的错误,事务被丢失)
# 情况2:执行exec命令之后的错误
multi # 打印:OK,开启事务
set name "zxp" # 打印:QUEUED
srem name zxp # 打印:QUEUED,故意用set的命令操作key
exec
# 执行结果:
# 1) OK
# 2)(error) WRONGTYPE Operation against a key holding the wrong kind of value
get name # 打印:"zxp"
通过情况2可以看出Redis并不具备传统数据库事务的回滚功能,Redis当使用事务功能时,执行EXEC命令发生错误时,Redis并没有对它们进行特别处理:即使事务中有某个/某些命令在执行时产生了错误,事务中的其他命令仍然会继续执行。Redis事务不支持传统数据库事务回滚功能。
2.3 WATCH
WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。被 WATCH 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回nil-reply来表示事务已经失败。WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。被 WATCH 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回nil-reply来表示事务已经失败。例如下面的例子(需要开启2个redis-cli测试)
# watch语法:watch key1[key2...]
# 客户端A
set user_id 100
watch user_id # 打印:OK
multi # 打印:OK,开启事务
set uuid 123123 # 随便执行个命令
# 客户端B,假设此时客户端B修改了Watch监听的user_id
incr user_id # 打印:101
# 客户端A,提交事务
exec # 打印:(nil),说明客户端A的事务失效了
客户端A通过WATCH命令监听了user_id,但客户端B在客户端执行exec(执行事务执行)命令之前修改了user_id,当客户端执行exec命令时服务器最终拒绝了客户端A的事务执行请求。
UNWATCH命令可以取消对所有的key的监视,除了显式的执行UNWATCH命令外,使用EXEC命令执行事务和使用DISCARD取消事务都会导致客户端撤销对所有key的监视,这是因为执行EXEC和DISCARD命令之后都会隐式的调用UNWATCH命令。
3.总结
Redis事务一次可以执行多个命令,Redis的事务具有两个特征,第一事务是一个隔离的操作,事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。第二事务是一个原子操作,要么事务中的命令全部执行,要么事务中的命令全部不执行。
Redis使用MULTI命令开启事务,使用MULTI命令客户端可以继续向服务器发送任意多条命令,这些命令不会被立即执行,而是被放到一个队列中,当执行EXEC命令时队列中所有的命令才会被执行。
Redis使用EXEC执行事务,使用DISCARD命令可以丢弃事务,事务队列会被清空,并且客户端会从事务状态中退出。
Redis管道与Redis事务区别在于:虽然管道和事务都可以包含多个命令,但流水线的作用是将多个命令打包,然后一并发送至服务器,而事务的作用是将多个命令打包,然后让服务器一并执行它们。可以使用流水线包裹事务这样既可得到流水线降低网络开销的优点,又可以获得Redis事务ACID的特性。
Redis 中的脚本本身就是一种事务,所以任何在事务里可以完成的事,在脚本里面也能完成。 并且一般来说, 使用脚本要来得更简单,并且速度更快。