库存服务优化思路

本文涉及的产品
云数据库 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顶一下,如果数据没有了,就将整个服务降级到老版本。


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

相关文章
|
监控 网络协议 Ubuntu
netstat,Linux 下的网络状态监控工具
Netstat 是 Linux 系统中的强大网络分析工具,用于查看网络配置和活动,如端口监听、网络连接和路由信息。通过基本命令 `netstat [options]` 可实现多种操作,例如 `-a` 显示所有端口,`-l` 显示监听端口,`-s` 展示协议统计信息。结合 `-p` 选项可查看占用端口的进程,而监控网络连接状态则可用 `-nt` 加 `grep ESTABLISHED` 查看已建立的连接。要深入了解和使用更多功能,可查阅 `man netstat`。
341 0
|
机器学习/深度学习 人工智能 算法
探索软件测试的未来:自动化与AI的融合之路移动应用开发的新纪元:从原生到跨平台
【8月更文挑战第27天】在软件开发的世界中,测试是确保产品质量的关键步骤。随着技术的不断进步,传统的手动测试方法正逐渐被自动化和人工智能(AI)技术所取代。本文将探讨自动化测试的现状与挑战,并展望未来AI如何重塑软件测试领域,同时提供实用的代码示例,引领读者一窥自动化测试的未来趋势。
|
存储 算法
数据结构实验三 线性表的链式存储结构及实现
数据结构实验三 线性表的链式存储结构及实现
364 0
|
存储 编译器 程序员
C语言中的表达式:深入理解与应用
C语言中的表达式:深入理解与应用
603 4
|
编解码 前端开发 搜索推荐
构建响应式网页设计的最佳实践
【5月更文挑战第17天】构建响应式网页设计的关键在于提供跨设备的优质体验。本文总结了8大最佳实践:1) 移动优先设计,2) 流式布局,3) 灵活处理图片和媒体,4) 使用CSS媒体查询,5) 简化导航和布局,6) 优化字体大小和行高,7) 考虑触摸和手势支持,以及8) 不断测试与调试。通过这些方法,开发者能创建适应各种屏幕尺寸且用户体验优良的网站。
1099 3
|
存储 数据安全/隐私保护 芯片
【STM32】详解嵌入式中FLASH闪存的特性和代码示例
【STM32】详解嵌入式中FLASH闪存的特性和代码示例
|
SQL 运维 JavaScript
开源!!!前后端分离房屋租赁管理系统!
开源!!!前后端分离房屋租赁管理系统!
536 0
|
SQL 存储 Python
Microsoft SQL Server 编写汉字转拼音函数
Microsoft SQL Server 编写汉字转拼音函数
|
网络协议 关系型数据库 MySQL
Android Termux安装MySQL,通过内网穿透实现公网远程访问
Android Termux安装MySQL,通过内网穿透实现公网远程访问
|
SQL 关系型数据库 MySQL
mysql查询出现QSqlQuery::value: not positioned on a valid record
mysql查询出现QSqlQuery::value: not positioned on a valid record
373 0