redis事务详解-避免踏坑

redis事务

    • redis事务简介
    • redis事务流程
    • 实例代码
    • 补充说明

redis事务简介

redis是支持一定事务能力的NoSQL,在redis中使用事务,通常的命令组合是watch…multi…exe,也就是要在一个redis连接中执行多个命令,这是我们可以考虑使用SessionCallback接口来达到这个目的。

redis事务流程

redis事务详解-避免踏坑_第1张图片
如上redis事务执行流程,首先watch监控redis的一些键;multi命令开启事务,开启事务后,接下来redis命令不会马上被执行,而是存放在一个队列里,这点是需要注意的地方,也就是在这是我们执行一些返回数据的命令,redis也不会马上执行,所以此时调用redis命令,返回的结果都是null;exec命令意义在于执行事务,只是它在队列命令执行前会判断watch监控的redis的键的数据是否发生过变化(即使赋予与之前相同的值也会被认为是变化过),如果发生变化,则redis取消事务,队列中命令全部不执行,否则执行事务,队列中命令全部执行,这里注意如果执行队列中的某一个命令错误,而错误后面的命令依旧被执行,这就是redis事务和数据库事务的不一样,redis事务是先让命令进入队列,一开始并没有检测这个命令是否能成狗,只有在exec命令执行时才发现错误。为了克服这个问题,一般在执行redis事务前,严格的检查数据,避免这样的情况发生。

实例代码

public Object multi(){
        redisTemplate.opsForValue().set("key1","value1");
        //SessionCallback实现在同一连接下执行多个redis命令
        redisTemplate.execute(new SessionCallback() {
            @Nullable
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                //设置要监控的key
                operations.watch("key1");
                //开启事务,在exec命令执行前,全部都只是进入队列
                operations.multi();
                operations.opsForValue().set("key2","value2");
                //operations.opsForValue () .increment ("key1", 1); //--1--
                //取值将为null,因为redis只是把命令放入队列,并没执行
                Object value2=operations.opsForValue().get("key2");
                System.out.println("命令在队列,所以value为null【"+value2+"】");

                operations.opsForValue().set("key3","value3");
                //取值将为null,因为redis只是把命令放入队列,并没执行
                Object value3=operations.opsForValue().get("key3");
                System.out.println("命令在队列,所以value为null【"+value3+"】");

                //执行exec命令,将先判断key是否在监控后被修改过,如果是则不执行事务,否则就执行事务
                return operations.exec(); //--2--
            }
        });
        return "ok";

为了揭示 Redis 事务的特性,我们对这段代码做以下两种测试。

  • 先在 Redis 客户端清空 key2 和 key3 两个键的数据,然后在②处设置断点,在调试的环境下让请求达到断点,此时在 Redis上修改 keyl 的值,然后再跳过断点,在请求完成后在Redis上查询 key2 和 key3 值,可以发现 key2、key3 返回的值都为空(null),因为程序中先使得 Redis的watch命令监控了 key1 的值,而后的 multi 让之后的命令进入队列,而在 exec 方法运行前 我们修改了key1,根据 Redis 事务的规则,它在 exec 方法后会探测 key1是否被修改过,如 果没有则会执行事务,否则就取消事务, 所以 key2 和 key3 没有被保存到 Redis 服务器中。
  • 继续把 key2 和 key3 两个值清空,把①处的注释取消,让代码可以运行,因为 keyl 是一个 字符串,所以这里的代码是对字符串加一,这显然是不能运算的。同样地,我们运行这段代 码后,可以看到服务器抛出了异常,然后我们去 Redis 服务器查询 key2 和 key3,可以看到 它们已经有了值。注意,这就是 Redis 事务和数据库事务的不一样,对于 Redis 事务是先让 命令进入队列,所以一开始它并没有检测这个加一命令是否能够成功,只有在 exec 命令执 行的时候,才能发现错误,对于出错的命令 Redis 只是报出错误,而错误后面的命令依旧被 执行,所以 key2 和 key3 都存在数据,这就是 Redis 事务的特点,也是使用 Redis 事务需要 特别注意的地方。为了克服这个问题, 一般我们要在执行 Redis 事务前,严格地检查数据, 以避免这样的情况发生。

补充说明

redis在2.6版本后提供了Lua脚本的支持,而且在执行Lua脚本在redis中还具备原子性,所以在需要保证数据一致性的高并发环境中,我们也可以使用redis的Lua语言来保证数据的一致性,而且Lua脚本具备更加强大的运算功能,在高并发需要保证数据一致性时,Lua脚本方案比使用redis自身提供的事务要更好一些。

你可能感兴趣的:(reids,事务)