一:Redis的事务
Redis通过MULTI, EXEC, DISCARD 和 WATCH 命令来实现事务的支持,通过它们我们可以一步操作执行一组命令,而且确保了两个重要的特征如下
1.所有的命令在一个事务中可确保顺序执行,切不会被其它线程打断(插入其它指令)。
2.确保所有的命令要么全部执行要么一个也不执行。注意并不能保证每个任务都执行成功,而且失败的命令也不能回滚事务。
二:使用方法
步骤1: 开启事务 MULTI
步骤2: 执行命令 command1 command2 ....
步骤3: 执行事务 EXEC 或 DISCARD
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR foo
QUEUED
127.0.0.1:6379> INCR bar
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 1
备注:exec返回一个数组对象,数组的每个对象顺序对应每个执行的命令
三:事务的失败分析
通常一个事务的失败原因主要可分为两类
类1:命令如队(queue)失败,即在EXEC命令调用之前失败,失败的原因多为命令-参数错误,或者是内存溢出
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> incr a b c
(error) ERR wrong number of arguments for 'incr' command
类2:EXEC命令调用之后失败。注意一组命令其中一个命令失败,并不会影响该命令后的其它命令的执行。
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set a abc
QUEUED
127.0.0.1:6379> lpop a
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
四:WATCH命令实现乐观锁
WATCH通过check-and-set (CAS) 即检查然后设置的机制来实现事务的回滚特性,即被WATCH
ed的属性在执行事务之前会检查该属性值是否被其它线程改变,如果改变事务执行失败,否则继续。
通过例子分析:有如下业务
val = GET mykey //步骤1:先获取属性值 若此时mykey的值为1
val = val + 1 //步骤2:给值加1
SET mykey $val //步骤3:重新赋值熟悉
若有2个线程同时执行以上代码,其结果mykey的值只能加1即值为2,(两个线程应该为3??)
我们可以通过WATCH命令监控mykey,即当其它或当前线程修改了mykey属性的值,事务执行失败
127.0.0.1:6379> set mykey 2
OK
127.0.0.1:6379> get mykey
"2"
127.0.0.1:6379> WATCH mykey #监控mykey
OK
127.0.0.1:6379> get mykey #查看mykey
"2"
127.0.0.1:6379> incr mykey #重新赋值mykey 加1
(integer) 3
127.0.0.1:6379> MULTI #开启事务
OK
127.0.0.1:6379> set mykey 4 #重新赋值mykey
QUEUED
127.0.0.1:6379> exec #执行事务
(nil) #执行失败
127.0.0.1:6379> get mykey
"3"
备注:官方文档上说当前连接对watched属性操作不会影响事务,好像不是,这里有对这个疑问的讨论https://github.com/antirez/redis-doc/issues/734
备注:UNWATCH指令,顾名思义即WATCH对立指令,即停止对某个属性的监控,exec和discard指令都会调用unwatch指令,我们也可以手动调用该指令。
思考:WATCH是什么?它是EXEC命令执行的条件,即被监控的属性没有被修改过为TURE,否则为FALSE
五:Redis脚本和事务
Redis script(Redis脚本)是事务定义的,所以任何在事务中做的事情你都可以通过脚本完成,而且脚本更快和简单
六:SpringBoot与Redis事务
/** * 设置string类型的数据 * * @param key 健 * @param value 值 * @return */ public String setStringKey(String key, String value) { return redisTemplate.execute((RedisCallback) con -> { //开启事务 con.multi(); con.set(key.getBytes(), value.getBytes()); //执行事务 con.exec(); return new String("OK"); }); } /** * 测试redis事务的乐观锁--watch * @param key 健 * @param value 值 * @return */ public String checkWatch(String key) { return redisTemplate.execute((RedisCallback ) con -> { Integer integer = new Integer(1); //预备数据 con.set(key.getBytes(), "1".getBytes()); //锁定key con.watch(key.getBytes()); //开启事务 con.multi(); con.incr(key.getBytes()); //执行事务 List
参考:https://redis.io/topics/transactions