美文网首页
Redis 事务

Redis 事务

作者: SheHuan | 来源:发表于2021-07-12 20:55 被阅读0次

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

    一、事务基本概念

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

    • multi:开启事务。
    • 输入要执行的 Redis 命令,可以理解为命令入队列。
    • exec:执行事务,会依次执行队列中的命令,各个命令的执行结果会在exec结束后统一返回。
    • discard:取消事务,放弃执行队列中的命令,注意,已经开始执行的事务是无法取消的。
    • watch:在通过multi开启事务之前,我们可以使用watch命令监控指定的 key,在事务执行之前,如果被监控的 key 对应的值被修改了,exec将放弃执行当前事务队列中的所有命令,这也是一种乐观锁实现。
    • unwatch:是和watch对应的命令,用来取消对 key 的监控,但如果执行了execdiscard命令,则事务中所有被监控的 key 都将自动取消监控,则无需再手动执行该命令了。

    下边我们结合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

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


    相关文章

      网友评论

          本文标题:Redis 事务

          本文链接:https://www.haomeiwen.com/subject/pndfsltx.html