← 返回首页
Redis事务
发表时间:2024-02-26 08:58:42
Redis事务

Redis事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。

1.Redis事务

Redis 事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

注意:Redis 的事务不是原子性,但是Redi执行每一个命令都是原子性的。

事务一般都是为原子性而生,既然Redis事务没有原子性,那他存在的意义是什么?

redis事务的主要作用就是串联多个命令防止 别的命令插队。

2.Redis事务的错误处理

实例:

#情况1:组队中某个命令出现了错误报告,执行时整个队列中所有的命令都会被取消。
127.0.0.1:6379> keys *
1) "name"
2) "balance"
3) "debt"
127.0.0.1:6379> get balance
"80"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> increby debt 20
(error) ERR unknown command 'increby'
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get balance
"80"

#情况2:命令组队的过程中没有问题,执行中出现了错误会导致部分成功部分失败
127.0.0.1:6379> keys *
1) "balance"
2) "debt"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name zhangsan
QUEUED
127.0.0.1:6379> decrby balance 20
QUEUED
127.0.0.1:6379> incrby name 20 # incr是对数字类型的进行自增,而name存的是字符串
QUEUED
127.0.0.1:6379> exec
1) OK
2) (integer) 80
3) (error) ERR value is not an integer or out of range

3.watch监听

redis就是使用这种check-and-set乐观锁机制实现事务的。内部是通过watch来监听数据是否发生过修改。

注意:Redis 禁止在multi和exec之间执行watch指令,而必须在 multi 之前做好盯住关键变量,否则会出错。

实例:

窗口1执行以下命令:

127.0.0.1:6379> get balance
"80"
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby balance 20
QUEUED

#执行exec之前,启动窗口2执行命令。
127.0.0.1:6379> exec
(nil)

窗口2执行以下命令。

127.0.0.1:6379> get balance
"80"
127.0.0.1:6379> set balance 100
OK
127.0.0.1:6379>

我们发现窗口1里的事务执行是失败的,返回结果为nil 。

4.watch的应用场景

在秒杀系统中,如果有两个线程同时修改秒杀活动的库存数量,我们就需要使用watch监听数量的改变,避免属性数据不一致的情形。

使用Java代码来模拟这个需求,这里用的客户端是Lettuce:

    @Transactional(rollbackOn = Exception.class)
    @Test
    public void testChangeSeckillItemNumber1() {
        //测试一下redis的事务。
        try {
            //1.对'seckill100'键的秒杀活动上锁。
            redisTemplate.watch("seckill100");
            //2.开启事务
            //允许事务
            redisTemplate.setEnableTransactionSupport(true);
            //3.开启事务
            redisTemplate.multi();
            SeckillActivity seckillActivity = (SeckillActivity) redisUtil.get("seckill100");
            //修改库存为10000;
            seckillActivity.setNumber(10000);
            redisUtil.set("seckill100",seckillActivity);
            //当前线程休眠50秒;
            Thread.sleep(50*1000);
            //4.提交事务
            redisTemplate.exec();
        } catch (Exception ex) {
            ex.printStackTrace();
            //5.取消监听
            redisTemplate.unwatch();
        }

    }


    @Transactional(rollbackOn = Exception.class)
    @Test
    public void testChangeSeckillItemNumber2() {
        //测试一下redis的事务。
        try {
            //1.对'seckill100'键的秒杀活动上锁。
            redisTemplate.watch("seckill100");
            //2.开启事务
            redisTemplate.setEnableTransactionSupport(true);
            //3.开启事务
            redisTemplate.multi();

            SeckillActivity seckillActivity = (SeckillActivity) redisUtil.get("seckill100");
            //修改库存数量为500
            seckillActivity.setNumber(500);
            redisUtil.set("seckill100",seckillActivity);
            //4.提交事务
            redisTemplate.exec();
        } catch (Exception ex) {
            ex.printStackTrace();
            //5.取消监听
            redisTemplate.unwatch();
        }
    }

5.小结

简单一句话总结,Redis的事务不是原子性,但是Redi执行每一个命令都是原子性的,并且没有隔离级别。