相比关系型数据库中的事务模型,Redis 中事务要简单一些。Redis 中的事务不能保证原子性,也就是说,事务中某一个命令执行时出现异常不会影响其它命令的执行;Redis 中的事务具有隔离性,即当前事物可以不被其它事务打断,但没有隔离级别的概念。
一、事务基本概念
在 Redis 中,事务相关的常用命令如下:
- 
multi:开启事务。
- 输入要执行的 Redis 命令,可以理解为命令入队列。
- 
exec:执行事务,会依次执行队列中的命令,各个命令的执行结果会在exec结束后统一返回。
- 
discard:取消事务,放弃执行队列中的命令,注意,已经开始执行的事务是无法取消的。
- 
watch:在通过multi开启事务之前,我们可以使用watch命令监控指定的 key,在事务执行之前,如果被监控的 key 对应的值被修改了,exec将放弃执行当前事务队列中的所有命令,这也是一种乐观锁实现。
- 
unwatch:是和watch对应的命令,用来取消对 key 的监控,但如果执行了exec或discard命令,则事务中所有被监控的 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。
正常情况下,如果我们不人工干预,则结果符合我们的预期:
 











网友评论