購物車系統的存儲設計
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 表示:
{
"cart": [
{
"SKUID": 8888,
"timestamp": 1578721136,
"count": 1,
"selected": true
},
{
"SKUID": 6666,
"timestamp": 1578721138,
"count": 2,
"selected": false
}
]
}
5 用戶購物車 存儲設計
用戶購物車須保證多端數據同步,數據須保存在服務端。常規思路:設計一張購物車表,把數據存在 MySQL。表結構同樣參照實體模型:
需在 user_id 建索引,因爲查詢購物車表,都以 user_id 作爲查詢條件。
也可選擇更快的 Redis 保存購物車數據:
-
• 用戶 ID=Key
-
• Redis 的 HASH=Value,保存購物車中的商品
如:
{
"KEY": 6666,
"VALUE": [
{
"FIELD": 8888,
"FIELD_VALUE": {
"timestamp": 1578721136,
"count": 1,
"selected": true
}
},
{
"FIELD": 6666,
"FIELD_VALUE": {
"timestamp": 1578721138,
"count": 2,
"selected": false
}
}
]
}
爲便理解,用 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 給購物車庫做緩存,技術可行。但考慮:
-
• 值得嗎?每個人的購物車都不一樣,所以這個緩存它的讀寫比差距不會很大,緩存命中率不會太高,緩存收益有限,爲維護緩存,還會增加系統複雜度。所以我們就要自行權衡一下,是不是值得的問題。除非超大規模系統,否則沒必要設置這緩存
-
• 若非要做這樣一個緩存,用什麼緩存更新策略?
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/lBS7IuJoE8cpRVkhh2lj9g