> 文章列表 > 3.redis-事务

3.redis-事务

3.redis-事务

01-Redis事务概述

  • 概述
    • Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化放到一个队列中按顺序地执行。事务 在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 不支持ACID
    • ①atomicity, 原子性, redis事务中的指令执行失败, 不影响后续指令执行;
    • ②durability, 持久性, redis数据存储在内存中, 可以使用AOF和RDB进行补充.

02-Redis事务指令

  • 语法

    #开启事务, 后续指令放入到事务队列中
    multi#提交事务, 执行事务队列中的指令, 结束事务
    exec#回滚事务, 清空事务队列中的指令,结束事务
    discard
    
  • 代码实现1: 成功

    • 3.redis-事务
  • 注意事项

    • exec指令执行之后可能会有多个结果, 要使用集合来封装.

03-Redis事务错误处理

  • ①编译期错误: 语法错误, 后续指令不能执行

    • 3.redis-事务
    • 3.redis-事务
  • ②运行时错误: 语法正确, 执行错误. 不影响后续指令执行.

    • 3.redis-事务

    • 3.redis-事务

04-Redis锁机制

  • 事务冲突问题

    • 3.redis-事务

    • 张三账户余额有10000元,他想在双11的时候抢购三款手机:mate20pro(价值1000元)、 mate30pro(价值5000元)、mate40pro(价值8000元),为了增加抢购成功率,他把账户给了另外两个 好朋友:李四、王五,让他们在双11帮忙抢购。结果,双11,他们三个人同时分别抢到了 mate20pro、mate30pro、mate40pro,然后他们都同时付了款…

  • 悲观锁

    • 3.redis-事务

    • 效率低

  • 乐观锁

    • 3.redis-事务
  • 总结

    • Redis采用CAS乐观锁.

05-Redis乐观锁

  • 代码实现
    • 3.redis-事务

06-秒杀案例需求分析及准备

  • 需求

    • 秒杀同一件商品(一件商品多个库存)
  • 分析

    • 3.redis-事务

    • ①商品库存

      • string类型, key=seckill:商品id:kc
    • ②秒杀成功者清单

      • set类型, key=seckill:商品id:usr
  • 准备

    set seckill:0101:kc 100
    

07-秒杀案例基本功能

  • 开发步骤

    • ①编写前端代码
    • ②编写表现层代码
    • ③编写SeckillUtils工具类
      • 执行秒杀
  • ①编写前端代码

    <!DOCTYPE html>
    <html lang="en">
    <head><meta charset="UTF-8"><title>首页</title><script src="script/vue.js"></script><script src="script/axios.js"></script>
    </head>
    <body>
    <div id="app"><button @click="handleSeckill">开始秒杀</button>
    </div>
    <script>Object.assign(window, Vue);const App = {setup() {const data = reactive({cid: "1001"})const handleSeckill = () => {axios({method:"post",url:"/doSeckill",params:{cid:data.cid}}).then((res) => {const {msg}=res.data;alert(msg);console.log(res.data);})}return {data,handleSeckill}}}Vue.createApp(App).mount('#app');
    </script>
    </body>
    </html>
    
  • ②编写表现层代码

    @Controller
    public class SeckillController {@PostMapping("/doSeckill/{cid}")@ResponseBodypublic ResultVO<Object> doSeckill(@PathVariable String cid){//执行秒杀String uid = UUID.randomUUID().toString().replace("-", "");ResultVO<Object> result= SeckillUtils.doSeckill(uid,cid);System.out.println("result = " + result);return result;}
    }
    
  • 编写jedis.properties配置问价

    # redis主机地址
    jedis.host=192.168.199.110
    # redis端口
    jedis.port=6379
    # 最大连接数
    jedis.maxTotal=1000
    # 控制一个pool最多有多少个状态为空闲的jedis实例
    jedis.maxIdle=100
    # 最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException
    jedis.maxWaitMillis=10000
    
  • ③编写ResultVo.java工具类

    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public class ResultVO<T> {private boolean flag;private String msg;private T data;
    }
    
  • ④编写JedisUtils工具类

    public class JedisUtils {private static JedisPool jedisPool;static {InputStream inputStream = JedisUtils.class.getClassLoader().getResourceAsStream("jedis.properties");Properties properties = new Properties();try {properties.load(inputStream);int maxTotal = Integer.valueOf(properties.getProperty("jedis.maxTotal"));int maxIdle = Integer.valueOf(properties.getProperty("jedis.maxIdle"));long maxWaitMillis = Integer.valueOf(properties.getProperty("jedis.maxWaitMillis"));String host = properties.getProperty("jedis.host");int port = Integer.valueOf(properties.getProperty("jedis.port"));//创建Jedis链接池对象JedisPoolConfig poolConfig = new JedisPoolConfig();poolConfig.setMaxTotal(maxTotal);poolConfig.setMaxIdle(maxIdle);poolConfig.setMaxWaitMillis(maxWaitMillis);jedisPool = new JedisPool(poolConfig,host,port);} catch (IOException e) {throw new RuntimeException(e);}}public static Jedis getJedis() {return jedisPool.getResource();}public static void close(Jedis jedis) {if ( null != jedis) {jedis.close();jedis = null;}}
    }
    
  • ③编写SeckillUtils工具类

    public class SeckillUtils {/* 执行秒杀* @param uid 用户id* @param cid 商品id* @return*/public static ResultVO<Object> doSeckill(String uid, String cid) {//1.生成库存key [string:存储简单类型] [set seckill:1001:kc 100]String kcKey="seckill:"+cid+":kc";System.out.println("kcKey = " + kcKey);//2.生成秒杀成功者清单key [set:存储多个结果且去重] [sadd seckill:1001:user user1 user2...]String userKey="seckill:"+cid+":user";//3.判断秒杀是否开始Jedis jedis = JedisUtils.getJedis();String kcStr = jedis.get(kcKey);System.out.println("kcStr = " + kcStr);if(kcStr==null){JedisUtils.close(jedis);return  new ResultVO<Object>(false,"秒杀未开始,敬请期待!",null);}//4.判断秒杀是否结束int kc = Integer.parseInt(kcStr);if(kc<=0){JedisUtils.close(jedis);return  new ResultVO<Object>(false,"秒杀已结束!",null);}//5.判断当前用户是否参加过秒杀if (jedis.sismember(userKey,uid)) {JedisUtils.close(jedis);return  new ResultVO<Object>(false,"不能重复参加秒杀活动!",null);}//6.开始秒杀//库存减一jedis.decr(kcKey);//记录秒杀成功的用户jedis.sadd(userKey,uid);JedisUtils.close(jedis);return  new ResultVO<Object>(false,"秒杀成功!",null);}
    }
    

08-ab工具模拟秒杀高并发

  • 超卖问题

    • 3.redis-事务

    • 有多个用户同时执行了秒杀操作,且都认为当前有库存,都执行了库存减一,所以就出现了超卖问 题。

    • 使用ab插件模拟高并发.

  • 开发步骤

    • ①安装ab插件
      • yum -y install httpd-tools
    • ②创建请求参数文件
      • /usr/local/data.txt
    • ③开始模拟高并发
  • ①安装ab插件

  • ②创建请求参数文件

    cid=1001&
    
  • ③开始模拟高并发

    ab -n 1000 -c 200 -p /usr/local/data.txt -T "application/x-www-form-urlencoded" 192.168.13.54:8080/doSeckill
    

09-秒杀案例解决超卖问题

  • 分析

    • 3.redis-事务
    • 利用乐观锁淘汰用户,解决超卖问题。
  • 代码实现

    /* 解决超卖问题*/
    public class SeckillUtils1 {/* 执行秒杀* @param uid 用户id* @param cid 商品id* @return*/public static ResultVO<Object> doSeckill(String uid, String cid) {//1.生成库存key [string:存储简单类型] [set seckill:1001:kc 100]String kcKey="seckill:"+cid+":kc";System.out.println("kcKey = " + kcKey);//2.生成秒杀成功者清单key [set:存储多个结果且去重] [sadd seckill:1001:user user1 user2...]String userKey="seckill:"+cid+":user";//3.判断秒杀是否开始Jedis jedis = JedisUtils.getJedis();//4.给库存key加上乐观锁jedis.watch(kcKey);String kcStr = jedis.get(kcKey);System.out.println("kcStr = " + kcStr);if(kcStr==null){JedisUtils.close(jedis);return  new ResultVO<Object>(false,"秒杀未开始,敬请期待!",null);}//4.判断秒杀是否结束int kc = Integer.parseInt(kcStr);if(kc<=0){JedisUtils.close(jedis);return  new ResultVO<Object>(false,"秒杀已结束!",null);}//5.判断当前用户是否参加过秒杀if (jedis.sismember(userKey,uid)) {JedisUtils.close(jedis);return  new ResultVO<Object>(false,"不能重复参加秒杀活动!",null);}//6.开始秒杀//开启事务Transaction multi = jedis.multi();//库存减一multi.decr(kcKey);//记录秒杀成功的用户multi.sadd(userKey,uid);//提交事务List<Object> result = multi.exec();//判断事务是否执行失败if ( result==null ||  result.size()==0) {JedisUtils.close(jedis);return  new ResultVO<Object>(false,"商品已被抢走!",null);}JedisUtils.close(jedis);return  new ResultVO<Object>(false,"秒杀成功!",null);}
    }
    

10-秒杀案例解决库存遗留问题

  • 概述

    • “ab -n 1000”,ab模拟高并发,意味着有1000个用户在参与秒杀,如果库存1000的话,商品绝对是 可以秒杀完的;
    • “ab -n 1000 -c 200”,重新解读下ab模拟高并发,1000次请求,最高会有200个请求是并发的,意味 着会有200个请求抢同一件商品,如果是这种情况,就会存在明明有1000个人抢,但是只被抢走5个商 品,就会出现库存遗留问题。
  • 概述

    • Lua是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的 函数,Lua并没有提供强大的库,一个完整的Lua解释器不过200k,所以Lua不适合作为开发独立应用 程序的语言,而是作为嵌入式脚本语言。
    • 将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。 提升性能。
    • LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以替代redis事务完成一些的操 作。
  • 开发步骤

    • ①修改SeckillUtils工具类
      • 引入LUA脚本
    • ②代码测试
  • ①修改SeckillUtils工具类

    /* LUA脚本解决库存遗留问题*/
    public class SeckillUtils {//将之前的多次请求,使用LUA脚本拼接成一次请求static String secKillScript = "local uid=KEYS[1];\\r\\n" +"local cid=KEYS[2];\\r\\n" +"local qtkey='seckill:'..cid..\\":kc\\";\\r\\n" +"local usersKey='seckill:'..cid..\\":usr\\";\\r\\n" +"local userExists=redis.call(\\"sismember\\",usersKey,uid);\\r\\n" +"if tonumber(userExists)==1 then \\r\\n" +"   return 2;\\r\\n" +"end\\r\\n" +"local num= redis.call(\\"get\\" ,qtkey);\\r\\n" +"if tonumber(num)<=0 then \\r\\n" +"   return 0;\\r\\n" +"else \\r\\n" +"   redis.call(\\"decr\\",qtkey);\\r\\n" +"   redis.call(\\"sadd\\",usersKey,uid);\\r\\n" +"end\\r\\n" +"return 1";public static ResultVO<Object> doSeckill(String uid, String prodid) throws IOException {Jedis jedis = JedisUtils.getJedis();String sha1 = jedis.scriptLoad(secKillScript);Object result = jedis.evalsha(sha1, 2, uid, prodid);String reString = String.valueOf(result);if ("0".equals(reString)) {System.out.println("失败!秒杀已经结束!");JedisUtils.close(jedis);return new ResultVO<Object>(false, "秒杀已经结束!", null);} else if ("1".equals(reString)) {System.out.println("抢购成功!!!!");} else if ("2".equals(reString)) {System.out.println("失败!您已经秒杀成功过了!把机会给别人吧!");JedisUtils.close(jedis);return new ResultVO<Object>(false, "不能重复秒杀!", null);} else {System.err.println("抢购异常!!");JedisUtils.close(jedis);return new ResultVO<Object>(false, "抢购异常!", null);}JedisUtils.close(jedis);return new ResultVO<Object>(true, "秒杀成功!", null);}
    }