秒殺系統(tǒng)解決兩個(gè)問題:一個(gè)是并發(fā)讀、一個(gè)是并發(fā)寫。
對(duì)應(yīng)到實(shí)際的系統(tǒng)開發(fā)在于高可用、一致性、高性能。
高性能
- 前端頁(yè)面進(jìn)行動(dòng)靜分離,將靜態(tài)頁(yè)面緩存。
在實(shí)際的秒殺系統(tǒng)中我們發(fā)現(xiàn)其實(shí)只有秒殺時(shí)間在動(dòng)態(tài)的變化。因此我們可以將用戶的個(gè)人數(shù)據(jù)以及秒殺時(shí)間等動(dòng)態(tài)數(shù)據(jù)與靜態(tài)數(shù)據(jù)進(jìn)行分離,將靜態(tài)數(shù)據(jù)進(jìn)行緩存。
- 緩存技術(shù):瀏覽器緩存,服務(wù)端緩存,cdn緩存
- 瀏覽器緩存:需要用戶刷新頁(yè)面才可以進(jìn)行變化,系統(tǒng)很難主動(dòng)的將消息推送給用戶,導(dǎo)致很長(zhǎng)一段時(shí)間看到的頁(yè)面都是不變的。
- 服務(wù)端緩存:將頁(yè)面緩存到服務(wù)端。在我的這個(gè)系統(tǒng)中就是將相對(duì)靜態(tài)的頁(yè)面比如商品詳情頁(yè)這些緩存到服務(wù)端redis中,采用springwebcontex與thymeleaf方法對(duì)其進(jìn)行渲染放到緩存中,然后直接從緩存中取。然后根據(jù)系統(tǒng)要求設(shè)置對(duì)應(yīng)的過期時(shí)間。
- cdn緩存:cdn緩存是一種可能在實(shí)際系統(tǒng)中更可用的方案。但是將數(shù)據(jù)放到cdn緩存中需要考慮緩存失效問題與高命中問題。保證系統(tǒng)在秒級(jí)時(shí)間內(nèi)統(tǒng)一失效和高命中率是比較關(guān)鍵的。
數(shù)據(jù)整合。將靜態(tài)數(shù)據(jù)與動(dòng)態(tài)數(shù)據(jù)整合主要有三種方案。
- CSI:服務(wù)器只返回靜態(tài)頁(yè)面,前端通過JS異步請(qǐng)求動(dòng)態(tài)數(shù)據(jù)。本系統(tǒng)就是通過ajax異步對(duì)數(shù)據(jù)進(jìn)行獲取。用戶的體驗(yàn)度較低。
- SSI:采用相對(duì)應(yīng)的SSI注釋行命令加載頁(yè)面。缺點(diǎn)是不能加載包含其他服務(wù)器上的文件。
- ESI:先請(qǐng)求動(dòng)態(tài)數(shù)據(jù)再將動(dòng)態(tài)數(shù)據(jù)與靜態(tài)數(shù)據(jù)一起返回,用戶看到的是一個(gè)完整的頁(yè)面。對(duì)服務(wù)其的性能要求較高。
- 熱點(diǎn)操作與熱點(diǎn)數(shù)據(jù)進(jìn)行優(yōu)化
- 一般來說,零點(diǎn)刷新、下單與添加購(gòu)物車都屬于熱點(diǎn)操作,可以提前對(duì)這些操作設(shè)置一些限制,比如限制用戶點(diǎn)擊的最大次數(shù)或者在某個(gè)時(shí)間段內(nèi)不能重復(fù)點(diǎn)擊。本系統(tǒng)就限制了某個(gè)時(shí)間段內(nèi)的最大點(diǎn)擊數(shù),同樣將點(diǎn)擊次數(shù)放在redis中。
熱點(diǎn)數(shù)據(jù)的處理
- 靜態(tài)熱點(diǎn)數(shù)據(jù)。可以提前將熱點(diǎn)商品數(shù)據(jù)整理出來放入緩存中。
- 動(dòng)態(tài)熱點(diǎn)數(shù)據(jù)。在實(shí)際秒殺過程中異步采集每個(gè)環(huán)節(jié)的熱點(diǎn)key信息然后進(jìn)行相關(guān)聚合分析對(duì)其進(jìn)行處理。
熱點(diǎn)數(shù)據(jù)隔離
- 我們可以將整個(gè)業(yè)務(wù)進(jìn)行分離。將這個(gè)業(yè)務(wù)申請(qǐng)單獨(dú)的域名進(jìn)行相關(guān)處理。
- 可以將秒殺系統(tǒng)的數(shù)據(jù)庫(kù)單獨(dú)分離部署,以免影響到其他業(yè)務(wù)。
本系統(tǒng)將待秒殺商品的信息放入redis緩存中,然后使用rabbitmq消息隊(duì)列對(duì)其進(jìn)行限流。
一致性
一致性我覺得就是一個(gè)庫(kù)存和重復(fù)下單的問題。
這在我在實(shí)際開發(fā)的過程中也有遇到過,在把秒殺下單的基本邏輯實(shí)現(xiàn)后采用jmeter進(jìn)行壓測(cè)時(shí)發(fā)現(xiàn)存在庫(kù)存為負(fù)與一個(gè)用戶重復(fù)下單的情況。
針對(duì)超賣這個(gè)問題首先將所有的sql語句都進(jìn)行了庫(kù)存的判斷,但發(fā)現(xiàn)還是會(huì)有問題,然后嘗試使用了redis分布式鎖進(jìn)行實(shí)現(xiàn),解決了超賣這個(gè)問題,但是發(fā)現(xiàn)帶來的消耗很大。
針對(duì)重復(fù)下單這個(gè)問題,在剛開始和最后的下單的時(shí)候?qū)?shù)據(jù)庫(kù)采用count 1這樣的指令對(duì)用戶是否重復(fù)下單進(jìn)行判斷,另外也在數(shù)據(jù)庫(kù)中建立唯一索引。
最后通過redis預(yù)減庫(kù)存和rabbitmq異步處理訂單消息解決一致性的問題。
將庫(kù)存放到redis緩存中,通過redis緩存進(jìn)行預(yù)減,設(shè)置一個(gè)hashmap,當(dāng)對(duì)應(yīng)商品的庫(kù)存為0時(shí)將標(biāo)志位置為false,減少對(duì)redis的訪問。
rabbitmq異步處理,庫(kù)存充足,訂單生成成功,更新數(shù)據(jù)庫(kù)。
高可用
保證秒殺系統(tǒng)的可用性,需要削減流量峰值
- 本系統(tǒng)首先在采用驗(yàn)證碼操作在入口處就削減流量峰值
- 采用rabbitmq消息隊(duì)列來異步處理消息