Redis事务

事务的作用是为了确保多个连续的操作的原子性,Redis也支持事务,但与MySQL不同,Redis的事务模型并不严格,使用前还是需要对其特性准确把握,避免误用。
【基本用法】
Redis中与事务相关的指令分别是multi,exec,discard,watch,unwatch。
multi表示事务开始,类似begin。在multi提交之后,所有的指令会被加载到一个queue中,而不是立即执行,当exec提交之后,之前提交到queue中的指令会被依次执行。而如果在exec之前提交了discard指令,那么queue中的指令就会被丢弃。而watch可以实现一个乐观锁。
如图,展示了在multi提交指令后,exec执行之前,其它的客户端无法读取books中的数据:

【不严格的事务模型】
当我们提交了一批指令,放在queue并提交了exec执行时,redis可以保证queue中指令的串行,不被其它客户端指令所影响,但是如果当中某个指令执行结果错误,redis是不会进行回滚操作的。
这其实和pipeline很像,但是与pipeline的区别在于,pipeline中的指令不能保证与其它客户端提交的指令“隔离”,也就是pipeline中的指令执行期间,其它客户端的指令也会穿插执行;而exec提交后,queue中的指令是具有“隔离性”的,其它不在queue中的指令是不会执行的。即Redis的事务保证不被其它指令和事务打断的权力。
【事务一般和pipeline结合使用】
Redis是个单线程模型,所以每个单独的指令在执行时天然就是原子性的,不会被其它指令影响到。所以涉及事务操作的,一般都是一批指令一起执行,为了减少客户端等待的RTT时间,事务往往和管道结合使用。

public class App {
    public static void main(String[] args) {
        Jedis jedis = RedisClientUtil.getJedisClient();
        Pipeline pipelined = jedis.pipelined();
        pipelined.multi();
        for (int i = 0; i < 10; i++) {
            pipelined.set("key" + i, "value" + i);
        }
        Response<List<Object>> response = pipelined.exec();
        pipelined.close();
        List<Object> objects = response.get();
        System.out.println("objects.toString() = " + objects.toString());
    }
}

执行结果如图:

这样就可以保证pipeline中的指令可以依次执行,且不被其他客户端提交的指令插队。
【watch实现乐观锁】
使用pipeline加事务机制可以保证我们提交的指令可以排他顺序执行,但如果操作过程需要用到指令无法完成的操作,必须在内存中处理怎么办呢?
Redis提供了一种watch机制,它就是一种乐观锁。使用思路如下:
在multi之前用watch指令盯住一个或者多个关键变量,当事务执行时,也就是服务器收到了exec指令要开始顺序执行缓存的事务队列时,Redis会检查关键变量自watch之后是否被修改了(包括当前事务所在的客户端)。如果关键变量被修改过,那么exec指令就会返回NULL回复告知客户端事务执行失败。
是不是很像MySQL中用于对比版本号的version:

select version as queryVersion from table;
update table set status=newStatus,version=queryVersion+1 where version=queryVersion;

这里的watch其实就很像MySQL中用于保证版本的version=queryVersion,在前面的盯住变量以保证事务操作发生于变量发生变化之前。
注意Redis禁止在multi和exec之间进行watch,必须在multi之前就用watch盯住变量。
【实验-一个乐观锁的实验】
对于我们需要比较的version,可以先进行watch,保证后续的操作都是value不变的前提下进行的。
watch住之后,可以对这个value进行一次对比,如果对比发现已经不是之前的值,可以直接放弃后续的操作。如果值没有异常,那么此后的操作直到exec执行,都可以保证这个事务队列的操作是基于这个变量不变的前提执行的。如果变量发生变化,exec会返回一个null。看以几个例子:
首先,在set一个key,value之后,外部客户端对其进行修改,watch住之后,对比value发现已经变化,直接返回:

第二次,我们在watch住之后,exec之前,将value值修改,此时exec返回了null,于是我们知道这个double失败了:

第三次,没有其他客户端修改key的value:

原文地址:https://www.cnblogs.com/bruceChan0018/p/15712979.html