redis-使用-事物 什么是redis事物 执行图 非cas事物使用案例 CAS事物案例 银行转账例子

Redis的事务是下面4个命令来实现
1.multi,开启Redis的事务,置客户端为事务态。
2.exec,提交事务,执行从multi到此命令前的命令队列,置客户端为非事务态。
3.discard,取消事务,置客户端为非事务态。
4.watch,监视键值对,作用时如果事务提交exec时发现监视的监视对发生变化,事务将被取消。

redis事物和数据库事物不一样,可以理解成是一串命令的集合 要么一起执行 要么都不执行

执行图

redis-使用-事物
什么是redis事物
执行图
非cas事物使用案例
CAS事物案例
银行转账例子

非cas事物使用案例

正常执行

本地:0>multi //标识连接为事物连接
OK
本地:
0>get test //命令入队 QUEUED
本地:
0>set test2 1 //命令入队 QUEUED
本地:
0>set test 2 //命令入队 QUEUED 本地:0>get test //命令入队 QUEUED 本地:0>exec //执行命令 并重置连接状态为事物连接 1) 1 2) OK 3) OK 4) 2 本地:0>

放弃事物 

本地:0>get test //修改前test数据值
3

本地:0>get test2  //修改前test2数据值
4

本地:0>multi   //标识连接为事物连接
OK

本地:0>set test 30 //命令入队
QUEUED

本地:0>set test2 40 //命令入队
QUEUED

本地:0>discard  //丢失事物队列命令,并重置连接为非事物连接
OK

本地:0>get test  //test数据未发送改变
3

本地:0>get test2 //test2数据未发生改变
4

命令编辑错误提交事物

本地:0>get test  //test修改前的值
3

本地:0>get test2  //test修改前的值
4

本地:0>multi   //表示连接为事物连接
OK

本地:0>set test 3333  //修改test
QUEUED

本地:0>sett tset2 2 //编译错误,不是正确的命令格式
ERR unknown command `sett`, with args beginning with: `tset2`, `2`, 

本地:0>set test2 4444  //修改test2
QUEUED

本地:0>exec  //提交报错 并重置事物连接为非事物
EXECABORT Transaction discarded because of previous errors.

本地:0>get test  //test数据未修改
3

本地:0>get test2 //test2数据未修改
4

命令运行时错误 

本地:0>get test 
f

本地:0>get  test2
5

本地:0>get test3
2

本地:0>multi //开启事物
OK

本地:0>set  test2 6 //修改test2
QUEUED

本地:0>incr test //对值为f的执行+1操作
QUEUED

本地:0>set test3 8 //修改test3
QUEUED

本地:0>exec  
1) OK
2) ERR value is not an integer or out of range //除了修改test失败其他都修改成功
3) OK
本地:0>get test
f

本地:0>get test2
6

本地:0>get  test3
8

CAS事物案例

什么是CAS

参考:点击跳转

正常执行

本地:0>get test //监控的key值
f

本地:0>watch test //监控test key的值 也可以监控多个
OK

本地:0>multi //开启事物
OK

本地:0>set test2 1 //修改test2
QUEUED

本地:0>set test 1 //修改test1
QUEUED

本地:0>exec //提交事物 并自动取消watch监控
1) OK
2) OK
本地:0>get test //修改成功
1

本地:0>get test2 //成功
1

异常提交

本地:0>get test //test修改前的值
1

本地:0>get test2 //test2修改前的值
1

本地:0>watch test //监控test 也可以监控多个
OK

本地:0>multi //开启事物
OK

本地:0>set test 33 //修改test的值
QUEUED

本地:0>set test2 33 //修改test2的值
QUEUED

本地:0>exec //提交事物,提交之前我使用另外一个连接改成了555  所以返回null修改失败 并取消test监控


本地:0>get test  //另外一个连接 改的值
555

本地:0>get test2 //未修改成功
1

银行转账例子

   public static void main(String[] args)
            throws Exception {
        Jedis conn = new Jedis("127.0.0.1", 6379);
        //=====================删除历史测试数据======================
        //转入方
        String inputUserIdKey=String.format("user:%s", "1");
        //转出方
        String outputUserIdKey=String.format("user:%s", "2");
        conn.del(inputUserIdKey);
        conn.del(outputUserIdKey);
        //设置默认值
        conn.hset(outputUserIdKey,"money","100");
        conn.hset(inputUserIdKey,"money","50");
        boolean processStatus=false;
        int index=0;
        do {
            index++;//重试3次
            processStatus= transaction(conn, "1", "2", 100);
        }while (!processStatus&&index<=3);//cas重试
        //打印转出后金额
        System.out.println(processStatus?"转账成功!":"转账失败!");
        System.out.println("转出方余额:"+conn.hget(outputUserIdKey,"money"));
        System.out.println("转入方余额:"+conn.hget(inputUserIdKey,"money"));
    }

    public static boolean transaction(Jedis conn, String inputUserId, String outUserId, Integer tranMoney) {
        String outUserKey = String.format("user:%s", outUserId);
        String inputUserKey = String.format("user:%s", inputUserId);
        //监控转出方金额 防止金额变化 不足以扣除
        conn.watch(String.format("user:%s", outUserId));
        //分为单位
        Long money = Long.valueOf(conn.hget(outUserKey, "money"));
        if (money < tranMoney) {
            System.out.println("余额不足");
            return false;
        }
        Transaction transaction= conn.multi();
        transaction.hincrBy(outUserKey, "money", -money);
        transaction.hincrBy(inputUserKey, "money", money);
        List<Object> result=  transaction.exec();
        //如果监控值改变返回的是空集合
        return  result!=null&&result.size()>0;
    }