我的编程空间,编程开发者的网络收藏夹
学习永远不晚

如何使用Redis实现电商系统的库存扣减

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

如何使用Redis实现电商系统的库存扣减

在日常开发中有很多地方都有类似扣减库存的操作,比如电商系统中的商品库存,抽奖系统中的奖品库存等。

解决方案

使用mysql数据库,使用一个字段来存储库存,每次扣减库存去更新这个字段。
还是使用数据库,但是将库存分层多份存到多条记录里面,扣减库存的时候路由一下,这样子增大了并发量,但是还是避免不了大量的去访问数据库来更新库存。
将库存放到redis使用redis的incrby特性来扣减库存。

分析

在上面的第一种和第二种方式都是基于数据来扣减库存。

基于数据库单库存

第一种方式在所有请求都会在这里等待锁,获取锁有去扣减库存。在并发量不高的情况下可以使用,但是一旦并发量大了就会有大量请求阻塞在这里,导致请求超时,进而整个系统雪崩;而且会频繁的去访问数据库,大量占用数据库资源,所以在并发高的情况下这种方式不适用。

基于数据库多库存

第二种方式其实是第一种方式的优化版本,在一定程度上提高了并发量,但是在还是会大量的对数据库做更新操作大量占用数据库资源。

基于数据库来实现扣减库存还存在的一些问题:

用数据库扣减库存的方式,扣减库存的操作必须在一条语句中执行,不能先selec在update,这样在并发下会出现超扣的情况。如:
update number set x=x-1 where x > 0

MySQL自身对于高并发的处理性能就会出现问题,一般来说,MySQL的处理性能会随着并发thread上升而上升,但是到了一定的并发度之后会出现明显的拐点,之后一路下降,最终甚至会比单thread的性能还要差。

当减库存和高并发碰到一起的时候,由于操作的库存数目在同一行,就会出现争抢InnoDB行锁的问题,导致出现互相等待甚至死锁,从而大大降低MySQL的处理性能,最终导致前端页面出现超时异常。

基于redis

针对上述问题的问题我们就有了第三种方案,将库存放到缓存,利用redis的incrby特性来扣减库存,解决了超扣和性能问题。但是一旦缓存丢失需要考虑恢复方案。比如抽奖系统扣奖品库存的时候,初始库存=总的库存数-已经发放的奖励数,但是如果是异步发奖,需要等到MQ消息消费完了才能重启redis初始化库存,否则也存在库存不一致的问题。

基于redis实现扣减库存的具体实现

我们使用redis的lua脚本来实现扣减库存
由于是分布式环境下所以还需要一个分布式锁来控制只能有一个服务去初始化库存
需要提供一个回调函数,在初始化库存的时候去调用这个函数获取初始化库存

初始化库存回调函数(IStockCallback )


public interface IStockCallback {

 
 int getStock();
}

扣减库存服务(StockService)


@Service
public class StockService {
    Logger logger = LoggerFactory.getLogger(StockService.class);

    
    public static final long UNINITIALIZED_STOCK = -3L;

    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    
    public static final String STOCK_LUA;

    static {
        
        StringBuilder sb = new StringBuilder();
        sb.append("if (redis.call('exists', KEYS[1]) == 1) then");
        sb.append("    local stock = tonumber(redis.call('get', KEYS[1]));");
        sb.append("    local num = tonumber(ARGV[1]);");
        sb.append("    if (stock == -1) then");
        sb.append("        return -1;");
        sb.append("    end;");
        sb.append("    if (stock >= num) then");
        sb.append("        return redis.call('incrby', KEYS[1], 0 - num);");
        sb.append("    end;");
        sb.append("    return -2;");
        sb.append("end;");
        sb.append("return -3;");
        STOCK_LUA = sb.toString();
    }

    
    public long stock(String key, long expire, int num, IStockCallback stockCallback) {
        long stock = stock(key, num);
        // 初始化库存
        if (stock == UNINITIALIZED_STOCK) {
            RedisLock redisLock = new RedisLock(redisTemplate, key);
            try {
                // 获取锁
                if (redisLock.tryLock()) {
                    // 双重验证,避免并发时重复回源到数据库
                    stock = stock(key, num);
                    if (stock == UNINITIALIZED_STOCK) {
                        // 获取初始化库存
                        final int initStock = stockCallback.getStock();
                        // 将库存设置到redis
                        redisTemplate.opsForValue().set(key, initStock, expire, TimeUnit.SECONDS);
                        // 调一次扣库存的操作
                        stock = stock(key, num);
                    }
                }
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            } finally {
                redisLock.unlock();
            }

        }
        return stock;
    }

    
    public long addStock(String key, int num) {

        return addStock(key, null, num);
    }

    
    public long addStock(String key, Long expire, int num) {
        boolean hasKey = redisTemplate.hasKey(key);
        // 判断key是否存在,存在就直接更新
        if (hasKey) {
            return redisTemplate.opsForValue().increment(key, num);
        }

        Assert.notNull(expire,"初始化库存失败,库存过期时间不能为null");
        RedisLock redisLock = new RedisLock(redisTemplate, key);
        try {
            if (redisLock.tryLock()) {
                // 获取到锁后再次判断一下是否有key
                hasKey = redisTemplate.hasKey(key);
                if (!hasKey) {
                    // 初始化库存
                    redisTemplate.opsForValue().set(key, num, expire, TimeUnit.SECONDS);
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
            redisLock.unlock();
        }

        return num;
    }

    
    public int getStock(String key) {
        Integer stock = (Integer) redisTemplate.opsForValue().get(key);
        return stock == null ? -1 : stock;
    }

    
    private Long stock(String key, int num) {
        // 脚本里的KEYS参数
        List<String> keys = new ArrayList<>();
        keys.add(key);
        // 脚本里的ARGV参数
        List<String> args = new ArrayList<>();
        args.add(Integer.toString(num));

        long result = redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                Object nativeConnection = connection.getNativeConnection();
                // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
                // 集群模式
                if (nativeConnection instanceof JedisCluster) {
                    return (Long) ((JedisCluster) nativeConnection).eval(STOCK_LUA, keys, args);
                }

                // 单机模式
                else if (nativeConnection instanceof Jedis) {
                    return (Long) ((Jedis) nativeConnection).eval(STOCK_LUA, keys, args);
                }
                return UNINITIALIZED_STOCK;
            }
        });
        return result;
    }

}

调用


@RestController
public class StockController {

    @Autowired
    private StockService stockService;

    @RequestMapping(value = "stock", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public Object stock() {
        // 商品ID
        long commodityId = 1;
        // 库存ID
        String redisKey = "redis_key:stock:" + commodityId;
        long stock = stockService.stock(redisKey, 60 * 60, 2, () -> initStock(commodityId));
        return stock >= 0;
    }

    
    private int initStock(long commodityId) {
        // TODO 这里做一些初始化库存的操作
        return 1000;
    }

    @RequestMapping(value = "getStock", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public Object getStock() {
        // 商品ID
        long commodityId = 1;
        // 库存ID
        String redisKey = "redis_key:stock:" + commodityId;

        return stockService.getStock(redisKey);
    }

    @RequestMapping(value = "addStock", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public Object addStock() {
        // 商品ID
        long commodityId = 2;
        // 库存ID
        String redisKey = "redis_key:stock:" + commodityId;

        return stockService.addStock(redisKey, 2);
    }
}

到此这篇关于如何使用Redis实现电商系统的库存扣减的文章就介绍到这了,更多相关Redis 库存扣减内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

如何使用Redis实现电商系统的库存扣减

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

如何使用Go语言和Redis开发电商后台系统

如何使用Go语言和Redis开发电商后台系统引言:随着电商行业的繁荣发展,电商后台系统成为了保障电商平台正常运营的重要组成部分。而使用Go语言和Redis来开发电商后台系统,则可以提供高效、稳定和可扩展的解决方案。本文将介绍如何使用Go语言
如何使用Go语言和Redis开发电商后台系统
2023-10-28

如何使用Go语言和Redis实现邮件系统

如何使用Go语言和Redis实现邮件系统在当今的互联网时代,邮件系统是人们进行私人和商务通信的重要工具之一。本文将介绍如何使用Go语言和Redis实现一个简单的邮件系统,并提供具体的代码示例。一、Go语言介绍Go语言是由Google公司开发
如何使用Go语言和Redis实现邮件系统
2023-10-28

如何使用Go语言和Redis实现推荐系统

如何使用Go语言和Redis实现推荐系统推荐系统是现代互联网平台中重要的一环,它帮助用户发现和获取感兴趣的信息。而Go语言和Redis是两个非常流行的工具,它们在实现推荐系统的过程中能够发挥重要作用。本文将介绍如何使用Go语言和Redis来
2023-10-27

基于Elasticsearch构建分布式电商搜索系统的实践(电商系统如何借助Elasticsearch实现分布式搜索?)

构建基于Elasticsearch的分布式电商搜索系统涉及以下实践:数据建模、索引创建和分片数据加载和查询处理相关性评分和缓存监控和管理Elasticsearch的优势包括:可扩展性、实时搜索、高可用性灵活性和可定制性、与其他系统的集成通过遵循这些实践,企业可以利用Elasticsearch的强大功能创建强大的搜索系统,提供快速且准确的搜索体验。
基于Elasticsearch构建分布式电商搜索系统的实践(电商系统如何借助Elasticsearch实现分布式搜索?)
2024-04-02

如何使用Go语言和Redis实现实时监控系统

如何使用 Go 语言和 Redis 实现实时监控系统引言:实时监控系统在今天的软件开发中扮演着重要的角色。它能够及时收集、分析和展示系统各项指标,帮助我们了解当前系统的运行状况,并且对系统进行及时调整和优化。本文将介绍如何使用 Go 语言和
2023-10-27

如何使用C++实现宠物商店信息管理系统

这篇文章将为大家详细讲解有关如何使用C++实现宠物商店信息管理系统,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。具体内容如下一、问题描述设计一个程序实现对小动物商店的简单管理,主要功能:宠物基本信息(编号
2023-06-29

如何使用Go语言和Redis实现在线投票系统

如何使用Go语言和Redis实现在线投票系统概述:在线投票系统是一个常见的应用场景,它可以用于各种场合,如选举、问卷调查、评选等。本文将介绍如何使用Go语言和Redis来实现一个简单的在线投票系统。我们将使用Go语言作为后端开发语言,Red
2023-10-26

如何使用Go语言和Redis实现在线考试系统

如何使用Go语言和Redis实现在线考试系统概述:在线考试系统是一种实现在线考试的应用程序。通过使用Go语言和Redis数据库,我们可以构建一个高效、可扩展和可靠的在线考试系统。本文将介绍如何使用Go语言和Redis来设计和实现一个基本的在
2023-10-26

如何使用Go语言和Redis实现酒店预订系统

如何使用Go语言和Redis实现酒店预订系统酒店预订系统是现代化酒店管理的核心组成部分之一。借助于Go语言和Redis,我们可以轻松地构建出一个高效且可靠的酒店预订系统。本文将介绍如何使用Go语言开发一个功能完善的酒店预订系统,并通过Red
2023-10-27

如何利用PHP开发买菜系统的商品库存预警功能?

如何利用PHP开发买菜系统的商品库存预警功能?随着互联网的快速发展,电子商务也逐渐成为人们购物的首选方式。其中,买菜系统作为一种便捷的购物方式,逐渐受到人们的青睐。买菜系统的商品库存预警功能对于商家来说尤为重要,它可以帮助商家及时了解商品库
如何利用PHP开发买菜系统的商品库存预警功能?
2023-11-01

如何实现shell脚本监控linux系统内存使用情况

本篇内容介绍了“如何实现shell脚本监控linux系统内存使用情况”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、安装linux下面的一
2023-06-09

如何利用PHP开发买菜系统的商品库存预订与提醒功能?

如何利用PHP开发买菜系统的商品库存预订与提醒功能?随着互联网的发展,人们的生活变得愈发便利。在线购物成为了大家的常态,不仅仅是购买衣物、电子产品等物品,现在甚至连菜都可以在线购买了。买菜系统的兴起给人们带来了很大的便利,然而,有时候买菜的
如何利用PHP开发买菜系统的商品库存预订与提醒功能?
2023-11-01

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录