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

相关推荐
敲代码的嘎仔8 小时前
用 Redis 合并写 + DelayQueue 延迟检测,把视频播放进度写库频率降到 1/60
java·开发语言·数据库·redis·缓存·音视频·高并发
程序猿乐锅11 小时前
【黑马点评 | 第一篇】从 Session 到 Redis+Token 的改造与拦截器实现
数据库·redis·缓存
野生技术架构师11 小时前
Redis Hot Key 与 Big Key:危害、发现与解决方案
数据库·redis·缓存
学渣超11 小时前
从一次早高峰数据库告警说起:你真的理解缓存该如何落地应用吗?
redis·后端·架构
程序员清风13 小时前
Python 操作 MySQL、Redis 与消息队列的完整实践
redis·python·mysql
momo061171 天前
Redis新手入门 -- 学习笔记
redis·后端
吃饱了得干活1 天前
Redisson 进阶实战:看门狗机制与分布式锁完全指南
java·redis·后端
我是苏苏2 天前
Redis开发09:Windows系统下主从复制的配置教程
数据库·redis·缓存
写后端的胖头鱼2 天前
【高频面试题】如何找出redis中的慢查询记录
redis·慢日志
彧azz2 天前
Redis 全面指南:从核心概念到高可用架构实战
数据库·redis·架构