Redis - 事务处理实践

Redis 事务简介

1. 概述

  • Redis采用乐观锁的方式进行事务控制,它使用watch命令监视给定的key,当exec(提交事务)的时候,如果监视的key从调用watch之后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key。注意watch的key是对整个连接有效的,如果连接断开,监视和事务都会被自动清除。当然exec,discard,unwatch命令都会清除连接中的所有监视。

2. 基本指令

  • redis进行事务控制时,通常是基于如下指令进行实现,例如:
    • multi :开启事务
    • exec:提交事务
    • discard:取消事务
    • watch:监控,如果监控的值发生改变,则提交事务时会失败
    • unwatch:取消监控
  • Redis保证一个事务中的所有命令要么都执行,要么都不执行(原子性)。如果在发送exec命令前客户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行。而一旦客户端发送了exec命令,所有的命令就都会被执行,即使此后客户端断线也没关系,因为Redis中已经记录了所有要执行的命令。

Redis 事务控制

3. exec提交事务

  • 例如:模拟转账,tony 500,jack 200,tony转给jack100。过程如下:
    127.0.0.1:6379> set tony 500
    OK
    127.0.0.1:6379> set jack 200
    OK
    127.0.0.1:6379> mget tony jack
    1) "500"
    2) "200"
    127.0.0.1:6379> multi #开启事务
    OK
    127.0.0.1:6379(TX)> decrby tony 100 #所有指令操作会进入到队列
    QUEUED
    127.0.0.1:6379(TX)> incrby jack 100
    QUEUED
    127.0.0.1:6379(TX)> mget tony jack
    QUEUED 
    127.0.0.1:6379(TX)> exec  #提交事务
    1) (integer) 400
    2) (integer) 300
    3) 1) "400"
       2) "300"
    127.0.0.1:6379> mget tony jack
    1) "400"
    2) "300"
    127.0.0.1:6379>
    
    

4. discard取消事务

  • Redis没有事务回滚,只有取消事务
    127.0.0.1:6379> mget tony jack
    1) "400"
    2) "300"
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379(TX)> incrby jack 100
    QUEUED
    127.0.0.1:6379(TX)> discard
    OK
    127.0.0.1:6379> get jack
    "300"
    127.0.0.1:6379> exec
    (error) ERR EXEC without MULTI
    127.0.0.1:6379> get jack
    "300"
    127.0.0.1:6379>
    
  • 当出现错误指令时,事务也会自动取消
    127.0.0.1:6379> mget tony jack
    1) "400"
    2) "300"
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379(TX)> incrby jack 100
    QUEUED
    127.0.0.1:6379(TX)> sadas
    (error) ERR unknown command `sadas`, with args beginning with:
    127.0.0.1:6379(TX)> get jack
    QUEUED
    127.0.0.1:6379(TX)> exec
    (error) EXECABORT Transaction discarded because of previous errors.
    127.0.0.1:6379> get jack
    "300"
    127.0.0.1:6379>
    

5. 秒杀抢票事务处理

  • 基于一个秒杀抢购案例,演示redis乐观锁方式,例如
    • 打开客户端1,执行如下操作:
      127.0.0.1:6379> set ticket 1
      OK
      127.0.0.1:6379> set money 0
      OK
      127.0.0.1:6379> watch ticket		#乐观锁,对值进行观察,改变则事务失败
      OK
      127.0.0.1:6379> multi				#开启事务
      OK
      127.0.0.1:6379> decr ticket
      QUEUED
      127.0.0.1:6379> incrby money 100
      QUEUED
      
    • 打开客户端2,执行如下操作,演示还没等客户端1提交事务,此时客户端2把票买到了。
      127.0.0.1:6379> get ticket
      "1"
      127.0.0.1:6379> decr ticket
      (integer) 0
      
    • 回到客户端1:提交事务,检查ticket的值
      127.0.0.1:6379> exec
      (nil) #执行事务,失败
      127.0.0.1:6379> get ticket
      “0”
      127.0.0.1:6379> unwatch #取消监控
      

6. Jedis 客户端事务操作

package com.jt;

import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

/**
 * @Author Sky-haohao
 * @Date 2021/10/11 20:05
 * @Version 1.0
 */
public class JedisTransactionTests {
    @Test
    public void testTransaction(){
        Jedis jedis = new Jedis("192.168.126.128",6379 );
        jedis.auth("baobao");
        jedis.set("jack", "300");
        jedis.set("rose", "500");
        //实现操作,rose转账100给Jack
        //开启事务
        Transaction transaction = jedis.multi();
        try {
            //执行业务操作
            transaction.decrBy("rose", 100);
            transaction.incrBy("jack",100);
            int n = 100/0; //模拟异常
            //提交事务
            transaction.exec();
        } catch (Exception e) {
            e.printStackTrace();
            //出现异常取消事务
            transaction.discard();
        }
        String jack = jedis.get("jack");
        String rose = jedis.get("rose");
        System.out.println("jack:"+jack);
        System.out.println("rose:"+rose);
        jedis.close();
    }
}

  • 运行结果: 可以发现出现异常,会取消事务
    java.lang.ArithmeticException: / by zero
    	at com.jt.JedisTransactionTests.testTransaction(JedisTransactionTests.java:26)
    	......
    	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
    jack:300
    rose:500
    

7. Jedis 客户端秒杀操作实践

package com.jt.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

import java.util.List;

/**
 * @Author Sky-haohao
 * @Date 2021/10/11 20:29
 * @Version 1.0
 *
 * Redis秒杀练习:
 *      模拟两个线程都去抢购同一张票(考虑乐观锁)
 * 基于Redis实现一个简单的多线程秒杀抢票操作
 * 这个操作中主要演示一下乐观锁的应用
 * 乐观锁:允许多个线程同时对一条记录进行修改,但只能有一个线程修改成功
 */
public class SecondsKillDemo02 {

    //定义抢票逻辑
    public static void secondsKillTickets(){
        //1.建立Redis连接
        Jedis jedis = new Jedis("192.168.126.128",6379 );
        jedis.auth("baobao");
        //2.监控Redis中的指定的key
        String ticket = jedis.get("ticket");
        if (ticket != null && "".equals(ticket) && Integer.valueOf(ticket)==0)
                throw new RuntimeException("余票不足");
        jedis.watch("ticket","money");
        //3.开启事务,并执行业务
        Transaction transaction = jedis.multi();
        try {
            transaction.decr("ticket");
            transaction.incrBy("money", 100);
            //4.提交事务
            List<Object> exec = transaction.exec();
            System.out.println("transaction ok");
            System.out.println(exec);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //5.取消监控
            jedis.unwatch();
            //6.释放资源
            jedis.close();
        }
    }

    public static void main(String[] args) {
        //1.定义Redis初始化数据
        Jedis jedis = new Jedis("192.168.126.128",6379 );
        jedis.auth("baobao");
        jedis.set("ticket", "1");
        jedis.set("money", "0");
        jedis.close();
        //2.创建多个线程,在线程中执行抢票操作
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //执行抢票
                secondsKillTickets();
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //执行抢票
                secondsKillTickets();
            }
        });
        //3.启动多个线程
        t1.start();
        t2.start();
    }
}
  • 运行结果
    transaction ok
    null
    transaction ok
    [0, 100]
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值