• 使用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
    在这里插入图片描述

  • 相关阅读:
    Bytebase 2.7.0 - ​新增分支(Branching)功能
    毫米波雷达在检测、分割、深度估计等多个方向的近期工作及简要介绍
    代码随想录Day_55打卡
    基于STM32设计的室内环境监测系统(华为云IOT)_2023
    初始化一个Gin框架的Go-Web项目
    Zip Slip漏洞审计实战
    限时开源,一份“扭转乾坤”的与时俱进的1700页Java八股文
    pandas
    生成生动3D人头形象
    使用 Python 的高效相机流
  • 原文地址:https://blog.csdn.net/weixin_49832841/article/details/132790936