使用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 配置文件重要属性的具体使用
May 20 Redis
springboot使用Redis作缓存使用入门教程
Jul 25 Redis
Redis命令处理过程源码解析
Feb 12 Redis
分布式架构Redis中有哪些数据结构及底层实现原理
Mar 13 Redis
浅谈Redis 中的过期删除策略和内存淘汰机制
Apr 03 Redis
Redis数据同步之redis shake的实现方法
Apr 21 Redis
浅谈Redis的事件驱动模型
May 30 Redis
Redis入门基础常用操作命令整理
Jun 01 Redis
Redis实现订单过期删除的方法步骤
Jun 05 Redis
浅谈Redis变慢的原因及排查方法
Jun 21 Redis
Redis主从复制操作和配置详情
Sep 23 Redis
python中使用redis用法详解
Dec 24 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
php你的验证码安全码?
2007/01/02 PHP
linux系统上支持php的 iconv()函数的方法
2011/10/01 PHP
php递归方法实现无限分类实例代码
2014/02/28 PHP
PHP中array_slice函数用法实例详解
2014/11/25 PHP
Jquery中增加参数与Json转换代码
2009/11/20 Javascript
YUI Compressor压缩JavaScript原理及微优化
2013/01/07 Javascript
javaScript实现浮点数转十六进制字符
2013/10/29 Javascript
jQuery过滤选择器:not()方法使用介绍
2014/04/20 Javascript
JS模拟实现Select效果代码
2015/09/24 Javascript
jQuery EasyUI 布局之动态添加tabs标签页
2015/11/18 Javascript
Mongoose学习全面理解(推荐)
2017/01/21 Javascript
使用openSpeDiv方法实现Ecshop登录弹窗框效果
2017/03/13 Javascript
Bootstrap 响应式实用工具实例详解
2017/03/29 Javascript
Angular 表单控件示例代码
2017/06/26 Javascript
JS对象序列化成json数据和json数据转化为JS对象的代码
2017/08/23 Javascript
Node.js 使用流实现读写同步边读边写功能
2017/09/11 Javascript
解析Json字符串的三种方法日常常用
2018/05/02 Javascript
vue实现简单loading进度条
2018/06/06 Javascript
解决vue 单文件组件中样式加载问题
2019/04/24 Javascript
layui实现根据table数据判断按钮显示情况的方法
2019/09/26 Javascript
React学习之受控组件与数据共享实例分析
2020/01/06 Javascript
Vue的全局过滤器和私有过滤器的实现
2020/04/20 Javascript
解决VUEX的mapState/...mapState等取值问题
2020/07/24 Javascript
JS实现炫酷轮播图
2020/11/15 Javascript
python处理json数据中的中文
2014/03/06 Python
十分钟利用Python制作属于你自己的个性logo
2018/05/07 Python
python实现企业微信定时发送文本消息的实例代码
2020/11/25 Python
matplotlib 范围选区(SpanSelector)的使用
2021/02/24 Python
利用HTML5中的Canvas绘制一张笑脸的教程
2015/05/07 HTML / CSS
html5给汉字加拼音加进度条的实现代码
2020/04/07 HTML / CSS
向全球直邮输送天然健康产品:iHerb.com
2020/05/03 全球购物
医院我们的节日活动实施方案
2014/08/22 职场文书
城市规划应届生推荐信
2014/09/08 职场文书
2014年基层党支部工作总结
2014/12/04 职场文书
政协委员个人总结
2015/03/03 职场文书
挂职锻炼个人总结
2015/03/05 职场文书