Redis 进阶 -- 流水线与事务事务的安全性(ACID性质)

Posted CodeJiao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis 进阶 -- 流水线与事务事务的安全性(ACID性质)相关的知识,希望对你有一定的参考价值。

文章目录

1. 流水线

在一般情况下,用户每执行一个Redis命令,Redis客户端和Redis服务器就需要执行以下步骤:

  1. 客户端向服务器发送命令请求。
  2. 服务器接收命令请求,并执行用户指定的命令调用,然后产生相应的命令执行结果。
  3. 服务器向客户端返回命令的执行结果。
  4. 客户端接收命令的执行结果,并向用户进行展示。

与大多数网络程序一样,执行Redis命令所消耗的大部分时间都用在了发送命令请求和接收命令结果上面,Redis服务器处理一个命令请求通常只需要很短的时间。

Redis提供的流水线特性:这个特性允许客户端把任意多条Redis命令请求打包在一起,然后一次性地将它们全部发送给服务器,而服务器则会在流水线包含的所有命令请求都处理完毕之后,一次性地将它们的执行结果全部返回给客户端。

虽然Redis服务器提供了流水线特性,但这个特性还需要客户端支持才能使用。

流水线使用注意事项:

虽然Redis服务器并不会限制客户端在流水线中包含的命令数量,但是却会为客户端的输入缓冲区设置默认值为1GB的体积上限:当客户端发送的数据量超过这一限制时,Redis服务器将强制关闭该客户端。


2. 事务

流水线特性,可以把多条LPOP命令打包在一起发送,但不能保证所有命令都会被服务器执行:这是因为流水线只能保证多条命令会一起被发送至服务器,但它并不保证这些命令都会被服务器执行。

事务一种能够让服务器将多个命令打包起来一并执行的技术

  • 事务可以将多个命令打包成一个命令来执行,当事务成功执行时,事务中包含的所有命令都会被执行。
  • 相反,如果事务没有成功执行,那么它包含的所有命令都不会被执行。

2.1 MULTI:开启事务

用户可以通过执行MULTI命令来开启一个新的事务,这个命令在成功执行之后将返回OK。

在一般情况下,除了少数阻塞命令之外,用户输入客户端中的数据操作命令总是会立即执行。但是当一个客户端执行MULTI命令之后,它就进入了事务模式,这时用户输入的所有数据操作命令都不会立即执行,而是会按顺序放入一个事务队列中,等待事务执行时再统一执行。

语法:

示例:

以下代码就展示了在MULTI命令执行之后,将SET命令、SADD命令和RPUSH命令放入事务队列中的例子:

正如代码所示,服务器在把客户端发送的命令放入事务队列之后,会向客户端返回一个QUEUED作为结果。

说明:

复杂度:O(1)。


2.2 EXEC:执行事务

在使用MULTI命令开启事务并将任意多个命令放入事务队列之后,用户就可以通过执行EXEC命令来执行事务了。


当事务成功执行时,EXEC命令将返回一个列表作为结果,这个列表会按照命令的入队顺序依次包含各个命令的执行结果。

示例:

以下代码展示了一个事务从开始到执行的整个过程。


2.3 DISCARD:放弃事务

如果用户在开启事务之后,不想执行事务而是想放弃事务,那么只需要执行DISCARD命令即可。

语法:


DISCARD命令会清空事务队列中已有的所有命令,并让客户端退出事务模式,最后返回OK表示事务已被取消。

示例:

以下代码展示了一个使用DISCARD命令放弃事务的例子:

说明:

复杂度:O(N),其中N为事务队列包含的命令数量。


2.4 事务的安全性(ACID性质)

在对数据库的事务特性进行介绍时,人们一般都会根据数据库对ACID性质的支持程度去判断数据库的事务是否安全。

具体来说,Redis的事务总是具有ACID性质中的A、C、I性质:

  • 原子性(Atomic):如果事务成功执行,那么事务中包含的所有命令都会被执行;相反,如果事务执行失败,那么事务中包含的所有命令都不会被执行。
  • 一致性(Consistent):Redis服务器会对事务及其包含的命令进行检查,确保无论事务是否执行成功,事务本身都不会对数据库造成破坏。
  • 隔离性(Isolate):每个Redis客户端都拥有自己独立的事务队列,并且每个Redis事务都是独立执行的,不同事务之间不会互相干扰。

除此之外,当Redis服务器运行在特定的持久化模式之下时,Redis的事务也具有ACID性质中的D性质:

  • 持久性(Durable):当事务执行完毕时,它的结果将被存储在硬盘中,即使服务器在此之后停机,事务对数据库所做的修改也不会丢失。

2.5 事务对服务器的影响

因为事务在执行时会独占服务器,所以用户应该避免在事务中执行过多命令,更不要将一些需要进行大量计算的命令放入事务中,以免造成服务器阻塞。


2.6 流水线与事务

流水线与事务虽然在概念上有些相似,但是在作用上却并不相同:流水线的作用是将多个命令打包,然后一并发送至服务器,而事务的作用则是将多个命令打包,然后让服务器一并执行它们。

因为Redis的事务在EXEC命令执行之前并不会产生实际效果,所以很多Redis客户端都会使用流水线去包裹事务命令,并将入队的命令缓存在本地,等到用户输入EXEC命令之后,再将所有事务命令通过流水线一并发送至服务器,这样客户端在执行事务时就可以达到“打包发送,打包执行”的最优效果。


3. 带有乐观锁的事务

乐观锁是一种乐观思想,假定当前环境是读多写少,遇到并发写的概率比较低,读数据时认为别的线程不会正在进行修改(所以没有上锁)。写数据时,判断当前与期望 值是否相同,如果相同则进行更新(更新期间加锁,保证是原子性的)。

Java中的乐观锁CAS(Compare And Set),比较并替换,比较当前值(主内存中的值),与预期值(当前线程中的值,主内存中值的一份拷贝)是否一样,一样则更新,否则继续进行CAS操作。

如上图所示,乐观锁可以同时进行读操作,读的时候其他线程不能进行写操作

接下来的内容将对Redis的乐观锁机制进行介绍,并在之后给出一个使用乐观锁实现的正确的、具有身份验证功能的锁。


3.1 WATCH:对键进行监视

客户端可以通过执行WATCH命令,要求服务器对一个或多个数据库键进行监视,如果在客户端尝试执行事务之前,这些键的值发生了变化,那么服务器将拒绝执行客户端发送的事务,并向它返回一个空值(nil)。

语法:


与此相反,如果所有被监视的键都没有发生任何变化,那么服务器将会如常地执行客户端发送的事务。

通过同时使用WATCH命令和Redis事务,我们可以构建出一种针对被监视键的乐观锁机制,确保事务只会在被监视键没有发生任何变化的情况下执行,从而保证事务对被监视键的所有修改都是安全、正确和有效的。

示例:

下表展示了某个事务执行失败的具体原因:因为客户端A监视了user_id_counter键,而客户端B却在客户端A执行事务之前对该键进行了修改,所以服务器最终拒绝了客户端A的事务执行请求。

说明:

时间复杂度:O(N),其中N为被监视键的数量。


3.2 UNWATCH:取消对键的监视

客户端可以通过执行UNWATCH命令,取消对所有键的监视。


服务器在接收到客户端发送的UNWATCH命令之后,将不会再对之前WATCH命令指定的键实施监视,这些键也不会再对客户端发送的事务造成任何影响。

示例:

以下代码展示了一个UNWATCH命令的执行示例。

除了显式地执行UNWATCH命令之外,使用EXEC命令执行事务和使用DISCARD命令取消事务,同样会导致客户端撤销对所有键的监视,这是因为这两个命令在执行之后都会隐式地调用UNWATCH命令。

说明:

复杂度:O(N),其中N为被取消监视的键数量。


4. 重点回顾

  • 在通常情况下,程序需要执行的Redis命令越多,需要进行的网络通信次数也会越多,程序的执行速度也会变得越慢。通过使用Redis的流水线特性,程序可以一次把多个命令发送给Redis服务器,这可以将执行多个命令所需的网络通信次数从原来的N次降低为1次,从而使得程序的执行效率得到显著提升。
  • 通过使用Redis的事务特性,用户可能将多个命令打包成一个命令执行:当事务成功执行时,事务中包含的所有命令都会被执行;相反,如果事务执行失败,那么它包含的所有命令都不会被执行。
  • Redis事务总是具有ACID性质中的原子性、一致性和隔离性,至于是否具有持久性则取决于Redis使用的持久化模式。
  • 流水线与事务虽然在概念上有相似之处,但它们并不相等:流水线的作用是打包发送多条命令,而事务的作用则是打包执行多条命令。
  • 为了优化事务的执行效率,很多Redis客户端都会把待执行的事务命令缓存在本地,然后在用户执行EXEC命令时,通过流水线一次把所有事务命令发送至Redis服务器。
  • 通过同时使用WATCH命令和事务,用户可以构建一种乐观锁机制,这种机制可以确保事务只会在指定键没有发生任何变化的情况下执行。


以上是关于Redis 进阶 -- 流水线与事务事务的安全性(ACID性质)的主要内容,如果未能解决你的问题,请参考以下文章

redis实战笔记-第4章 数据安全与性能保障

Redis基本操作进阶篇--事务学习

Redis 应用进阶

Redis学习--Redis的事务与持久化

Redis高级进阶

Redis入门指南-笔记-进阶