• 使用redis+lua通过原子减解决超卖问题【示例】



    前言

    超卖,即在并发的情况下,所售商品数量大于商品的库存数量。在并发量大的情况下,用户请求同时到达,对数据库进行操作,在没有采取相应的处理的情况时从而导致出现超卖现象。

    一、准备工作

    在redis中放入十件商品

    在这里插入图片描述


    二、不使用Lua

    使用20个线程抢商品

    public void test () {
        ExecutorService service = Executors.newFixedThreadPool(20);
        for (int i = 0; i < 20; i++) {
            int finalI = i;
            service.execute(new Runnable() {
                @Override
                public void run() {
                    if (Integer.parseInt(String.valueOf(redisUtils.get("test"))) > 0) {
                        int execute = Integer.parseInt(String.valueOf(redisUtils.decr("test", 1)));
                        if (execute != 0) {
                            log.info("线程" + finalI + "抢到了商品!!!");
                        } else {
                            log.info("线程" + finalI + "未抢到商品");
                        }
                    } else {
                        log.info("商品数量不足");
                    }
                }
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述
    运行代码发现已经超出了十个人抢到了商品。
    此时redis的存值已经变为了负数,出现了超卖的情况。

    在这里插入图片描述


    三、使用Lua

    public void test_lua () {
        StringBuilder sb = new StringBuilder();
        sb.append("if (redis.call('exists', KEYS[1]) == 1) then");    // 判断key是否存在
        sb.append("    local stock = tonumber(redis.call('get', KEYS[1]));");   // 获取锁
        sb.append("    if (stock == -1) then");
        sb.append("        return 1;");
        sb.append("    end;");
        sb.append("    if (stock > 0) then");
        sb.append("        redis.call('decrby', KEYS[1], 1);");    // 商品数量减1
        sb.append("        return stock;");
        sb.append("    end;");
        sb.append("    return 0;");
        sb.append("end;");
        sb.append("return -1;");
        String STOCK_LUA = sb.toString();
    
        DefaultRedisScript<Long> objectDefaultRedisScript = new DefaultRedisScript<>();
        objectDefaultRedisScript.setScriptText(STOCK_LUA);
        objectDefaultRedisScript.setResultType(Long.class);
    
        ArrayList<String> keys = new ArrayList<>();     // 脚本中的KEYS参数
        keys.add("test");
    
        ExecutorService service = Executors.newFixedThreadPool(20);
        for (int i = 0; i < 20; i++) {
            int finalI = i;
            service.execute(new Runnable() {
                @Override
                public void run() {
                    int execute = Integer.parseInt(redisTemplate.execute(objectDefaultRedisScript, keys).toString());
                    if (execute != 0) {
                        log.info("线程" + finalI + "抢到了商品!!!");
                    } else {
                        log.info("线程" + finalI + "未抢到商品");
                    }
                }
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    依然使用20个线程抢商品,运行代码只有10个线程抢到了商品
    在这里插入图片描述
    此时redis中的存值为0
    在这里插入图片描述

  • 相关阅读:
    leetcode:滑动窗口----3. 无重复字符的最长子串
    基于SpringBoot的OCR识别服务端方案
    【Latex】模板设置及使用教程
    【电路笔记】-电源电压
    Linux内核移植之主频设置
    神经网络模型的工作原理,人脑神经网络模型
    Spark之【基础介绍】
    【牛客-剑指offer-数据结构篇】【图解】JZ18 删除链表的节点 Java实现
    IntelliJ IDEA中使用“Generate equals() and hashCode()“提供不同模板的区别
    小孩子学什么编程?
  • 原文地址:https://blog.csdn.net/weixin_49832841/article/details/132790936