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

目录

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

一、业务背景

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

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

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

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

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

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

二、库存管理方案(Redis)

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

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

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

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

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

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

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

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

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

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

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

例如,C类宝物,12月23日、24日,258房间,97和99号宝箱在11点至13点被预定,则存储为:


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为数组序列化以后的字符串,实际项目中可以使用json格式。好了,现在Redis对于三种宝物的存储都有了。

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

五、存储优化

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

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

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

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

六、总结

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

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

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

以上就是Redis做预定库存缓存功能设计使用的详细内容,更多关于Redis预定库存缓存设计的资料请关注我们其它相关文章!

(0)

相关推荐

  • Redis中缓存穿透/击穿/雪崩问题和解决方法

    目录 缓存问题 1. 缓存穿透---查不到 解决方案 2. 缓存击穿---量太大,缓存过期 解决方案 3. 缓存雪崩 解决方案 缓存问题 1. 缓存穿透---查不到 缓存穿透是指用户想查询一个数据,发现Redis中没有,也就是缓存没有命中,就向持久性数据库发起查询,发现数据库也没有这个数据,于是查询失败了. 当用户请求很多的情况下,缓存没有命中,数据库也没有数据,会给数据库造成很大的压力,这就是缓存穿透. 解决方案 第一种解决方案:使用布隆过滤器 使用布隆过滤器之后,将存储的数据放入布隆过滤器中

  • Redis都做了哪些加快速度的设计

    列表对象是 Redis 中 5 种基础数据类型之一,在 Redis 3.2 版本之前,列表对象底层存储结构有两种:linkedlist(双端列表)和 ziplist(压缩列表),而在 Redis 3.2 版本之后,列表对象底层存储结构只有一种:quicklist(快速列表),难道通过精心设计的 ziplist 最终被 Redis 抛弃了吗? 列表对象 同字符串对象一样,列表对象到底使用哪一种数据结构来进行存储也是通过编码来进行区分: 编码属性 描述 object encoding命令返回值 OB

  • 基于redis实现的点赞功能设计思路详解

    前言 点赞其实是一个很有意思的功能.基本的设计思路有大致两种, 一种自然是用mysql等 数据库直接落地存储, 另外一种就是利用点赞的业务特征来扔到redis(或memcache)中, 然后离线刷回mysql等. 直接写入Mysql 直接写入Mysql是最简单的做法. 做两个表即可, 1.post_like 记录文章被赞的次数,已有多少人赞过这种数据就可以直接从表中查到; 2.user_like_post 记录用户赞过了哪些文章, 当打开文章列表时,显示的有没有赞过的数据就在这里面; 缺点 1.

  • Redis之sql缓存的具体使用

    目录 1.环境搭建 2.Redis配置 3.功能实现 4.缓存注解的使用说明 1.环境搭建 <!-- RedisTemplate --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> spring: redis: ho

  • Redis分布式锁防止缓存击穿的实现

    缓存击穿 和缓存穿透不同的是,缓存击穿是指:缓存中没有,但是数据库中存在的热点数据. 例如:首页的热点新闻,并发访问量非常大的热点数据,如果缓存过期失效,服务器会去查询DB,这时候如果大量的并发去查询DB,可能会瞬间压垮DB. 画了个简图,如下所示: 解决方案:DB查询加分布式锁. 未加锁的情况 解决问题之前,先看一下不做处理的代码和运行情况. 根据商品ID查询商品详情代码 清空Redis缓存,开启5个线程去并发访问测试,测试代码如下: 我们预期希望DB只查询一次,后面4个查询从Redis缓存中

  • Redis缓存IO模型的演进教程示例精讲

    目录 前言 事件模型 通信 copy数据的开销 数据怎么知道发给哪个socket socket的数据怎么通知程序来取 Reactor IO多路复用器 select epoll epoll是怎么做到的? 单线程到多线程的演进 单线程 异步线程 多线程 多线程的作用点? 多线程的原理 前言 redis作为应用最广泛的nosql数据库之一,大大小小也经历过很多次升级.在4.0版本之前,单线程+IO多路复用使得redis的性能已经达到一个非常高的高度了.作者也说过,之所以设计成单线程是因为redis的瓶

  • Redis做预定库存缓存功能设计使用

    目录 最近在自己的工作中,把其中一个PHP项目的缓存从以前的APC缓存逐渐切换到Redis中,并且根据Redis所支持的数据结构做了库存维护功能.缓存是在业务层做的,准确讲应该是在MVC模型中Model的ORM里面.主要逻辑就是先查缓存,查不到的话再查数据库.不过这些不是本文的主要内容,下面我把库存管理功能的缓存设计思路分享一下,希望能带给大家一些收获,有不足之处或者有更好方案的,也希望各位多多指教. 一.业务背景 为了略去我们公司项目背景,我决定把这次的问题类比成一个考卷上的问题.至于业务细节

  • Spring Boot中使用Redis做缓存的方法实例

    前言 本文主要给大家介绍的是关于Spring Boot中使用Redis做缓存的相关内容,这里有两种方式: 使用注解方式(但是小爷不喜欢) 直接<Spring Boot 使用 Redis>中的redisTemplate 下面来看看详细的介绍: 1.创建UserService public interface UserService { public User findById(int id); public User create(User user); public User update(U

  • SpringBoot+Mybatis项目使用Redis做Mybatis的二级缓存的方法

    介绍 使用mybatis时可以使用二级缓存提高查询速度,进而改善用户体验. 使用redis做mybatis的二级缓存可是内存可控<如将单独的服务器部署出来用于二级缓存>,管理方便. 1.在pom.xml文件中引入redis依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifac

  • Spring Boot基础学习之Mybatis操作中使用Redis做缓存详解

    前言 这篇博客学习下Mybatis操作中使用Redis做缓存.这里其实主要学习几个注解:@CachePut.@Cacheable.@CacheEvict.@CacheConfig. 下面话不多说了,来一起看看详细的介绍吧 一.基础知识 @Cacheable @Cacheable 的作用 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 参数 解释 example value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如: @Cacheable(value="myc

  • 在项目中使用redis做缓存的一些思路

    目录 在项目中redis做缓存的一些思路 首先,缓存的对象有三种 本人走过的一些弯路 为什么没用Redis做缓存 使用Table作本地缓存 使用Redis作缓存 让我们来思考一下下面几个问题 那么使用本地缓存的问题是什么呢? 什么时候使用Redis? 在项目中redis做缓存的一些思路 首先,缓存的对象有三种 1.数据库中单条的的数据(以表名跟id作为key永久保存到redis),在有更新的地方都要更新缓存(不适用于需要经常更新的数据): 2.对于一些不分页,不需要实时(需要多表查询)的列表,我

  • 使用SpringCache加Redis做缓存

    目录 Redis + SpringCache 1. 添加依赖 <!-- redis --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- spring2.X集成redis所需common-pool2-->

  • SpringBoot中使用redis做分布式锁的方法

    一.模拟问题 最近在公司遇到一个问题,挂号系统是做的集群,比如启动了两个相同的服务,病人挂号的时候可能会出现同号的情况,比如两个病人挂出来的号都是上午2号.这就出现了问题,由于是集群部署的,所以单纯在代码中的方法中加锁是不能解决这种情况的.下面我将模拟这种情况,用redis做分布式锁来解决这个问题. 1.新建挂号明细表 2.在idea上新建项目 下图是创建好的项目结构,上面那个parent项目是其他项目不用管它,和新建的没有关系 3.开始创建controller,service,dao(mapp

  • Redis+Caffeine两级缓存的实现

    目录 优点与问题 准备工作 V1.0版本 V2.0版本 V3.0版本 在高性能的服务架构设计中,缓存是一个不可或缺的环节.在实际的项目中,我们通常会将一些热点数据存储到Redis或MemCache这类缓存中间件中,只有当缓存的访问没有命中时再查询数据库.在提升访问速度的同时,也能降低数据库的压力. 随着不断的发展,这一架构也产生了改进,在一些场景下可能单纯使用Redis类的远程缓存已经不够了,还需要进一步配合本地缓存使用,例如Guava cache或Caffeine,从而再次提升程序的响应速度与

  • 浅谈我是如何用redis做实时订阅推送的

    前阵子开发了公司领劵中心的项目,这个项目是以redis作为关键技术落地的. 先说一下领劵中心的项目吧,这个项目就类似京东app的领劵中心,当然图是截取京东的,公司的就不截了... 其中有一个功能叫做领劵的订阅推送.什么是领劵的订阅推送?就是用户订阅了该劵的推送,在可领取前的一分钟就要把提醒信息推送到用户的app中.本来这个订阅功能应该是消息中心那边做的,但他们说这个短时间内做不了.所以让我这个负责优惠劵的做了-.-!.具体方案就是到具体的推送时间点了,coupon系统调用消息中心的推送接口,把信

随机推荐