事务
Redis 的命令执行通常由主线程串行处理,因此在一条普通命令执行期间,不会插入其他客户端的命令。可以将 Redis 的命令处理过程简单理解为:服务器按照顺序处理待执行的命令。
Redis 也提供了类似于 MySQL 事务的功能,称为Redis 事务。Redis 事务可以将一组命令组织起来,在执行阶段按照提交顺序连续执行,并保证其他客户端的命令不会插入事务内部的执行过程。
Redis 事务的基本特点如下:
- 具有隔离执行特性: 事务中的命令通过 EXEC 按顺序连续执行。在事务执行期间,其他客户端的命令不会插入其中,因此其他客户端不会观察到事务执行过程中的中间状态。
- 没有 MySQL 那样可配置的事务隔离级别: Redis 事务不提供 读未提交、读已提交、可重复读、串行化 这类可配置的 SQL 事务隔离级别,但仍然具有事务执行阶段的隔离性。
- 不提供自动回滚: Redis 事务不保证所有命令全部成功或全部失败。如果某条命令在执行期间发生运行时错误,其他命令仍然会继续执行,已经完成的修改也不会自动撤销。
- 命令按顺序执行: Redis 会依次执行事务队列中的命令,在事务执行过程中不会插入其他客户端的命令。
需要注意: Redis 事务保证命令执行阶段的隔离性,但不支持因运行时错误而自动回滚整个事务。因此,Redis 事务的隔离执行特性不等同于,MySQL 事务的全有或全无原子性。
命令用法
Redis事务的开启命令为MULTI,结束为EXEC,在这中间为要执行的命令
127.0.0.1:6379>MULTI OK127.0.0.1:6379(TX)>setk1 v1 QUEUED127.0.0.1:6379(TX)>setk2 v2 QUEUED127.0.0.1:6379(TX)>EXEC OK OK在事务中的命令不会被立即执行,而是先被放到事务队列中,在执行EXEC时,Redis按照入队顺序执行命令,并返回每条命令的执行结果
若在事务中间想放弃执行,可以用DISCARD
127.0.0.1:6379>MULTI OK127.0.0.1:6379(TX)>setk1 v1 QUEUED127.0.0.1:6379(TX)>setk2 v2 QUEUED127.0.0.1:6379(TX)>DISCARD OK若事务中的命令有语法错误,会导致该事务中所有命令全部失败
127.0.0.1:6379>MULTI OK127.0.0.1:6379(TX)>setk1 v2 QUEUED127.0.0.1:6379(TX)>setvERR wrong number of argumentsfor'set'command127.0.0.1:6379(TX)>setk2 v2 QUEUED127.0.0.1:6379(TX)>EXEC EXECABORT Transaction discarded because of previous errors.如果错误不是语法错误,而是运行时错误,Redis 的事务也不支持回滚操作,只有错误命令出错,其他命令正常执行
127.0.0.1:6379>MULTI OK127.0.0.1:6379(TX)>setk1 v1 QUEUED127.0.0.1:6379(TX)>INCR email QUEUED127.0.0.1:6379(TX)>setk2 v2 QUEUED127.0.0.1:6379(TX)>EXEC OK ERR value is not an integer or out of range OKWATCH监控锁
悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。互斥锁就属于悲观锁
乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改,若没有被修改就更新内容,若被修改就失败。主要采用两种方式:版本号机制和CAS操作。
WATCH命令就是一种乐观锁机制,它用于监视指定键是否发生变化,只能和事务一起用
WATCH key[key...]执行WATCH后,Redis 会监视指定的一个或多个键:
- 如果从
WATCH到EXEC期间,被监视的键没有发生变化,事务可以正常执行。 - 如果被监视的键发生了变化,
EXEC会取消本次事务,返回空值(nil)。 WATCH不会阻止其他客户端修改这些键。
WATCH后没有修改:
127.0.0.1:6379> WATCH email OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379(TX)> set k1 v1 QUEUED 127.0.0.1:6379(TX)> set email valkyrieexusiai@outlook.com QUEUED 127.0.0.1:6379(TX)> set k2 v2 QUEUED 127.0.0.1:6379(TX)> EXEC OK OK OKWATCH后修改:
127.0.0.1:6379>WATCH email OK127.0.0.1:6379>MULTI OK127.0.0.1:6379(TX)>setk1 v1 QUEUED127.0.0.1:6379(TX)>setemail3204559698@qq.com QUEUED127.0.0.1:6379(TX)>setk2 v2 QUEUED127.0.0.1:6379(TX)>EXEC此时因为在EXEC前在另外一个客户端修改了email,导致事务被取消
WATCH必须要在MULTI之前开始监视,若在事务中开始监视会报错
127.0.0.1:6379(TX)>WATCH email ERR WATCH inside MULTI is not allowed若在开始事务前向取消监控,也可以用UNWATCH取消所有监控锁
管道
在redis-cli(客户端)发的每条命令,都需要通过网络传输先到redis-server(服务端),结果再通过网络传输返回给redis-cli。详细一点为以下两步:
- 客户端向服务端发送命令分四步(发送命令→命令排队→命令执行→返回结果),并监听Socket返回,通常以阻塞模式等待服务端响应。
- 服务端处理命令,并将结果返回给客户端
这两消耗的时间称为RTT(Round Trip Time),往返时间
因此,发送1000条命令,就会有1000次命令级别的RTT
问题在于:Redis执行单条命令可能只需要很短的时间,但客户端和服务器之间的网络通信却可能花费更长时间。
如果在一次 RTT 中传输多条命令,效率就可以得到提升。例如MSET,可以一次传输多条命令
但这种命令只能适用于一种类型,涉及到多个类型还需要多条命令
Redis的Pipeline允许客户端把多条命令连续发送给 Redis(一次发送),再集中读取这些命令的返回结果(一次返回),而不必每发送一条就等待一次响应。
在常见的同步 Redis 客户端调用方式下,如果连续执行多条命令,通常需要等待上一条命令的响应后,才会发送下一条命令
用法
例如,现在想一次性发送以下几条命令
MSET k1 v1 k2 v2 k3 v3 LPUSH list1 v1 v2 v3 HSET hash1 k1 v1 k2 v2 k3 v3 SET num1INCRBY num33可以用Linux的管道,将命令集文件传入客户端,在redis-cli加--pipe选项
[valexi@learn-linux ~]$catcmd.txt|redis-cli-a******--raw--pipeWarning: Using a password with'-a'or'-u'option on thecommandline interface may not be safe. All data transferred. Waitingforthe last reply... Last reply received from server. errors:0, replies:5Redis原生的批处理命令,例如MSET支持原子性,但管道不支持
单次管道传输的命令集不应该无限增大,通常应该控制每批命令的数量和总数据量,再分批执行。
例如,如果一次传入了100w条命令的命令集,会导致内存压力增大(客户端需要保存待发送的数据和接收的响应;Redis 也可能需要缓冲尚未被客户端读取的响应。),响应时间可能变长
Redis官方文档的示例为拆分成一批1w条命令的数据集,读取响应后发下一批
事务与管道的区别
虽然事务和管道的核心做法都是批量发送命令,但两者目的不同
- 事务目的为将一组命令作为事务队列集中执行;管道目的为提高批量命令的通信效率
- 事务执行期间保证不被其他客户端命令穿插;管道不保证
- 事务不保证绝对的原子性,不会因单条命令运行错误而回滚整个事务;管道没有原子性
- 事务不能减少RTT时间;管道可以