原理
首先 Redis 是支持一定事务能力的 NoSQL,在 Redis 中使用事务,通常的命令组合是 watch… multi…exec,也就是要在一个 Redis 连接中执行多个命令,这时我们可以考虑使用 SessionCallback 接口来达到这个目的。 其中,watch 命令是可以监控 Redis 的一些键;multi 命令是开始事务,开始事务后,该客户端的命令不会马上被执行,而是存放在一个队列里,这点是需要注意的地方,也就是在这时我们执行一些返回数据的命令,Redis 也是不会马上执行的,而是把命令放到一个队列里,所以此时调用 Redis 的命令,结果都是返回 null,这是初学者容易犯的错误;exec 命令的意义在于执行事务,只是它在队列命令执行前会判断被 watch 监控的 Redis 的键的数据是否发生过变化(即使赋予与之前相同的值也会被认为是变化过),如果它认为发生了变化,那么 Redis 就会取消事务,否则就会执行事务,Redis 在执行事务时,要么全部执行,要么全部不执行,而且不会被其他客户端打断,这样就保证了 Redis 事务下数据的一致性。下图就是 Redis 事务执行的过程。 
实践
redis事务简单示例
@GetMapping("/exec")
public ResponseEntity exec() {
redisTemplate.opsForValue().set("key1", "张三");
List list = (List) redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations redisOperations) throws DataAccessException {
redisOperations.watch("key1");
redisOperations.multi();
redisOperations.opsForValue().set("key2", "Pete2");
redisOperations.opsForValue().set("key3", "Pete3");
return redisOperations.exec();
}
});
return ResponseEntity.ok(list);
}
上面是一个redis事务使用的简单案例 【1】其中设置key2和key3是在一个连接事务中,它们要么一起成功要么一起失败。 【2】redisOperations.exec();是事务执行的关键方法,执行 exec 命令,将先判别 key1 是否在监控后被修改过,如果是则不执行事务,否则就执行事务。 【3】场景测试:以debug方式执行该代码,并在redisOperations.exec();处打上断点,即先让exec()不执行,然后此时去修改redis服务器中key1的值,可以修改其中的value或者删除key都可以,此时即表示watch监听的数据发生了变化,接着断点放开,上面的代码执行未报错,但是key2和key3的值并没有完成写入。 【4】因为程序中先使得 Redis的 watch 命令监控了 key1 的值,而后的 multi 让之后的命令进入队列,而在 exec 方法运行前我们修改了 key1,根据 Redis 事务的规则,它在 exec 方法后会探测 key1 是否被修改过,如果没有则会执行事务,否则就取消事务,所以 key2 和 key3 没有被保存到 Redis 服务器中。 【5】上面的redisTemplate.execute执行结果返回是一个List,如果执行了则该命令为true,否则为空。
redis事务测试异常
@GetMapping("/testException")
public ResponseEntity testException() {
redisTemplate.opsForValue().set("key1", "张三");
List list = (List) redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations redisOperations) throws DataAccessException {
redisOperations.watch("key1");
redisOperations.multi();
redisOperations.opsForValue().set("key2", "Pete2");
redisOperations.opsForValue().increment("key1", 1);
redisOperations.opsForValue().set("key3", "Pete3");
return redisOperations.exec();
}
});
return ResponseEntity.ok(list);
}
上面示例中需要注意在于redisOperations.opsForValue().increment(“key1”, 1); 这里对一个字符串类型的值做了相加操作,这个是会抛出异常的,无法执行该操作。  同样地,我们运行这段代码后,可以看到服务器抛出了异常,然后我们去 Redis 服务器查询 key2 和 key3,可以看到它们已经有了值。  注意,这就是 Redis 事务和数据库事务的不一样,对于 Redis 事务是先让命令进入队列,所以一开始它并没有检测这个加一命令是否能够成功,只有在 exec 命令执行的时候,才能发现错误,对于出错的命令 Redis 只是报出错误,而错误后面的命令依旧被执行,所以 key2 和 key3 都存在数据,这就是 Redis 事务的特点,也是使用 Redis 事务需要特别注意的地方。为了克服这个问题,一般我们要在执行 Redis 事务前,严格地检查数据,以避免这样的情况发生。
|