購物車系統的存儲設計
1、主要功能
在用戶選購商品時,下單前,暫存用戶想購買的商品。
購物車對數據可靠性要求不高,性能也無特別要求,在整個電商系統是相對容易設計和實現的一個子系統。
購物車系統的主要功能:
- 把商品加入購物車(后文稱“加購”)
- 購物車列表頁
- 發起結算下單
- 在所有界面都要顯示的購物車小圖標
支撐這些功能,存儲模型如何設計?
只要一個“購物車”實體。
2、主要屬性
打開京東購物車頁面:SKUID(商品ID)、數量、加購時間和勾選狀態
備注:圖片來源于網絡,僅供本文介紹、評論及說明某問題,適當引用。
“勾選狀態”屬性,即在購物車界面,每件商品前面的那個小對號,表示在結算下單時,是否要包含這件商品。至于商品價格和總價、商品介紹等都能實時從其他系統獲取,無需購物車系統保存。
購物車功能簡單,但設計購物車系統的存儲時,仍有一些問題需考慮。
3、原則
3.1 思考
3.1.1 用戶未登錄,在瀏覽器中加購,關閉瀏覽器再打開,剛才加購的商品還在嗎?
存在。
若用戶未登錄,加購的商品也會被保存在用戶的電腦。即使關閉瀏覽器再打開,購物車的商品仍存在。
3.1.2 用戶未登錄,在瀏覽器中加購,然后登錄,剛才加購的商品還在嗎?
存在。
若用戶先加購,再登錄。登錄前加購的商品就會被自動合并到用戶名下,所以登錄后購物車中仍有登錄前加購的商品。
3.1.3 關閉瀏覽器再打開,上一步加購的商品還在嗎?
不存在。
關閉瀏覽器再打開,這時又變為未登錄狀態,但是之前未登錄時加購的商品已經被合并到剛剛登錄的用戶名下了,所以購物車是空的。
3.1.4 再打開手機,用相同的用戶登錄,第二步加購的商品還在嗎?
存在。使用手機登錄相同的用戶,看到的就是該用戶的購物車,這時無論你在手機App、電腦還是微信中登錄,只要相同用戶,看到就是同一購物車,所以第2步加購的商品是存在的。
若不仔細把這些問題考慮清楚,用戶使用購物車時,就會感覺不好用,不是加購的商品莫名其妙丟了,就是購物車莫名其妙多出一些商品。
要解決上面這些問題,只要在存儲設計時,把握如下
3.2 原則
- 若未登錄,需臨時暫存購物車的商品
- 用戶登錄時,把暫存購物車的商品合并到用戶購物車,并清除暫存購物車
- 用戶登錄后,購物車中的商品,需在瀏覽器、手機APP和微信等等這些終端保持同步
購物車系統需保存兩類購物車:
- 未登錄情況下的“暫存購物車”
- 登錄后的“用戶購物車”
4 “暫存購物車”存儲設計
4.1 保存在客戶端or服務端?
若存在服務端,則每個暫存購物車都得有個全局唯一標識,這不易設計。保存在服務端,還要浪費服務端資源。所以,肯定保存在客戶端:
- 節約服務器存儲資源
- 無購物車標識問題每個客戶端就保存它自己唯一一個購物車即可,無需標識。
客戶端存儲可選擇不多:
- Session不太合適。SESSION保留時間短,且SESSION的數據實際上還是保存在服務端
- Cookie
- LocalStorage瀏覽器的LocalStorage和App的本地存儲類似,都以LocalStorage代表。Cookie、LocalStorage都可用來保存購物車數據。
選擇哪種更好?各有優劣。這場景中,使用Cookie和LocalStorage最關鍵區別:
- 客戶端、服務端的每次交互,都會自動帶著Cookie數據往返,這樣服務端可讀寫客戶端Cookie中的數據
- LocalStorage里的數據,只能由客戶端訪問
使用Cookie存儲,實現簡單,加減購物車、合并購物車過程,由于服務端可讀寫Cookie,這樣全部邏輯都可在服務端實現,并且客戶端和服務端請求的次數也相對少。
使用LocalStorage存儲,實現相對復雜,客戶端和服務端都要實現業務邏輯,但LocalStorage好在其存儲容量比Cookie的4KB上限大得多,而且不用像Cookie那樣,無論用不用,每次請求都要帶著,可節省帶寬。
所以,選擇Cookie或LocalStorage存儲“暫存購物車”都行,根據優劣勢選型即可:
- 設計的是個小型電商,Cookie存儲實現起來更簡單
- 你的電商是面那種批發的行業用戶,用戶需加購大量商品,Cookie可能容量不夠用,選擇LocalStorage更合適
不管哪種存儲,暫存購物車保存的
4.2 數據格式
都一樣。參照實體模型設計,JSON表示:
5 用戶購物車 存儲設計
用戶購物車須保證多端數據同步,數據須保存在服務端。常規思路:設計一張購物車表,把數據存在MySQL。表結構同樣參照實體模型:
需在user_id建索引,因為查詢購物車表,都以user_id作為查詢條件。
也可選擇更快的Redis保存購物車數據:
- 用戶ID=Key
- Redis的HASH=Value,保存購物車中的商品
如:
為便理解,用JSON表示Redis中HASH的數據結構:
- KEY中的值6666是用戶ID
- FIELD存放商品ID
- FIELD_VALUE是個JSON字符串,保存加購時間、商品數量和勾選狀態
讀寫性能,Redis比MySQL快得多,Redis就一定比MySQL好嗎?
5.1 MySQL V.S Redis 存儲
- Redis性能比MySQL高出至少一個量級,響應時間更短,支撐更多并發請求
- MySQL數據可靠性好于Redis,因為Redis異步刷盤,若服務器掉電,Redis有可能丟數據。但考慮到購物車里的數據,對可靠性要求不高,丟少量數據的后果也就是,個別用戶的購物車少了幾件商品,問題不大。所以,購物車場景,Redis數據可靠性不高這個缺點,不是不能接受
- MySQL另一優勢:支持豐富的查詢方式和事務機制,但對購物車核心功能無用。但每個電商系統都有它個性化需求,若需以其他方式訪問購物車數據,如統計今天加購的商品總數,這時,使用MySQL存儲數據,易實現,而使用Redis存儲,查詢麻煩且低效
綜合比較下來,考慮到需求變化,推薦MySQL存儲購物車數據。若追求性能或高并發,也可選擇使用Redis。
設計存儲架構過程就是不斷抉擇過程。很多情況下,可選擇方案不止一套,選擇時需考慮實現復雜度、性能、系統可用性、數據可靠性、可擴展性等。這些條件每一個都不是絕對不可以犧牲的,不要讓一些“所謂的常識”禁錮思維。
比如,一般認為數據絕不可丟,即不能犧牲數據可靠性。但用戶購物車存儲,使用Redis替代MySQL,就是犧牲數據可靠性換取高性能。很低概率的丟失少量數據可接受。性能提升帶來的收益遠大于丟失少量數據而付出的代價,這選擇就值得。
如果說不考慮需求變化這個因素,犧牲一點點數據可靠性,換取大幅性能提升,Redis是最優解。
6 總結
- 購物車系統的主要功能包括:加購、購物車列表頁和結算下單
- 核心實體:只有一個“購物車”實體
- 至少包括:SKUID、數量、加購時間和勾選狀態屬性
在給購物車設計存儲時,為確保:
- 購物車內的數據在多端一致
- 用戶登錄前后購物車內商品能無縫銜接
除了每個用戶的“用戶購物車”,還要實現一個“暫存購物車”保存用戶未登錄時加購的商品,并在用戶登錄后自動合并“暫存購物車”和“用戶購物車”。
暫存購物車存儲在客戶端瀏覽器或App,可存放到Cookie或LocalStorage。用戶購物車保存在服務端,可以選擇使用:
- Redis存儲會有更高的性能,可以支撐更多的并發請求
- MySQL是更常規通用的方式,便于應對變化,系統擴展性更好
思考
既然用戶的購物車數據存放在MySQL或Redis各有優劣。那能否把購物車數據存在MySQL,并用Redis緩存?不就兼顧二者優勢?若可行,如何保證Redis中的數據和MySQL數據一致性?
用Redis給購物車庫做緩存,技術可行。但考慮:
- 值得嗎?每個人的購物車都不一樣,所以這個緩存它的讀寫比差距不會很大,緩存命中率不會太高,緩存收益有限,為維護緩存,還會增加系統復雜度。所以我們就要自行權衡一下,是不是值得的問題。除非超大規模系統,否則沒必要設置這緩存
- 若非要做這樣一個緩存,用什么緩存更新策略?