一、業務背景
我決定將本次問題比喻為考卷上的問題,以避免介紹我們公司專案的背景。至於業務細節,大家也不需要關注~看題目就可以了:
假設你是某國最牛的收藏家,手裡有各種價值連成的寶物。有一天你可能會感到收藏變得無聊了,於是決定出售這些珍貴物品以獲取現金。
不過把這些值錢的寶貝放在菜市場上賣實在太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 |
#
|
三、進階場景&庫存管理方案
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 Value —— hash table [ |
#1 2 3 |
|
其中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值,如下圖所示。需要注意的是,在這種情況下,使用set資料類型就可以滿足要求了,而不必使用hash資料類型,因為set資料類型的增刪改查複雜度都為O(1)。裡面儲存了所有redis中已經存在的庫存key值。
這麼做的一個好處是,萬一哪天碰到一些特殊情況,需要把所有庫存相關緩存全部清空的話,我們可以很容易地取出所有的庫存key並做刪除操作。另外一個好處是,給我們提供了繼續擴展的思路……設想一下,現在最複雜的情況是C類寶物,一共5個維度。假設未來,你不再使用一棟樓的300個房間去販賣寶物,而是多幢樓,那麼用戶在下訂單的時候又要多出一個維度——樓樓編號。碰到這種情況,我們完全可以將這個多出來的庫存Key集合退化為樓棟編號來使用,保證了可能出現的更複雜情況下的擴展性。
在做了這次擴充之後,每次新增預定記錄時,需要注意偵測庫存key集合中是否已經存在對應的redis key值,如果不存在需要將redis key值加入庫存key集合中。刪除操作也類似。
以上是怎麼用Redis做預定庫存快取功能的詳細內容。更多資訊請關注PHP中文網其他相關文章!