Redis事务

一:Redis的事务

  Redis通过MULTIEXECDISCARD 和 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) 即检查然后设置的机制来实现事务的回滚特性,即被WATCHed的属性在执行事务之前会检查该属性值是否被其它线程改变,如果改变事务执行失败,否则继续。

通过例子分析:有如下业务

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<String>) 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<String>) con -> {
            Integer integer = new Integer(1);
            //预备数据
            con.set(key.getBytes(), "1".getBytes());
            //锁定key
            con.watch(key.getBytes());
            //开启事务
            con.multi();
            con.incr(key.getBytes());
            //执行事务
            List<Object> exec = con.exec();
            Assert.notNull(exec,String.format("数据key=%s已被修改,请确认", key));
            for (Object object : exec) {
                System.out.println(object);
            }
            return new String("OK");
        });
    }

 

 

 

参考:https://redis.io/topics/transactions

posted @ 2020-06-19 08:41  爱我-中华  阅读(237)  评论(0编辑  收藏  举报