Redis 事务

2021-07-12  本文已影响0人  SheHuan

相比关系型数据库中的事务模型,Redis 中事务要简单一些。Redis 中的事务不能保证原子性,也就是说,事务中某一个命令执行时出现异常不会影响其它命令的执行;Redis 中的事务具有隔离性,即当前事物可以不被其它事务打断,但没有隔离级别的概念。

一、事务基本概念

在 Redis 中,事务相关的常用命令如下:

下边我们结合Jedis来看看如何使用事务。

二、事务的基本操作

public class TransactionTest {
    public static void main(String[] args) {
        new TransactionTest().test1();
    }

    public void test1() {
        JedisPool jedisPool = new JedisPool("localhost", 6379);
        Jedis jedis = jedisPool.getResource();
        jedis.auth("shehuan");
        jedis.flushDB();
        // 开启事务
        Transaction tx = jedis.multi();
        try {
            // 命令入队
            tx.set("key1", "value1");
            tx.incr("key1");
            tx.set("key2", "10");
            tx.incr("key2");
            // 执行事务
            List<Object> results = tx.exec();
            // 查看事务执行结果
            results.forEach(r -> {
                System.out.println(r.toString());
            });
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            jedis.close();
        }
    }
}

从上边可以看出tx.incr("key1")命令虽然执行失败,但不影响其它命令,事务正常执行结束,这也验证了 Redis 中的事务不能保证原子性。

三、取消事务

public class TransactionTest {
    public static void main(String[] args) {
        new TransactionTest().test2();
    }

    public void test2() {
        JedisPool jedisPool = new JedisPool("localhost", 6379);
        Jedis jedis = jedisPool.getResource();
        jedis.auth("shehuan");
        jedis.flushDB();
        // 开启事务
        Transaction tx = jedis.multi();
        try {
            // 命令入队
            tx.set("key1", "value1");
            tx.set("key2", "10");
            // 制造异常
            int i = 1 / 0;
            // 执行事务
            List<Object> results = tx.exec();
            // 查看事务执行结果
            results.forEach(r -> {
                System.out.println(r.toString());
            });
        } catch (Exception e) {
            e.printStackTrace();
            // 取消事务
            tx.discard();
            System.out.println("key1=" + jedis.get("key1"));
            System.out.println("key2=" + jedis.get("key2"));
        } finally {
            jedis.close();
        }
    }
}

在事务执行前,我们手动制造了异常,这样事务就不会执行,捕获异常后再取消事务,如果有业务上的异常我们可以这样处理。注意,在事务执行过程中发生异常是无法被捕获的,文中第一个例子就说明了这一点。

四、watch 监控

有关watch命令的作用在前边已经介绍过了,下边通过一个简单的商品抢购例子看下具体的用法。

public class TransactionTest {
    public static void main(String[] args) {
        new TransactionTest().test3();
    }

    public void test3() {
        JedisPool jedisPool = new JedisPool("localhost", 6379);
        Jedis jedis = jedisPool.getResource();
        jedis.auth("shehuan");
        jedis.flushDB();

        // 设置商品库存为1000件
        jedis.set("stock", "1000");
        // 监控库存
        jedis.watch("stock");
        // 获取库存
        int stock = Integer.parseInt(jedis.get("stock"));
        // 如果库存大于购买数量
        if (stock > 10) {
            stock = stock - 10;
        } else {
            // 取消监控
            jedis.unwatch();
            return;
        }
        // 开启事务
        Transaction tx = jedis.multi();
        try {
            // 减扣库存
            tx.set("stock", String.valueOf(stock));
            // 执行事务
            List<Object> results = tx.exec();
            // 如果事务执行过程中发现监控的 key 对应的值发生改变,也就是库存在其它地方被修改,则事务的执行结果为 null
            if (results == null) {
                System.out.println("库存减扣失败!");
            } else {
                System.out.println("剩余库存:" + jedis.get("stock"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            jedis.close();
        }
    }
}

在执行事务代码行打上断点,然后运行程序:


在 Redis 客户端窗口修改库存:


现在释放断点,让程序继续执行,最终结果如下:


由于在程序监控库存后,我们又在客户端窗口修改了库存,导致事务执行时发现监控的 key 对应的值发生了变化,所以放弃执行事务中的命令,不会去减扣库存,此时事务的执行结果为null

正常情况下,如果我们不人工干预,则结果符合我们的预期:


上一篇 下一篇

猜你喜欢

热点阅读