事务
关系型数据库通常会通过事务保证ACID,即原子性、隔离性、持久性、一致性,Redis虽然也有事务,但是没法完全支持事务的这几种特性,
Redis是单线程的,所以符合事务的隔离性,同时Redis具备备份功能,也符合持久性,但是一致性和原子性是没有的。
基本用法
关系型数据库的事务指令一般是begin(开始)、commit(提交)、rollback(回滚)三者。
begin();
try {
command1();
command2();
commit();
} catch (Exception e) {
rollback();
}
redis也有三个与之对应,multi(开始)、exec(执行)、discard(丢弃)。
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incr test
QUEUED
127.0.0.1:6379(TX)> incr test
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 1
2) (integer) 2
当事务multi开始时,之后的所有指令都将被存在服务端的一个事务队列中,最终服务端收到exec指令时,才开始执行整个事务队列,执行完毕后一次性返回所有的运行结果,
因为redis的单线程特性,不用担心事务指令队列执行时被其他指令打搅,所以redis事务是可以保证隔离性的。
下图是事务执行的完成过程,multi开启一个事务,服务端返回OK,后续的操作服务端都返回QUEUE,这意味着指令都被放入到队列中,等到exec时,服务端开始执行并返回结果。
redis事务没有原子性
事务的原子性是指事务要么完全成功,要么全部失败,而Redis是不支持事务原子性的可以用下面的demo演示,
通过结果可以看到,事务中最后一个指令执行失败了,因为data的值并不是一个数值,所以无法自增,根据原子性此时应该失败,但是下面的将data设置为hi依然执行了,并且事务结束后我们获取到的也是hi,
redis事务执行时出现执行失败后续的指令依然会得到执行,所以说redis事务是没有原子性的。
127.0.0.1:6379> get data
"nihao"
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set data hello
QUEUED
127.0.0.1:6379(TX)> incr data
QUEUED
127.0.0.1:6379(TX)> set data hi
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
127.0.0.1:6379> get data
"hi"
discard (放弃执行)
discard指令用于丢弃事务缓存的指令队列里的所有指令,当然要在exec指令前执行,可以看到下面的demo,discard后,meta依然是nil,并没有发生指令执行的情况。
127.0.0.1:6379> get meta
(nil)
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incr meta
QUEUED
127.0.0.1:6379(TX)> incr meta
QUEUED
127.0.0.1:6379(TX)> discard
OK
127.0.0.1:6379> get meta
(nil)
使用管道优化
Redis事务每次发送指令也会造成一次网络读写,当事务的指令较多,也会造成网络耗时的开销,所以一般事务可以搭配管道技术一起使用,节约网络开销,使程序更快。
watch指令
watch指令用在事务开始之前,监控一个变量,当事务开始执行时,发现该变量的值和watch时的值不一样,将会终止执行,并返回一个null给客户端。
一般业务场景中可以用做乐观锁,例如需要读取redis中的某个值在内存中做计算,最后把计算的值放回去,因为这个过程不是原子性的,很有可能读写过程中有别的线程把这个值改了并存到了redis中,这会造成混乱,
使用redis锁可以防止这个问题,但是分布式锁是悲观锁,如果并没有人在这期间修改该值,也造成了获取锁和释放锁的资源浪费,根据上面的描述watch比较适合做这个事情,
在事务开始前watch住该值,如果事务exec时发现该值没有改变,则正常执行,否则返回null告诉客户端执行失败,客户端做重试或其他业务逻辑处理。
需要注意的是redis进制在multi和exec执行watch,否则会出错 ERR WATCH inside MULTI is not allowed
。
在下方demo中,使用了watch来对redis中的一个key进行自增操作,最终的值正确为10,watch在其中配合事务做出了乐观锁的效果,颇有CAS的味道。
代码地址:https://github.com/qiaomengnan16/redis-demo/tree/main/redis-watch