什么是秒殺
通俗一點(diǎn)講就是網(wǎng)絡(luò)商家為促銷(xiāo)等目的組織的網(wǎng)上限時(shí)搶購(gòu)活動(dòng)
比如說(shuō)京東秒殺算柳,就是一種定時(shí)定量秒殺茴厉,在規(guī)定的時(shí)間內(nèi)吼畏,無(wú)論商品是否秒殺完畢昔汉,該場(chǎng)次的秒殺活動(dòng)都會(huì)結(jié)束懈万。這種秒殺拴清,對(duì)時(shí)間不是特別嚴(yán)格,只要下手快點(diǎn)会通,秒中的概率還是比較大的口予。
淘寶以前就做過(guò)一元搶購(gòu),一般都是限量 1 件商品涕侈,同時(shí)價(jià)格低到「令人發(fā)齒」沪停,這種秒殺一般都在開(kāi)始時(shí)間 1 到 3 秒內(nèi)就已經(jīng)搶光了,參與這個(gè)秒殺一般都是看運(yùn)氣的裳涛,不必太強(qiáng)求
業(yè)務(wù)特點(diǎn)
瞬時(shí)并發(fā)量大
秒殺時(shí)會(huì)有大量用戶(hù)在同一時(shí)間進(jìn)行搶購(gòu)木张,瞬時(shí)并發(fā)訪問(wèn)量突增 10 倍,甚至 100 倍以上都有端三。
庫(kù)存量少
一般秒殺活動(dòng)商品量很少舷礼,這就導(dǎo)致了只有極少量用戶(hù)能成功購(gòu)買(mǎi)到。
業(yè)務(wù)簡(jiǎn)單
流程比較簡(jiǎn)單郊闯,一般都是下訂單妻献、扣庫(kù)存、支付訂單
技術(shù)難點(diǎn)
現(xiàn)有業(yè)務(wù)的沖擊
秒殺是營(yíng)銷(xiāo)活動(dòng)中的一種团赁,如果和其他營(yíng)銷(xiāo)活動(dòng)應(yīng)用部署在同一服務(wù)器上育拨,肯定會(huì)對(duì)現(xiàn)有其他活動(dòng)造成沖擊,極端情況下可能導(dǎo)致整個(gè)電商系統(tǒng)服務(wù)宕機(jī)
直接下訂單
下單頁(yè)面是一個(gè)正常的 URL 地址欢摄,需要控制在秒殺開(kāi)始前熬丧,不能下訂單,只能瀏覽對(duì)應(yīng)活動(dòng)商品的信息怀挠。簡(jiǎn)單來(lái)說(shuō)析蝴,需要 Disable 訂單按鈕
頁(yè)面流量突增
秒殺活動(dòng)開(kāi)始前后,會(huì)有很多用戶(hù)請(qǐng)求對(duì)應(yīng)商品頁(yè)面唆香,會(huì)造成后臺(tái)服務(wù)器的流量突增嫌变,同時(shí)對(duì)應(yīng)的網(wǎng)絡(luò)帶寬增加,需要控制商品頁(yè)面的流量不會(huì)對(duì)后臺(tái)服務(wù)器躬它、DB、Redis 等組件的造成過(guò)大的壓力
架構(gòu)設(shè)計(jì)思想
限流
由于活動(dòng)庫(kù)存量一般都是很少东涡,對(duì)應(yīng)的只有少部分用戶(hù)才能秒殺成功冯吓。所以我們需要限制大部分用戶(hù)流量,只準(zhǔn)少量用戶(hù)流量進(jìn)入后端服務(wù)器
削峰
秒殺開(kāi)始的那一瞬間疮跑,會(huì)有大量用戶(hù)沖擊進(jìn)來(lái)组贺,所以在開(kāi)始時(shí)候會(huì)有一個(gè)瞬間流量峰值。如何把瞬間的流量峰值變得更平緩祖娘,是能否成功設(shè)計(jì)好秒殺系統(tǒng)的關(guān)鍵因素失尖。實(shí)現(xiàn)流量削峰填谷,一般的采用緩存和 MQ 中間件來(lái)解決
異步
秒殺其實(shí)可以當(dāng)做高并發(fā)系統(tǒng)來(lái)處理,在這個(gè)時(shí)候掀潮,可以考慮從業(yè)務(wù)上做兼容菇夸,將同步的業(yè)務(wù),設(shè)計(jì)成異步處理的任務(wù)仪吧,提高網(wǎng)站的整體可用性
緩存
秒殺系統(tǒng)的瓶頸主要體現(xiàn)在下訂單庄新、扣減庫(kù)存流程中。在這些流程中主要用到 OLTP 的數(shù)據(jù)庫(kù)薯鼠,類(lèi)似 MySQL择诈、SQLServer、Oracle出皇。由于數(shù)據(jù)庫(kù)底層采用 B+ 樹(shù)的儲(chǔ)存結(jié)構(gòu)羞芍,對(duì)應(yīng)我們隨機(jī)寫(xiě)入與讀取的效率,相對(duì)較低郊艘。如果我們把部分業(yè)務(wù)邏輯遷移到內(nèi)存的緩存或者 Redis 中涩金,會(huì)極大的提高并發(fā)效率
整體架構(gòu)
客戶(hù)端優(yōu)化
秒殺頁(yè)面
秒殺活動(dòng)開(kāi)始前,其實(shí)就有很多用戶(hù)訪問(wèn)該頁(yè)面了暇仲。如果這個(gè)頁(yè)面的一些資源步做,比如 CSS、JS奈附、圖片全度、商品詳情等,都訪問(wèn)后端服務(wù)器斥滤,甚至 DB 的話(huà)将鸵,服務(wù)肯定會(huì)出現(xiàn)不可用的情況。所以一般我們會(huì)把這個(gè)頁(yè)面整體進(jìn)行靜態(tài)化佑颇,并將頁(yè)面靜態(tài)化之后的頁(yè)面分發(fā)到 CDN 邊緣節(jié)點(diǎn)上顶掉,起到壓力分散的作用
防止提前下單
防止提前下單主要是在靜態(tài)化頁(yè)面中加入一個(gè) JS 文件引用,該 JS 文件包含活動(dòng)是否開(kāi)始的標(biāo)記以及開(kāi)始時(shí)的動(dòng)態(tài)下單頁(yè)面的 URL 參數(shù)挑胸。同時(shí)痒筒,這個(gè) JS 文件是不會(huì)被 CDN 系統(tǒng)緩存的,會(huì)一直請(qǐng)求后端服務(wù)的茬贵,所以這個(gè) JS 文件一定要很小簿透。當(dāng)活動(dòng)快開(kāi)始的時(shí)候(比如提前),通過(guò)后臺(tái)接口修改這個(gè) JS 文件使之生效
API 接入層優(yōu)化
客戶(hù)端優(yōu)化解藻,對(duì)于不是搞計(jì)算機(jī)方面的用戶(hù)還是可以防止住的老充。但是稍有一定網(wǎng)絡(luò)基礎(chǔ)的用戶(hù)就起不到作用了,因此服務(wù)端也需要加些對(duì)應(yīng)控制螟左,不能信任客戶(hù)端的任何操作啡浊。一般控制分為 2 大類(lèi)
限制用戶(hù)維度訪問(wèn)頻率
針對(duì)同一個(gè)用戶(hù)( Userid 維度)觅够,做頁(yè)面級(jí)別緩存,單元時(shí)間內(nèi)的請(qǐng)求巷嚣,統(tǒng)一走緩存喘先,返回同一個(gè)頁(yè)面
限制商品維度訪問(wèn)頻率
大量請(qǐng)求同時(shí)間段查詢(xún)同一個(gè)商品時(shí),可以做頁(yè)面級(jí)別緩存涂籽,不管下回是誰(shuí)來(lái)訪問(wèn)苹祟,只要是這個(gè)頁(yè)面就直接返回
SOA 服務(wù)層優(yōu)化
上面兩層只能限制異常用戶(hù)訪問(wèn),如果秒殺活動(dòng)運(yùn)營(yíng)的比較好评雌,很多用戶(hù)都參加了树枫,就會(huì)造成系統(tǒng)壓力過(guò)大甚至宕機(jī),因此需要后端流量控制
對(duì)于后端系統(tǒng)的控制可以通過(guò)消息隊(duì)列景东、異步處理砂轻、提高并發(fā)等方式解決。對(duì)于超過(guò)系統(tǒng)水位線(xiàn)的請(qǐng)求斤吐,直接采取 「Fail-Fast」原則搔涝,拒絕掉
秒殺整體流程圖
秒殺系統(tǒng)核心在于層層過(guò)濾,逐漸遞減瞬時(shí)訪問(wèn)壓力和措,減少最終對(duì)數(shù)據(jù)庫(kù)的沖擊庄呈。通過(guò)上面流程圖就會(huì)發(fā)現(xiàn)壓力最大的地方在哪里?
MQ 排隊(duì)服務(wù)派阱,只要 MQ 排隊(duì)服務(wù)頂住诬留,后面下訂單與扣減庫(kù)存的壓力都是自己能控制的,根據(jù)數(shù)據(jù)庫(kù)的壓力贫母,可以定制化創(chuàng)建訂單消費(fèi)者的數(shù)量文兑,避免出現(xiàn)消費(fèi)者數(shù)據(jù)量過(guò)多,導(dǎo)致數(shù)據(jù)庫(kù)壓力過(guò)大或者直接宕機(jī)腺劣。
庫(kù)存服務(wù)專(zhuān)門(mén)為秒殺的商品提供庫(kù)存管理绿贞,實(shí)現(xiàn)提前鎖定庫(kù)存,避免超賣(mài)的現(xiàn)象橘原。同時(shí)籍铁,通過(guò)超時(shí)處理任務(wù)發(fā)現(xiàn)已搶到商品,但未付款的訂單靠柑,并在規(guī)定付款時(shí)間后寨辩,處理這些訂單,將恢復(fù)訂單商品對(duì)應(yīng)的庫(kù)存量
Nginx優(yōu)化
- 動(dòng)靜分離歼冰,不走tomcat獲取靜態(tài)資源
server {
listen 8088;
location ~ \.(gif|jpg|jpeg|png|bmp|swf)$ {
root C:/Users/502764158/Desktop/test;
}
location ~ \.(jsp|do)$ {
proxy_pass http://localhost:8082;
}
}
}
- gzip壓縮,減少靜態(tài)文件傳輸?shù)捏w積耻警,節(jié)省帶寬隔嫡,提高渲染速度
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_comp_level 3;
gzip_disable "MSIE [1-6]\.";
gzip_types text/plain application/x-javascript text/css application/xml text/javascript image/jpeg image/gif image/png;
- 配置集群負(fù)載和容災(zāi)甸怕,設(shè)置失效重連的時(shí)間,失效后腮恩,定期不會(huì)再重試掛掉的節(jié)點(diǎn),參數(shù)
- fail_timeout默認(rèn)為10s
- max_fails默認(rèn)為1梢杭。就是說(shuō),只要某個(gè)server失效一次秸滴,則在接下來(lái)的10s內(nèi)武契,就不會(huì)分發(fā)請(qǐng)求到該server上
- proxy_connect_timeout 后端服務(wù)器連接的超時(shí)時(shí)間_發(fā)起握手等候響應(yīng)超時(shí)時(shí)間
upstream netitcast.com {
#服務(wù)器集群名字
server 127.0.0.1:8080;
server 127.0.0.1:38083;
server 127.0.0.1:8083;
}
server {
listen 88;
server_name localhost;
location / {
proxy_pass http://netitcast.com;
proxy_connect_timeout 1;
fail_timeout 5;
}
}
- 集成Varnish做靜態(tài)資源的緩存
- 集成tengine做過(guò)載的保護(hù)
頁(yè)面優(yōu)化
- 降低交互的壓力
- 盡量把js、css文件放在少數(shù)幾個(gè)里面荡含,減少瀏覽器和后端交互獲取靜態(tài)資源的次數(shù)
- 盡量避免在秒殺商品頁(yè)面使用大的圖片咒唆,或者使用過(guò)多的圖片
- 安全控制
- 時(shí)間有效性驗(yàn)證:未到秒殺時(shí)間不能進(jìn)行搶單,并且同時(shí)程序后端也要做時(shí)間有效性驗(yàn)證释液,因?yàn)榫W(wǎng)頁(yè)的時(shí)間和各自的系統(tǒng)時(shí)間決定全释,而且秒殺器可以通過(guò)繞開(kāi)校驗(yàn)直接調(diào)用搶單
- 異步搶單:通過(guò)點(diǎn)擊按鈕刷新?lián)寣殻皇撬⑿马?yè)面的方式搶寶(答題驗(yàn)證碼等等也是ajax交互)
- redis做IP限流
- redis做UserId限流
Redis集群
分布式鎖(悲觀鎖)
緩存熱點(diǎn)數(shù)據(jù)(庫(kù)存):如果QPS太高的話(huà)误债,另一種方案是通過(guò)localcache浸船,分布式狀態(tài)一致性通過(guò)數(shù)據(jù)庫(kù)來(lái)控制
分布式悲觀鎖(參考redis悲觀鎖的代碼)
- 悲觀鎖(因?yàn)榭隙?zhēng)搶嚴(yán)重)
- Expire時(shí)間(搶到鎖后,立刻設(shè)置過(guò)期時(shí)間寝蹈,防止某個(gè)線(xiàn)程的異常停擺李命,導(dǎo)致整個(gè)業(yè)務(wù)的停擺)
- 定時(shí)循環(huán)和快速反饋(for緩存有超時(shí)設(shè)置,每次超時(shí)后箫老,重新讀取一次庫(kù)存封字,還有貨再進(jìn)行第二輪的for循環(huán)爭(zhēng)奪,實(shí)現(xiàn)快速反饋槽惫,避免沒(méi)有貨了還在持續(xù)搶鎖)
- 異步處理訂單
- redis搶鎖成功后周叮,記錄搶到鎖的用戶(hù)信息后,就可以直接釋放鎖界斜,并反饋用戶(hù)仿耽,通過(guò)異步的方式來(lái)處理訂單,提升秒殺的效率各薇,降低無(wú)意義的線(xiàn)程等待
- 為了避免異步的數(shù)據(jù)不同步项贺,需要搶到鎖的時(shí)候,在redis里面緩存用戶(hù)信息列表峭判,緩存結(jié)束后开缎,觸發(fā)搶單成功用戶(hù)信息持久化,并且定時(shí)的比對(duì)一致性
消息隊(duì)列限流
消息隊(duì)列削峰限流(RocketMQ自帶的Consumer自帶線(xiàn)程池和限流措施)林螃,集群奕删。一般都是微服務(wù),訂單中心疗认、庫(kù)存中心完残、積分中心伏钠、用戶(hù)的商品中心
數(shù)據(jù)庫(kù)
- 拆分事務(wù)提高并發(fā)度
- 根據(jù)業(yè)務(wù)需求考慮分庫(kù):讀寫(xiě)分離、熱點(diǎn)隔離拆分谨设,但是會(huì)引入分布式事務(wù)問(wèn)題熟掂,以及跨庫(kù)操作的難度
要執(zhí)行的操作:扣減庫(kù)存、生成新訂單扎拣、生成待支付訂單赴肚、扣減優(yōu)惠券、積分變動(dòng)
庫(kù)存表是數(shù)據(jù)庫(kù)并發(fā)的瓶頸所在二蓝,需要在事務(wù)控制上做權(quán)衡:可以把扣減庫(kù)存設(shè)置成一個(gè)獨(dú)立的事務(wù)誉券,其它操作成一個(gè)大的事務(wù)(訂單、優(yōu)惠券侣夷、積分操作)横朋,提高并發(fā)度,但是要做好額外的check
update 庫(kù)存表 set 庫(kù)存=庫(kù)存-1 where id=** and 庫(kù)存>1 - 為了提升并發(fā)百拓,需要在事務(wù)上做妥協(xié)
單機(jī)上拆分事務(wù):比如扣減庫(kù)存表+(生成待支付訂單+優(yōu)惠券扣減+積分變動(dòng))是一個(gè)大的事務(wù)琴锭,為了提高并發(fā),可以拆分為2個(gè)事務(wù)
分庫(kù)以后引入分布式事務(wù)問(wèn)題,為了保證用戶(hù)體驗(yàn)衙传,最好還是通過(guò)日志分析來(lái)人工維護(hù)决帖,否則阻塞太嚴(yán)重,并發(fā)差
答題驗(yàn)證碼
- 可以防止秒殺器的干擾蓖捶,讓更多用戶(hù)有機(jī)會(huì)搶到
- 延緩請(qǐng)求地回,每個(gè)人的反應(yīng)時(shí)間不同,把瞬間流量分散開(kāi)來(lái)了
- 驗(yàn)證碼的設(shè)計(jì)可以分為2種
驗(yàn)證失敗重新刷新答題(12306):服務(wù)器交互量大俊鱼,每錯(cuò)一次交互一次刻像,但是可以大大降低秒殺器答題的可能性,因?yàn)闆](méi)有試錯(cuò)這個(gè)功能并闲,答題一直在變
驗(yàn)證失敗提示失敗细睡,但是不刷新答題的算法:要么答題成功,進(jìn)入下單界面帝火,要么提示打錯(cuò)溜徙,繼續(xù)答題(不刷新答題,無(wú)須交互犀填,用js驗(yàn)證結(jié)果)蠢壹。
這種方案,可以在加載題目的時(shí)候一起加載MD5加密的答案九巡,然后后臺(tái)再校驗(yàn)一遍图贸,實(shí)現(xiàn)類(lèi)似的防止作弊的效果。好處是不需要額外的服務(wù)器交互。
MD加密答案的算法里面要引入 userId PK這些因素進(jìn)來(lái)來(lái)確保每次答案都不一樣而且沒(méi)有規(guī)律求妹,避免秒殺器統(tǒng)計(jì)結(jié)果集答題的驗(yàn)證:除了驗(yàn)證答案的正確性意外乏盐,還要統(tǒng)計(jì)反應(yīng)時(shí)間佳窑,例如12306的難題制恍,正常人類(lèi)的答題速度最快是1.5s,那么神凑,小于1s的驗(yàn)證可以判定為機(jī)器驗(yàn)證
總結(jié)
層層過(guò)濾净神,盡量將請(qǐng)求攔截在上游,降低下游的壓力溉委,充分利用緩存與消息隊(duì)列鹃唯,提高請(qǐng)求處理速度以及削峰填谷的作用
削峰限流
- 前端+Redis攔截,只有redis扣減成功的請(qǐng)求才能進(jìn)入到下游
- MQ堆積訂單瓣喊,保護(hù)訂單處理層的負(fù)載坡慌,Consumer根據(jù)自己的消費(fèi)能力來(lái)取Task,實(shí)際上下游的壓力就可控了藻三。重點(diǎn)做好路由層和MQ的安全
- 引入答題驗(yàn)證碼洪橘、請(qǐng)求的隨機(jī)休眠等措施,削峰填谷
安全保護(hù)
- 頁(yè)面和前端要做判斷棵帽,防止活動(dòng)未開(kāi)始就搶單熄求,防止重復(fù)點(diǎn)擊按鈕連續(xù)搶單
- 防止秒殺器惡意搶單,IP限流逗概、UserId限流限購(gòu)弟晚、引入答題干擾答題器,并且對(duì)答題器答題時(shí)間做常理推斷
- 過(guò)載丟棄逾苫,QPS或者CPU等核心指標(biāo)超過(guò)一定限額時(shí)卿城,丟棄請(qǐng)求,避免服務(wù)器掛掉铅搓,保證大部分用戶(hù)可用
頁(yè)面優(yōu)化瑟押,動(dòng)靜分離
- 秒殺商品的網(wǎng)頁(yè)內(nèi)容盡可能做的簡(jiǎn)單:圖片小、js css 體積小數(shù)量少狸吞,內(nèi)容盡可能的做到動(dòng)靜分離
- 秒殺的搶寶過(guò)程中做成異步刷新?lián)寣毭阋恍枰脩?hù)刷新頁(yè)面來(lái)?yè)專(zhuān)档头?wù)器交互的壓力
- 可以使用Nginx的動(dòng)靜分離,不通過(guò)傳統(tǒng)web瀏覽器獲取靜態(tài)資源
- nginx開(kāi)啟gzip壓縮蹋偏,壓縮靜態(tài)資源便斥,減少傳輸帶寬,提升傳輸速度
- 或者使用Varnish威始,把靜態(tài)資源緩存到內(nèi)存當(dāng)中枢纠,避免靜態(tài)資源的獲取給服務(wù)器造成的壓力
異步處理
- redis搶單成功后,把后續(xù)的業(yè)務(wù)丟到線(xiàn)程池中異步的處理黎棠,提高搶單的響應(yīng)速度
- 線(xiàn)程池處理時(shí)晋渺,把任務(wù)丟到MQ中镰绎,異步的等待各個(gè)子系統(tǒng)處理(訂單系統(tǒng)、庫(kù)存系統(tǒng)木西、支付系統(tǒng)畴栖、優(yōu)惠券系統(tǒng)),異步操作有事務(wù)問(wèn)題八千,本地事務(wù)和分布式事務(wù)吗讶,但是為了提升并發(fā)度,最好犧牲一致性恋捆。通過(guò)定時(shí)掃描統(tǒng)計(jì)日志照皆,來(lái)發(fā)現(xiàn)有問(wèn)題的訂單,并且及時(shí)處理
熱點(diǎn)分離
盡量的避免秒殺功能給正常功能帶來(lái)的影響沸停,比如秒殺把服務(wù)器某個(gè)功能拖垮了
分離可以提升系統(tǒng)的容災(zāi)性膜毁,但是完全的隔離的改造成本太高了,盡量借助中間件的配置愤钾,來(lái)實(shí)現(xiàn)冷熱分離
- 集群節(jié)點(diǎn)的分離:nginx配置讓秒殺業(yè)務(wù)走的集群節(jié)點(diǎn)和普通業(yè)務(wù)走的集群不一樣瘟滨。
- MQ的分離:避免秒殺業(yè)務(wù)把消息隊(duì)列堆滿(mǎn)了,普通業(yè)務(wù)的交易延遲也特別厲害绰垂。
- 數(shù)據(jù)庫(kù)的分離:根據(jù)實(shí)際的秒殺的QPS來(lái)選擇室奏,熱點(diǎn)數(shù)據(jù)分庫(kù)以后,增加了分布式事務(wù)的問(wèn)題劲装,以及查詢(xún)的時(shí)候跨庫(kù)查詢(xún)性能要差一些(ShardingJDBC有這種功能)胧沫,所以要權(quán)衡以后再?zèng)Q定是否需要分庫(kù)
避免單點(diǎn)
各個(gè)環(huán)節(jié)都要盡力避免
降級(jí)
臨時(shí)關(guān)閉一些沒(méi)那么重要的功能,比如秒殺商品的轉(zhuǎn)贈(zèng)功能占业、紅包的提現(xiàn)功能绒怨,待秒殺峰值過(guò)了,設(shè)置開(kāi)關(guān)谦疾,再動(dòng)態(tài)開(kāi)放這些次要的功能