3.redis-事务

01-Redis事务概述

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

02-Redis事务指令

  • 语法

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

    • 3.redis-事务_第1张图片
  • 注意事项

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

03-Redis事务错误处理

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

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

    • 3.redis-事务_第4张图片

    • 3.redis-事务_第5张图片

04-Redis锁机制

  • 事务冲突问题

    • 3.redis-事务_第6张图片

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

  • 悲观锁

    • 3.redis-事务_第7张图片

    • 效率低

  • 乐观锁

    • 3.redis-事务_第8张图片
  • 总结

    • Redis采用CAS乐观锁.

05-Redis乐观锁

  • 代码实现
    • 3.redis-事务_第9张图片

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

  • 需求

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

    • 3.redis-事务_第10张图片

    • ①商品库存

      • 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}")
        @ResponseBody
        public 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-事务_第11张图片

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

    • 使用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-事务_第12张图片
    • 利用乐观锁淘汰用户,解决超卖问题。
  • 代码实现

    /**
     * 解决超卖问题
     */
    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);
    
        }
    }
    

你可能感兴趣的:(reids,redis,数据库,缓存)