管道
- Redis 的管道特性由 Redis 客户端提供,客户端通过改变了读写的顺序带来性能的巨大提升。
- Redis 的管道命令让客户端通过对管道中的指令列表改变读写顺序就可以大幅节省 IO 时间。
- 管道中指令越多,效果越好。
- 管道的本质:操作系统内核从为套接字分配的缓冲区(发送缓冲 send buffer / 接收缓冲 recv buffer)中读写数据。
- 执行命令时,系统会调用 write 和 read 进行 IO 操作:
- write 操作只负责将数据写到本地操作系统内核的发送缓冲,如果发送缓冲满了,则等待缓冲相应就是写操作 IO 操作的真正耗时。
- read 操作只负责将数据从本地操作系统内核的接收缓冲中取出,如果接收缓冲是空的,则等待数据到来就是读操作 IO 操作的真正耗时。
- 对于管道来说,连续的 write 操作根本就没有耗时,之后第一个 read 操作会等待一个网络的来回开销,然后所有的响应消息就都已经回送到内核的读缓冲了,后续的 read 操作直接就可以从缓冲拿到结果,瞬间返回。
- 管道不仅减少了 RTT,同时也减少了 IO 调用次数(IO 调用涉及到用户态到内核态之间的切换)。
(Source: 被遗忘的站点)
(Source: 被遗忘的站点)
下面分别对比了传统模式、管道模式、事务批量执行模式在命令时序方面的差异,可以看到利用 批量执行
或 事务+管道
可以实现很大的性能优化:
(Source: Taswar BhattiTaswar Bhatti)
(Source: Taswar BhattiTaswar Bhatti)
(Source: Taswar BhattiTaswar Bhatti)
事务
-
Redis 的事务模型很不严格,这要求我们不能像使用关系数据库的事务一样来使用 Redis。
-
Redis 的事务命令分别是:
multi
:开始事务,相当于begin
exec
:执行事务,相当于commit
discard
:丢弃事务,不执行前序事务
-
Redis 的事务仅仅是满足了事务的「隔离性」,隔离性中的串行化——当前执行的事务有着不被其它事务打断的权利:
> multi # 开始事务 OK > set books iamastring QUEUED > incr books QUEUED > set poorman iamdesperate QUEUED > exec # 执行事务 1) OK 2) (error) ERR value is not an integer or out of range # 执行错误但会继续执行后续命令,并生效 3) OK > get books "iamastring" > get poorman "iamdesperate > get books (nil) > multi # 开始事务 OK > incr books QUEUED > incr books QUEUED > discard # 丢弃事务 OK > get books (nil)
-
使用 Redis 事务时通常需要执行较多的命令,一半会配合
管道
进行使用,以提高性能。 -
Redis 提供
watch
命令处理并发产生的竞争条件问题: 事务只能在所有被监视键都没有被修改的前提下执行,如不满足则不执行事务。 -
watch
命令是乐观锁,在watch
执行后,exec
执行前,如果被监视键的值改变,则事务执行失败。此时可重试事务操作,直到没有发生碰撞为止。大多数情况下,变量碰撞的情况很少,所以通常并不需要进行重试。WATCH mykey val = GET mykey val = val + 1 MULTI SET mykey $val EXEC # 如果 mykey 改变,则返回 null
-
Redis 不支持事务回滚
rollback
,原因如下:- Redis 是单线程服务,事务仅会因错误命令而执行失败。错误的命令应在开发的过程中被发现,而不应该出现在生产环境中。
- 实现回滚需要付出额外的开销和成本,这违背了 Redis 的设计哲学和生态。
延伸阅读: