redis的事务是以命令multi开始,然后执行若干redis读写命令,最后以exec命令结束执行。整个过程中,在还没有执行 exec 命令前的所有 redis 的业务读写命令都没有真正的执行,只是放在了一个队列中,等 exec 命令执行时,客户端会一次性把队列中的这些命令发送给 redis 服务,批量执行。这种执行方式的另一种叫法称 “流水线操作” 或者 “管道操作”。
redis的事务可以让一个客户端在不被其它客户端打断的情况下执行多个命令。执行exec时, multi和exec之间包裹的所有命令一个接一个的被执行完毕之后,客户端才会处理其它客户端的命令。
看下面一段java代码:
public void testGetMulti(@RequestParam("key1") String key1,@RequestParam("key2") String key2){
redisService.multi();
String v1 = redisService.getValue(key1); // 1
String v2 = redisService.getValue(key2); // 2
System.out.println("### v1 ###"+v1); // 3
System.out.println("### v2 ###"+v2); // 4
List<Object> exec = redisService.exec();
System.out.println("### exec ### "+JSON.toJSONString(exec)); // 5
}
在multi命令和exec命令之间的redis读操作 1,2步骤的代码执行后,打印结果是空的(确保key1,key2之前是有值的)。因为使用了redis的事务命令,夹杂在multi和exec之间的redis操作都没有被客户端发送到redis服务去执行,而是暂存在一个队列中,等 exec 执行后,所有的命令才被发送到 redis 服务,批量执行,这样从而也减少了客户端与服务端的频繁socket通信,降低了开销,提高了应用端的性能。
如果应用中仅仅是为了减少redis的客户端与服务端的交互频次,上面的 multi 和 exec 结合使用是一种不错的方式。如果为了保证redis命令执行的原子性和读写安全,就要配合 watch 命令来确保读写安全了。
看如下代码:
public void testWatch(String key1,String value) {
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.watch(key1);// 1
redisTemplate.multi(); //2
redisTemplate.opsForValue().set(key1,value); //3
List<Object> exec = redisTemplate.exec(); //4
System.out.println("### exec ### " + JSON.toJSONString(exec));
}
在代码 1 处,使用watch监听key1,如果在执行 exec 的时候,发现监听的key1值被修改了,那么 multi和exec中的所有redis操作都中断,不会往下执行。watch监听key1,相当于获取了key1的值,再执行 exec 命令时,相当于进行 CAS 操作,进行 CAS 操作的时候,拿着旧值与key1的值比较,如果匹配上,则更新,如果没匹配上,则更新失败。