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