一、业务背景
我决定将本次问题比作考卷上的问题,以避免介绍我们公司项目的背景。至于业务细节,大家也无需关注~看题目就可以了:
假设你是某国最牛的收藏家,手里有各种价值连成的宝物。有一天你可能会感到收藏变得无聊了,于是决定出售这些珍贵物品以获取现金。
不过把这些值钱的宝贝放在菜市场上卖实在太low了。在“互联网+”时代,我们当然要玩一些不一样的卖法:在你名下有一栋300个房间的大楼(编号为001至300),每个房间放着一个密码锁保险箱,在下个月(12月1日至12月31日)的每一天,你都会挑选300件最好的“极品宝物”(也称作A类宝物),分别放入这300个房间的保险箱里,每天每个房间放什么宝物已经定好了,所有想买宝物的人必须至少提前一天在网上预定,到时候凭借预定码自己打开保险箱取货。没有被预定的宝物将会被你收回,不再售卖。
要做这样一个网络预定系统,它的前端界面大概是这样的:

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

那么,这样一个简单的库存系统,如何在redis中存储呢?
二、库存管理方案(Redis)
我们最初的构想是,我们的存货可以被看作是一个巨大的三维数组,其中第一维表示宝物类型,第二维表示房间号,第三维表示预定日期。Redis提供了五种存储类型,分别为:String、Hash、List、Set、Sorted Set。我们可以在当前场景下使用Hash类型来存储数据,因为它能够满足我们的需求,同时Set类型也是可行的选项。
Redis的key设置为 宝物类型+房间号(例如 A:205,A代表极品宝物,205为房间号),Redis的value为hash类型,hash key为日期(例如 2016-12-05),hash value为true或false,表示已经被预定或没有被预定。用图表示为:

如果A类宝物158房间在12月8日已经被预定,则存储为
1 2 3 | Redis Key —— A:158
Redis Value —— hash table [ '2016-12-08' => 1]
|
三、进阶场景&库存管理方案
A类顶级宝物的推出受到了热烈欢迎,仅推出不久便已被订购数不少。许多中产阶级对收藏感兴趣,但高昂的价格常常令他们望而却步。于是,你从自己的珍藏中选择了B类宝物,它比A类宝物稍逊一些,但价格更为合理,也被称为“优良宝物”。
由于B类宝物比A类宝物多一些,你打算换一种玩法,在这300个房间中,每个房间又放入了一个保险箱,这次,你每隔一个小时都会向300个房间的箱中各放入一件B类宝物,没有被预定的宝物在这一个小时过后会被收回,换成下一个小时的宝物。买家预订后,按照所预定的小时来取走宝物。对于B类宝物,你的预定系统会多了一个选项,即取货时间。如下图:

现在由于多了一个预定条件(取货时间),那在做库存存储的时候,粗暴的方式想一下,库存其实就是一个大的四维数组。该句话可以重写为:四维信息包括宝物类型、房间号、预定日期和取货时间。在Redis中怎样存储这类宝物呢?
其实仔细想一下,在存储A类极品宝物的时候,我们在Redis中的存储是有浪费维度的情况的,
实际上,当时只使用了一个hashValue存储了预定的状态,导致该维度的信息被浪费了。考虑到取货时间全是整点,一整天也就是0至1点,1至2点,……,23至24点共计24种情况,所以我们完全可以使用二进制整数表示被预定的时间。例如1表示0至1点,2表示1至2点,4表示2至3点,……,
23至24点可以用2的23次方(8388608)来表示。多个时间段被预定,只需要将数值取逻辑或操作即可。
这样,我们的Redis结构变成了这样子:

例如,B类宝物103房间,12月5日和6日的上午8点至12点被预定,在redis中存储为
1 2 3 | 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个房间中,C类宝物储存数量最多,因此你在每个房间增加了100个专门用于储存C类宝物的宝箱。这100个宝箱分别被编号为1号,2号,……,100号。同样的,每天的每个小时,你都会向这300个房间中,每个房间的100个宝箱中分别放入一件C类宝物(也就意味着,整个大楼每小时C类宝物会更新30000件)。如果没有人预定,则下一个小时宝物更换。终于,这下可以满足所有人的需求了。
对于C类宝物,你的预定界面成了下面的样子:

我们又多了一个预定条件。此时,又面临着库存存储的问题。照例,这个库存其实就是一个大的五维数组,宝物类型、房间号、预定日期、取货时间、宝箱编号各自占有一个维度。前面我们已经用掉了Redis的各个容量,现在要存储数据该怎么办?
这次的Redis库存存储必须要结合业务特点来了。首先,宝箱编号和取货时间这两个维度,能取的值范围并不太多,宝箱编号只有100个,只要把hash value变成一个长度为100的数组,数组的每个位置都存有INT类型表示的取货时间即可。然而hash value只能是string……于是乎,只好做一个数组的序列化操作,读取的时候再反序列化回来即可。好在长度只有100,序列化效率并不会成为系统的瓶颈。
存储方式为:12月23日、24日在258房间的C类宝物中,编号为97和99的宝箱在上午11点至下午1点期间已被预定
1 2 3 | 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为数组序列化以后
.........................................................