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

系列文章目录

一、SpringBoot连接MySQL数据库实例【tk.mybatis连接mysql数据库】

二、SpringBoot连接Redis与Redisson【代码】

三、SpringBoot整合WebSocket【代码】

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

五、SpringBoot整合Elasticsearch【代码示例】

文章目录


前言

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

一、准备工作

在redis中放入十件商品


二、不使用Lua

使用20个线程抢商品

java 复制代码
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("商品数量不足");
                }
            }
        });
    }
}

运行代码发现已经超出了十个人抢到了商品。

此时redis的存值已经变为了负数,出现了超卖的情况。


三、使用Lua

java 复制代码
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 + "未抢到商品");
                }
            }
        });
    }
}

依然使用20个线程抢商品,运行代码只有10个线程抢到了商品

此时redis中的存值为0

相关推荐
小毛驴8509 小时前
redis 如何持久化
数据库·redis·缓存
Kevinyu_16 小时前
Redisson
java·redis·缓存
代码老y17 小时前
Redis 生产实战 7×24:容量规划、性能调优、故障演练与成本治理 40 条军规
数据库·redis·缓存
sevevty-seven17 小时前
Redis 事务错误处理机制与开发应对策略
数据库·redis·log4j
武子康21 小时前
大数据-44 Redis 慢查询日志详解与性能优化实战指南
大数据·redis·后端
代码老y1 天前
从单线程到云原生:Redis 二十年演进全景与内在机理深剖
数据库·redis·云原生
啊基米德1 天前
lua(xlua)基础知识点记录二
c#·lua·xlua
hxdcxy1 天前
redis中间件
数据库·redis·中间件
laoliu19961 天前
GGE Lua 详细教程
开发语言·junit·lua