问题:如果Java代码出现了异常,怎么对Redis进行回滚?
一次和朋友聊天聊到了这个问题,当时第一想法就是,try-catch异常,在catch里对之前插入到Redis的数据进行删除 *** 作。但是接下来又有一个问题:如果在删除时报错了怎么办?
什么是事务?学过关系型数据库的应该都知道,事务有一个ACID原则,即事务的四大特性:
- atomicity(原子性):一个事务是一个不可分割的工作单位,事务中包括的 *** 作要么都做,要么都不做
- consistency(一致性):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
- isolation(隔离性):一个事务内部的 *** 作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- durability(持久性):指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他 *** 作或故障不应该对其有任何影响。
上面的ACID指的是关系型数据库,但是Redis作为一个NoSQL(非关系型数据库),它的事务肯定是和关系型数据库的不一样。
Redis的事务很简单,简单到它的本质其实就是一个队列。流程如下:
需要说明一下,加入到队列中的Redis命令并不会执行,只有输入了EXEC(提交事务)的命令后,才会依次执行队列中的命令。
事务的三个阶段:
- 开始事务
- 命令入对
- 执行事务
- 使用multi命令,开启事务
- 输入redis命令
- 使用exec命令,提交事务
# 查看所有的Key 127.0.0.1:6379> keys * (empty list or set) # 开启事务 127.0.0.1:6379> multi OK # set name = zhangsan 127.0.0.1:6379> set name zhangsan QUEUED # set age = 18 127.0.0.1:6379> set age 18 QUEUED # 提交事务 127.0.0.1:6379> exec 1) OK 2) OK # 查看所有的Key 127.0.0.1:6379> keys * 1) "name" 2) "age"
可以看到,开启事务之前,什么数据都没有,只有执行EXEC(提交事务)命令后,才会依次执行被事务包含的命令,并打印OK。
Redis事务的“原子性”atomicity(原子性)的要求就是,要么全部执行,要么全部不执行。但是Redis的原子性却有所不同,当事务提交提交之后,会依次执行队列中的命令,如果队列中的其中一个命令执行出错,并不会影响到其他的命令。显然这个不满足原子性的条件,所以Redis事务不是原子性的。
# 查看所有的Key 127.0.0.1:6379> keys * (empty list or set) # 开启事务 127.0.0.1:6379> multi OK # set name = zhangsan 127.0.0.1:6379> set name zhangsan QUEUED # set age = 18 127.0.0.1:6379> set age 18 QUEUED # 将key为name的值+1 127.0.0.1:6379> incr name QUEUED # 将key为age的值+1 127.0.0.1:6379> incr age QUEUED # 提交事务 127.0.0.1:6379> exec 1) OK 2) OK 3) (error) ERR value is not an integer or out of range 4) (integer) 19 # 查看所有的Key 127.0.0.1:6379> keys * 1) "name" 2) "age" # get name 127.0.0.1:6379> get name "zhangsan" # get age 127.0.0.1:6379> get age "19" 127.0.0.1:6379>
可以看到,执行时报了(error) ERR value is not an integer or out of range错误,但是整体来看,只有incr age命令执行失败其它的都是执行成功 。所以可以看到,确实不符合原子性的要求。
注:Redis的单条命令是原子性的,但是事务不是原子性的。
Redis的事务“回滚”严格来说,Redis的事务是不能回滚的。Redis只提供了一个DISCARD命令,作用就是取消事务。前面提到过,Redis的事务本质就是一个队列,开启事务之后的所有命令都是被放在队列中,只有执行EXEC(提交事务)命令,才会依次执行队列中的命令。执行DISCARD命令时,会放弃队列中的所用命令,结束本次事务,所以严格来说,Redis的事务是不能回滚的,只能取消,且取消 *** 作需要在执行EXEC(提交事务)命令之前。
# 查看所有的Key 127.0.0.1:6379> keys * (empty list or set) # 开启事务 127.0.0.1:6379> multi OK # set name = zhangsan 127.0.0.1:6379> set name zhangsan QUEUED # 取消事务 127.0.0.1:6379> discard OK # 查看所有的Key 127.0.0.1:6379> keys * (empty list or set)
可以看到,开启事务之前什么数据都没有,执行DISCARD(取消事务)命令后,也是什么数都没插入。
Redis事务的WATCH和Unwatch命令WATCHWATCH:监视一个或者多个Key,如果在事务执行之前,被监控的Key的值如果被改变,则事务将会被打断。
Unwatch:放弃监控。
监视Name
# 查看所有的Key 127.0.0.1:6379> keys * (empty list or set) # 监视 key = name 127.0.0.1:6379> watch name OK # 开启事务 127.0.0.1:6379> multi OK # set name = zhangsan 127.0.0.1:6379> set name zhangsan QUEUED # set age = 18 127.0.0.1:6379> set age 18 QUEUED
创建一个新的绘画,修改name的值
# set name = list 127.0.0.1:6379> set name lisi OK
提交事务
# 提交事务 127.0.0.1:6379> exec (nil) # 查看所有的Key 127.0.0.1:6379> keys * 1) "name" # get name 127.0.0.1:6379> get name "lisi"
可以看到,和之前的不同,执行exec后返回的不是OK而是nil。同时 事务中设置的name和age的值也不存在,只有在另一个会话中set的name。可以得出结论:被监控的Key的值如果被改变,则整个事务将会执行失败。
Unwatch终端A
# 查看所有的Key 127.0.0.1:6379> keys * (empty list or set) # 监控 key = name 127.0.0.1:6379> watch name OK # 取消监视 127.0.0.1:6379> unwatch OK # 开启事务 127.0.0.1:6379> multi OK # set name = zhangsan 127.0.0.1:6379> set name zhangsan
终端B
# set name = list 127.0.0.1:6379> set name list OK
终端A
# set name wanger 127.0.0.1:6379> set name wanger QUEUED # 提交事务 127.0.0.1:6379> exec 1) OK 2) OK # 查看所有的Key 127.0.0.1:6379> keys * 1) "name" # get name 127.0.0.1:6379> get name "wanger"
可以看到,执行了unwatch命令后,在终端B修改name的值,不会出现整个事务失败的问题。
总结- Redis的单条命令支持原子性,但是事务不支持原子性。
- Redis的事务是不能回滚的,但是可以取消事务(放弃事务)。
- Redis事务有四个方法
- DISCARD:取消事务(放弃事务)
- EXEC:提交事务(执行事务)
- MULTI:开启事务
- UNWATCH:取消所有监视
- WATCH:监视1个或者多个Key
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)