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

相关推荐
四谎真好看4 分钟前
Redis学习笔记(实战篇4)
redis·笔记·学习·学习笔记
dustcell.25 分钟前
企业级高可用电商平台实战项目设计
运维·redis·nginx·docker·web·lvs·haproxy
小白学大数据28 分钟前
效率翻倍:Scrapy-Redis 分布式全站爬虫并发优化进阶
redis·分布式·爬虫·scrapy
YDS8299 小时前
黑马点评 —— 分布式锁详解加源码剖析
java·spring boot·redis·分布式
小江的记录本10 小时前
【Redis】Redis全方位知识体系(附《Redis常用命令速查表(完整版)》)
java·数据库·redis·后端·python·spring·缓存
程序猿ZhangSir14 小时前
详解了解 Redis IO多路复用底层原理,Select,poll,epoll三者的区别?
数据库·redis·缓存
编程之升级打怪14 小时前
用Python语言实现简单的Redis缓冲数据库驱动库
redis·python
SadSunset14 小时前
第一章:Redis 入门介绍
数据库·redis·缓存
Arthas21717 小时前
互联网大厂Java面试实录:谢飞机的电商微服务之旅 - Spring Boot/Cloud/Redis/Kafka实战
spring boot·redis·spring cloud·微服务·kafka·java面试·电商
IAtlantiscsdn17 小时前
Redis面试题总结
数据库·redis·缓存