使用Redis做预定库存缓存功能


Posted in Redis onApril 02, 2022

最近在自己的工作中,把其中一个PHP项目的缓存从以前的APC缓存逐渐切换到Redis中,并且根据Redis所支持的数据结构做了库存维护功能。缓存是在业务层做的,准确讲应该是在MVC模型中ModelORM里面。主要逻辑就是先查缓存,查不到的话再查数据库。不过这些不是本文的主要内容,下面我把库存管理功能的缓存设计思路分享一下,希望能带给大家一些收获,有不足之处或者有更好方案的,也希望各位多多指教。

一、业务背景

为了略去我们公司项目背景,我决定把这次的问题类比成一个考卷上的问题。至于业务细节,大家也无需关注~看题目就可以了:

假设你是某国最牛的收藏家,手里有各种价值连成的宝物。知道有一天,你觉得做收藏太没意思了,打算把这些宝物卖掉换点现金。

不过把这些值钱的宝贝放在菜市场上卖实在太low了。在“互联网+”时代,我们当然要玩一些不一样的卖法:在你名下有一栋300个房间的大楼(编号为001300),每个房间放着一个密码锁保险箱,在下个月(121日至1231日)的每一天,你都会挑选300件最好的“极品宝物”(也称作A类宝物),分别放入这300个房间的保险箱里,每天每个房间放什么宝物已经定好了,所有想买宝物的人必须至少提前一天在网上预定,到时候凭借预定码自己打开保险箱取货。没有被预定的宝物将会被你收回,不再售卖。

要做这样一个网络预定系统,它的前端界面大概是这样的:

使用Redis做预定库存缓存功能

上图中三个要填的控件,单击后可以出现选择框。现在的问题是,一个房间只有一个宝物,不能被重复预定。所以当买家选择了宝物类型和房间号之后,在选择预定日期时,要在日期选择框给用户一个提示。比如123051号房间已被预定,现在又有另一位用户选择了051号房间,那么在弹出日期选择框时,123日要置为不可选。如下图(123日显示为“缺”):

使用Redis做预定库存缓存功能

那么,这样一个简单的库存系统,如何在redis中存储呢?

二、库存管理方案(Redis

最粗暴的想法是,我们的库存其实就是一个很大的三维数组,第一维宝物类型,第二维房间号,第三维即预定日期。Redis支持5种存储类型:StringHashListSetSorted Set。目前的场景中HashSet类型都可以满足要求,在此我们选择使用Hash类型做存储。

Rediskey设置为 宝物类型+房间号(例如 A:205A代表极品宝物,205为房间号),Redisvaluehash类型,hash key为日期(例如 2016-12-05),hash valuetruefalse,表示已经被预定或没有被预定。用图表示为:

使用Redis做预定库存缓存功能

如果A类宝物158房间在128日已经被预定,则存储为

Redis Key —— A:158

Redis Value —— hash table ['2016-12-08' => 1]

三、进阶场景&库存管理方案

你所推出的A类极品宝物很受欢迎,刚推出去不久即被预定出去很多。然而,动辄数十万元的价格也让很多有收藏兴趣、却没那么富裕的中产阶级望而却步。于是,你又从自己的收藏中挑选出了比A类宝物稍次一些的B类宝物(也称作“优质宝物”),价格更加亲民。

由于B类宝物比A类宝物多一些,你打算换一种玩法,在这300个房间中,每个房间又放入了一个保险箱,这次,你每隔一个小时都会向300个房间的箱中各放入一件B类宝物,没有被预定的宝物在这一个小时过后会被收回,换成下一个小时的宝物。买家预订后,按照所预定的小时来取走宝物。对于B类宝物,你的预定系统会多了一个选项,即取货时间。如下图:

使用Redis做预定库存缓存功能

现在由于多了一个预定条件(取货时间),那在做库存存储的时候,粗暴的方式想一下,库存其实就是一个大的四维数组。第一维宝物类型,第二维房间号,第三维预定日期,第四维取货时间。在Redis中怎样存储这类宝物呢?

其实仔细想一下,在存储A类极品宝物的时候,我们在Redis中的存储是有浪费维度的情况的,

当时hashValue只存了一个true表示有预定,这个维度其实是被浪费掉了。考虑到取货时间全是整点,一整天也就是01点,12点,……,2324点共计24种情况,所以我们完全可以使用二进制整数表示被预定的时间。例如1表示01点,2表示12点,4表示23点,……,

8388608 = 2^23)表示2324点。多个时间段被预定,只需要将数值取逻辑或操作即可。

这样,我们的Redis结构变成了这样子:

使用Redis做预定库存缓存功能

例如,B类宝物103房间,125日和6日的上午8点至12点被预定,在redis中存储为

Redis Key  —— B:103

Redis Value —— hash table ['2016-12-05' => 3840, '2016-12-06' => 3840]

对于B类宝物,在做新增预定时,需要注意先将原有的hash value取出,和新的预定取货时间做逻辑或操作,然后再把结果写回Redis中,而不能像A类宝物一样直接调用hSet去设置hash value;取消预定时,要注意先将原有的hash value取出,把要取消的时间段从hash value中扣除掉(异或+逻辑与操作),然后重新将剩余的已预订取货时间写回Redis中,而不能直接调用hDel去删除。

四、再次进阶&库存管理方案

自从推出了B类宝物之后,你的生意又比以往火爆了许多。于是新的需求又来了,现在有大量的游客、学生党等没什么丰厚积蓄的人表示对你的宝物非常感兴趣,来这个城市旅游的人都希望带一些纪念品回去。然而,B类宝物的价格虽然比A类便宜一些,对于这些人来讲还是有点贵。于是,你决定把自己余量最多的实惠宝物(C类宝物)拿出来售卖。

这部分宝物数量是最多的,于是你在这300个房间中,每个房间新增了100个宝箱,专门用于存放C类宝物。这100个宝箱分别被编号为1号,2号,……,100号。同样的,每天的每个小时,你都会向这300个房间中,每个房间的100个宝箱中分别放入一件C类宝物(也就意味着,整个大楼每小时C类宝物会更新30000件)。如果没有人预定,则下一个小时宝物更换。终于,这下可以满足所有人的需求了。

对于C类宝物,你的预定界面成了下面的样子:

使用Redis做预定库存缓存功能

我们又多了一个预定条件。此时,又面临着库存存储的问题。照例,这个库存其实就是一个大的五维数组,宝物类型、房间号、预定日期、取货时间、宝箱编号各自占有一个维度。不过前面我们的Redis各个维度基本上已经占满了,这次应该怎么存储呢?

这次的Redis库存存储必须要结合业务特点来了。首先,宝箱编号和取货时间这两个维度,能取的值范围并不太多,宝箱编号只有100个,只要把hash value变成一个长度为100的数组,数组的每个位置都存有INT类型表示的取货时间即可。然而hash value只能是string……于是乎,只好做一个数组的序列化操作,读取的时候再反序列化回来即可。好在长度只有100,序列化效率并不会成为系统的瓶颈。

例如,C类宝物,1223日、24日,258房间,9799号宝箱在11点至13点被预定,则存储为:

Redis Key —— C:258

Redis Value —— hash table ['2016-12-23' => '[97 => 6144, 99 => 6144]', '2016-12-24' => '[97 => 6144, 99 => 6144]' ]

  

其中6144用二进制表示为‘110000000000’,hash value为数组序列化以后的字符串,实际项目中可以使用json格式。好了,现在Redis对于三种宝物的存储都有了。

使用Redis做预定库存缓存功能

对于C类宝物,在用户取消预定、新增预定时,同样不能简单地调用hSethDel进行覆盖设置和删除,要取出已经预定的情况,与已经预定的取货时间做位运算。

五、存储优化

库存理论上就是一个多维数组,我们所做的主要工作就是怎样把各个维度合理的存储起来,并能够方便地进行增加、删除、查询操作。从节约使用内存的角度讲,在最开始还没有任何人预定的时候,Redis整个可以是空的,对于A类宝物来说,hash value等于false和根本不存在对应的redis keyhash key是等效的。

另外,宝物类型和房间号合起来做redis key,会导致我们在redis中和宝物库存相关的key的数量比较多,为了方便统一管理这些key,可以再增加一条redis缓存,专门用来存储和宝物库存相关的所有redis key值,如下图所示。需要注意的是,这次我们并不需要hash数据类型了,set类型就已经足够,增删改查复杂度都是O(1)。里面存储了所有redis中已经存在的库存key值。

使用Redis做预定库存缓存功能

这么做的一个好处是,万一哪天碰到一些特殊情况,需要把所有库存相关缓存全部清空的话,我们可以很容易地取出所有的库存key并做删除操作。另外一个好处是,给我们提供了继续扩展的思路……设想一下,现在最复杂的情况是C类宝物,一共5个维度。假设未来,你不再使用一幢楼的300个房间去售卖宝物,而是多幢楼,那么用户在下订单的时候又要多出一个维度——楼栋编号。碰到这种情况,我们完全可以将这个多出来的库存Key集合退化为楼栋编号来使用,保证了可能出现的更复杂情况下的扩展性。

在做了这次扩展之后,每次新增预定记录时,需要注意检测库存key集合中是否已经存在对应的redis key值,如果不存在需要将redis key值加入库存key集合中。删除操作也类似。

六、总结

上面使用了循序渐进的方法讲述了一下问题,不过现实的场景中,这三种宝物类型在我们的业务中是同时存在的。上面的设计保持了三种宝物类型存储上的统一性。如果只考虑A类宝物的话,库存只有三个维度,其实完全不必使用hash数据类型来存储,set类型就足够了。

我们存储这些预定情况的主要目的,就是为了方便快速地查到库存冲突情况。比如有人已经定了123日,59号房间的A类宝物,那又有另外一个人想预定一样的日期、房间的A类宝物时,通过内存中的库存查询,我们可以很方便地告诉客户,该库存已经被其他人抢先预定了。

以上就是我在业务中碰到的一个缓存设计的小问题,不吝赐教!

Redis 相关文章推荐
redis 查看所有的key方式
May 07 Redis
redis 限制内存使用大小的实现
May 08 Redis
SpringBoot 集成Redis 过程
Jun 02 Redis
Windows下redis下载、redis安装及使用教程
Jun 02 Redis
压缩Redis里的字符串大对象操作
Jun 23 Redis
Redis中一个String类型引发的惨案
Jul 25 Redis
在项目中使用redis做缓存的一些思路
Sep 14 Redis
Redis的字符串是如何实现的
Oct 24 Redis
使用RedisTemplat实现简单的分布式锁
Nov 20 Redis
linux下安装redis图文详细步骤
Dec 04 Redis
聊聊redis-dump工具安装问题
Jan 18 Redis
Redis高可用集群redis-cluster详解
Mar 20 Redis
sentinel支持的redis高可用集群配置详解
redis sentinel监控高可用集群实现的配置步骤
redis调用二维码时的不断刷新排查分析
Apr 01 #Redis
基于Redis6.2.6版本部署Redis Cluster集群的问题
Redis分布式锁的7种实现
Redis 哨兵机制及配置实现
Redis如何使用乐观锁(CAS)保证数据一致性
Mar 25 #Redis
You might like
CentOS 6.2使用yum安装LAMP以及phpMyadmin详解
2013/06/17 PHP
PHP使用观察者模式处理异常信息的方法详解
2019/09/24 PHP
php7 图形用户界面GUI 开发示例
2020/02/22 PHP
比Jquery的document.ready更快的方法
2010/04/28 Javascript
深入理解jQuery中live与bind方法的区别
2013/12/18 Javascript
JavaScript面向对象的实现方法小结
2015/04/14 Javascript
JavaScript中的继承之类继承
2016/05/01 Javascript
Node.js DES加密的简单实现
2016/07/07 Javascript
JS实现根据用户输入分钟进行倒计时功能
2016/11/14 Javascript
vue.js+Element实现表格里的增删改查
2017/01/18 Javascript
js学习总结之dom2级事件基础知识详解
2017/07/27 Javascript
Vuejs中使用markdown服务器端渲染的示例
2017/11/22 Javascript
axios进阶实践之利用最优雅的方式写ajax请求
2017/12/20 Javascript
Angular5中调用第三方js插件的方法
2018/02/26 Javascript
React BootStrap用户体验框架快速上手
2018/03/06 Javascript
nodejs 日志模块winston的使用方法
2018/05/02 NodeJs
详解在Vue中使用TypeScript的一些思考(实践)
2018/07/06 Javascript
PHP网页抓取之抓取百度贴吧邮箱数据代码分享
2016/04/13 Python
python bottle框架支持jquery ajax的RESTful风格的PUT和DELETE方法
2017/05/24 Python
Python字符串拼接的几种方法整理
2017/08/02 Python
Python中%是什么意思?python中百分号如何使用?
2018/03/20 Python
python下载微信公众号相关文章
2019/02/26 Python
PyQt5 实现给窗口设置背景图片的方法
2019/06/13 Python
wxpython布局的实现方法
2019/11/01 Python
Windows 平台做 Python 开发的最佳组合(推荐)
2020/07/27 Python
微软日本官方网站:Microsoft日本
2017/11/26 全球购物
台湾时尚彩瞳专门店:imeime
2019/08/16 全球购物
设计毕业生简历中的自我评价
2013/10/01 职场文书
大学生求职中的自我评价
2013/10/01 职场文书
安全员岗位职责
2013/11/11 职场文书
安全检查管理制度
2014/02/02 职场文书
知识竞赛活动方案
2014/02/18 职场文书
安全生产知识竞赛活动总结
2014/07/07 职场文书
乡镇党委书记第三阶段个人整改措施
2014/09/16 职场文书
2014年技术部工作总结
2014/12/12 职场文书
导游词之日月潭
2019/11/05 职场文书