使用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

相关推荐
Seven971 天前
剑指offer-31、整数中1出现的次数
redis
AAA修煤气灶刘哥2 天前
别让Redis「歪脖子」!一次搞定数据倾斜与请求倾斜的捉妖记
redis·分布式·后端
christine-rr3 天前
linux常用命令(4)——压缩命令
linux·服务器·redis
凯子坚持 c3 天前
精通 Redis list:使用 redis-plus-plus 的现代 C++ 实践深度解析
c++·redis·list
weixin_456904273 天前
跨域(CORS)和缓存中间件(Redis)深度解析
redis·缓存·中间件
波波烤鸭3 天前
Redis 高可用实战源码解析(Sentinel + Cluster 整合应用)
数据库·redis·sentinel
MarkHard1233 天前
如何利用redis使用一个滑动窗口限流
数据库·redis·缓存
island13143 天前
【Redis#10】渐进式遍历 | 数据库管理 | redis_cli | RES
数据库·redis·bootstrap
心想事成的幸运大王3 天前
Redis的过期策略
数据库·redis·缓存
java搬砖工-苤-初心不变3 天前
基于 lua_shared_dict 的本地内存限流实现
开发语言·junit·lua