库存服务优化思路

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 库存可以以数量为单元,假如有5台手机,那么库存就是 5 ,卖掉一台,库存就减 1 变成 4 。这种以数量为单位进行的库存计算相对比较简单,在架构设计中无论你优化DB中的SQL,还是存储结构,或者引入Redis做缓存都比较好设计和实现。

很多业务系统构架中都有库存服务


  • 库存可以以数量为单元,假如有5台手机,那么库存就是 5 ,卖掉一台,库存就减 1 变成 4 。这种以数量为单位进行的库存计算相对比较简单,在架构设计中无论你优化DB中的SQL,还是存储结构,或者引入Redis做缓存都比较好设计和实现。
  • 有些库存不是以数量为单位的,比如酒店房间,这是可以重复利用的,是以天为单位的,行话叫“间夜”,理论上如果某间房间一年365天都可用,那么一年就有365个库存,一天就一个。A客人1月1号占了,B客人1月1号就不能占。当然,还有钟点房,时间粒度更细,这个先不讨论,后面会涉及。
  • 还有些业务的库存是以更细粒度为单位的,比如小时。租车业务就是以小时为单位进行库存占用的,假设我们只有一辆车,A客户8:00-10:00租用,B客户可以 11:00-15:00租用。


以下的库存优化思路是针对租车这种细粒度的占用单位的


笔者几年前参与过租车业务中库存系统的改造升级,当时的库存服务主要的问题有两个

  • 没有形成独立的服务,与其他服务耦合在一个“全家桶”式的系统中
  • 与库存相关的业务提供的RPC服务响应较慢,而又由于库存是核心服务,所以库存一慢,各相关子系统都会拖慢


对于第一个问题,相对比较好解决,我们将库存服务独立出来,包括数据库。问题在于第二个,其实慢的原因也很明显,就是对于库存的计算完全依赖数据库,利用SQL与java代码进行计算,当然重头戏在DB,当时采用的数据库是SQL Server,幸亏是SQL Server,如果是My SQL的话可能性能撑不到我们对它进行改造的时候。随着数据量的增加,整个库存服务在完全依赖DB的情况下,服务的查询速度较慢,是秒的量级。


当时的改造先进行了一轮谨慎的尝试,即对业务进行整体重新梳理、对现存SQL,java代码进行全面优化。优化的结果并不理想,虽然这个过程让业务更清晰,也发现了些遗留问题和BUG,但是并没有解决慢的根本问题。


在第一轮的基础上想过用缓存进行处理的方案,但由于单位粒度太细,缓存的设计方案复杂又不好落地,并没有采用。在QPS和TPS趋于平稳的情况下,库存系统的问题并没有被逐渐放大,反而似乎没有到大家忍受不了的地步。于是就不了了之了。


而实际上,这件事真的就结束了吗?我想不一定


由于当时公司的系统越来越多,并且旧系统也会升级改造,随着一些业务的新增或升级,如果有突发的流量,比如做个营销活动,系统能不能撑得住?即使撑得住,用户体验会不会好?我不知道,但我知道如果一个核心服务慢了,整体的体验都不会好。

其实对于我个人来讲,那次的改造不彻底也是我的一个心结,首先当时没有明确的标准和目标,没有特别清晰的量化下来,比如库存服务优化到300-500ms。其实当时我很清楚它慢,就是没有很好的解决了那个问题,心有不甘。


于是今天想起来,提出一个方案,看能不能解决它!


整个库存服务的重点就是如何准确的计算出来它的值,如果算的不对,可能导致有车租不出去(算少了),或没车租出去了(算多了)。而计算这个值的公式特别简单:


      可预定车辆数 = 现有运营车辆数 - 被占用车辆数


1  我们将现有运营车辆数以 门店_车型_amount 为 key ,车辆数为 value 存在Redis中。 当现有运营车辆数变化时,在修改DB的同时同步Redis,当然无论是DB还是Redis都是要做高可用架构设计的,以防止单点故障的发生。


2 被占用车辆原先是以数据库表的方式存储的记录,之前由于时间粒度太细,所以用SQL计算,所以慢,这里我们采用贪心算法的思路,具体是这样的:


   首先按门店+车型 一对一的将占车记录取出来。如A门店C1车型,A门店C2车型。将每组每条占车记录的开始和结束时间转为Unix时间戳,最小单位是秒,如:1546315932。


   再将开始和结束时间组成一个闭区间如[1546315932,1546316000],再把每组的这些闭区间组成一个二维数组。int[][]intervals。


例如:

[[1546315932,1546316000],[1546315942,1546317000],[1546315932,1546315943],[1546315901,1546315907]]

   最后每一个门店+车型都对应一个占车二维数组。


   将这些数组以 门店_车型_occupy 为 key ,二维数组为 value 序列化到redis中


   当要计算某门店某车型的库存时,先将上面的以门店_车型_occupy为key的值取出,然后利用贪心算法计算出时间段重合的占用数量,再用门店_车型_amount为key的总量减去占用数量就得到了可用库存数。如果时间段不重合就更新Redis二维数组添加新的元素。Redis在操作期间是需要LOCK的。

贪心算法


贪心算法,特别适合解决 Interval Scheduling(区间调度问题),比如算出多个区间中最多有几个互不相交的区间;或给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。如果我们请求库存的参数中的时间与现有占车记录的有重叠,那么返回的需要移除区间的最小数量就是 >=1 的。

代码实现如下


public int eraseOverlapIntervals(int[][] intervals) {
    if (intervals.length == 0) {
        return 0;
    }
     Arrays.sort(intervals,new Comparator<int[]>(){
           @Override
           public int compare(int[] o1,int[] o2){
               return o1[1]-o2[1];
           }
     });
    int cnt = 1;
    int end = intervals[0][1];
    for (int i = 1; i < intervals.length; i++) {
        if (intervals[i][0] < end) {
            continue;
        }
        end = intervals[i][1];
        cnt++;
    }
    return intervals.length - cnt;
}


70.gif


由于贪心算法是线性时间复杂度,又是在内存中计算,所以效率会比数据库高很多。


  • 这里你可能会关心Reids存储的数据量,由于车辆的开放预定周期不会太久,比如半年或一年,那么分到每个门店和车型其实预定的人并不那么多,即使在未来的预定周期内所有车都被预定了数据量也不很大。
  • 对于过期的数据,比如一个月以前或一周以前的不再参与库存计算的Redis数据,用定时任务清掉就可以了,或者也可以再行持久化备份一份。
  • 这里讲的是单车型的,如果要算多车型的,就起多线程并行计算。


以上就是利用了缓存和贪心算法进行的缓存优化,在此基础上还应该注意:不要对原服务(依赖数据库版本)进行删除,可做为系统服务降级时使用。Redis也要做好高可用和数据库降级处理,如果Redis挂了,还可以用DB顶一下,如果数据没有了,就将整个服务降级到老版本。


需要注意的是,上面的方案我并没有在生产环境实施过,只是一种单纯的设计,如有不严谨和不对的地方,欢迎指正如你需要解决相似的问题,请谨慎参考!

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
消息中间件 缓存 监控
订单系统的优化
订单系统的优化
|
4月前
|
运维 测试技术
拆分软件测试流程,一张图秒杀所有面试
本文主要介绍了软件测试流程的核心内容,包括需求分析、测试用例编写、测试执行、缺陷提交及回归测试等关键步骤。以迭代测试为例,详细说明了每个环节的具体操作和注意事项,并提供了一张测试流程图以便理解。测试流程确保了软件质量,是面试中常见的考察点。
88 7
拆分软件测试流程,一张图秒杀所有面试
|
5月前
|
缓存 NoSQL 安全
|
6月前
|
数据采集 供应链 监控
ERP系统中的库存周转率计算与优化解析
【7月更文挑战第25天】 ERP系统中的库存周转率计算与优化解析
198 0
|
存储 小程序 数据库
小程序整体的思路
小程序整体的思路
214 0
|
8月前
|
存储 数据库 UED
秒杀系统数据库设计核心要点详解
秒杀系统数据库设计核心要点详解
258 1
|
8月前
|
消息中间件 存储 NoSQL
面试题解析:如何解决分布式秒杀系统中的库存超卖问题?
面试题解析:如何解决分布式秒杀系统中的库存超卖问题?
449 0
|
Dubbo 应用服务中间件 数据库
浅谈订单重构之路
浅谈订单重构之路
182 0
|
存储 前端开发 NoSQL
开发小技巧系列 - 如何防止重复生成订单
如何防止重复生成订单,数据幂等性的处理
202 0
|
SQL 前端开发 NoSQL
谈一谈大厂都怎么防止重复下单?
谈一谈大厂都怎么防止重复下单?