使用Redis作預約庫存緩存功能

最近在本身的工做中,把其中一個PHP項目的緩存從之前的APC緩存逐漸切換到Redis中,而且根據Redis所支持的數據結構作了庫存維護功能。緩存是在業務層作的,準確講應該是在MVC模型中ModelORM裏面。主要邏輯就是先查緩存,查不到的話再查數據庫。不過這些不是本文的主要內容,下面我把庫存管理功能的緩存設計思路分享一下,但願能帶給你們一些收穫,有不足之處或者有更好方案的,也但願各位多多指教。php

 

1、業務背景前端

 

爲了略去咱們公司項目背景,我決定把此次的問題類比成一個考卷上的問題。至於業務細節,你們也無需關注~看題目就能夠了:redis

 

假設你是某國最牛的收藏家,手裏有各類價值連成的寶物。知道有一天,你以爲作收藏太沒意思了,打算把這些寶物賣掉換點現金。數據庫

 

不過把這些值錢的寶貝放在菜市場上賣實在太low了。在「互聯網+」時代,咱們固然要玩一些不同的賣法:在你名下有一棟300個房間的大樓(編號爲001300),每一個房間放着一個密碼鎖保險箱,在下個月(121日至1231日)的每一天,你都會挑選300件最好的「極品寶物」(也稱做A類寶物),分別放入這300個房間的保險箱裏,天天每一個房間放什麼寶物已經定好了,全部想買寶物的人必須至少提早一天在網上預約,到時候憑藉預約碼本身打開保險箱取貨。沒有被預約的寶物將會被你收回,再也不售賣。json

 

要作這樣一個網絡預約系統,它的前端界面大概是這樣的:數組

上圖中三個要填的控件,單擊後能夠出現選擇框。如今的問題是,一個房間只有一個寶物,不能被重複預約。因此當買家選擇了寶物類型和房間號以後,在選擇預約日期時,要在日期選擇框給用戶一個提示。好比123051號房間已被預約,如今又有另外一位用戶選擇了051號房間,那麼在彈出日期選擇框時,123日要置爲不可選。以下圖(123日顯示爲「缺」):緩存

那麼,這樣一個簡單的庫存系統,如何在redis中存儲呢?網絡

 

2、庫存管理方案(Redis數據結構

 

最粗暴的想法是,咱們的庫存其實就是一個很大的三維數組,第一維寶物類型,第二維房間號,第三維即預約日期。Redis支持5種存儲類型:StringHashListSetSorted Set。目前的場景中HashSet類型均可以知足要求,在此咱們選擇使用Hash類型作存儲。優化

 

Rediskey設置爲 寶物類型+房間號(例如 A:205A表明極品寶物,205爲房間號),Redisvaluehash類型,hash key爲日期(例如 2016-12-05),hash valuetruefalse,表示已經被預約或沒有被預約。用圖表示爲:

若是A類寶物158房間在128日已經被預約,則存儲爲

Redis Key —— A:158

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

 

3、進階場景&庫存管理方案

 

你所推出的A類極品寶物很受歡迎,剛推出去不久即被預約出去不少。然而,動輒數十萬元的價格也讓不少有收藏興趣、卻沒那麼富裕的中產階級望而卻步。因而,你又從本身的收藏中挑選出了比A類寶物稍次一些的B類寶物(也稱做「優質寶物」),價格更加親民。

 

因爲B類寶物比A類寶物多一些,你打算換一種玩法,在這300個房間中,每一個房間又放入了一個保險箱,此次,你每隔一個小時都會向300個房間的箱中各放入一件B類寶物,沒有被預約的寶物在這一個小時事後會被收回,換成下一個小時的寶物。買家預訂後,按照所預約的小時來取走寶物。對於B類寶物,你的預約系統會多了一個選項,即取貨時間。以下圖:

如今因爲多了一個預約條件(取貨時間),那在作庫存存儲的時候,粗暴的方式想一下,庫存其實就是一個大的四維數組。第一維寶物類型,第二維房間號,第三維預約日期,第四維取貨時間。在Redis中怎樣存儲這類寶物呢?

 

其實仔細想一下,在存儲A類極品寶物的時候,咱們在Redis中的存儲是有浪費維度的狀況的,

當時hashValue只存了一個true表示有預約,這個維度實際上是被浪費掉了。考慮到取貨時間全是整點,一成天也就是01點,12點,……,2324點共計24種狀況,因此咱們徹底可使用二進制整數表示被預約的時間。例如1表示01點,2表示12點,4表示23點,……,

8388608 = 2^23)表示2324點。多個時間段被預約,只須要將數值取邏輯或操做便可。

 

這樣,咱們的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去刪除。

 

4、再次進階&庫存管理方案

 

自從推出了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類寶物,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對於三種寶物的存儲都有了。

對於C類寶物,在用戶取消預約、新增預約時,一樣不能簡單地調用hSethDel進行覆蓋設置和刪除,要取出已經預約的狀況,與已經預約的取貨時間作位運算。

 

5、存儲優化

 

庫存理論上就是一個多維數組,咱們所作的主要工做就是怎樣把各個維度合理的存儲起來,並可以方便地進行增長、刪除、查詢操做。從節約使用內存的角度講,在最開始尚未任何人預約的時候,Redis整個能夠是空的,對於A類寶物來講,hash value等於false和根本不存在對應的redis keyhash 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集合中。刪除操做也相似。

 

6、總結

 

上面使用了按部就班的方法講述了一下問題,不過現實的場景中,這三種寶物類型在咱們的業務中是同時存在的。上面的設計保持了三種寶物類型存儲上的統一性。若是隻考慮A類寶物的話,庫存只有三個維度,其實徹底沒必要使用hash數據類型來存儲,set類型就足夠了。

 

咱們存儲這些預約狀況的主要目的,就是爲了方便快速地查到庫存衝突狀況。好比有人已經定了123日,59號房間的A類寶物,那又有另一我的想預約同樣的日期、房間的A類寶物時,經過內存中的庫存查詢,咱們能夠很方便地告訴客戶,該庫存已經被其餘人搶先預約了。

 

以上就是我在業務中碰到的一個緩存設計的小問題,不吝賜教!

相關文章
相關標籤/搜索