我這邊是在京東某部門負(fù)責(zé)營銷活動的開發(fā),大家所知道的秒殺其實(shí)就是活動的一種,這些活動有個(gè)共同點(diǎn),就是流量極大.首先每個(gè)商品你都要知道他是不是活動商品?屬于什么活動商品?我有沒有購買資格?活動的開團(tuán)時(shí)間是什么?活動有哪些商品,每個(gè)商品的活動疊加結(jié)果是什么?還有許多活動有庫存的概念,比如秒殺或者限量購,邏輯很復(fù)雜,這里里面有太多太多的細(xì)節(jié),很多細(xì)節(jié)可能沒法一一說清楚,這里總結(jié)些重要的秒殺的問題以及解決方案給大家,
不扯虛的,全部是線上沉淀下來的純干貨;
這里我以我做的一個(gè)最有意思的營銷活動,先給大家介紹一下,不涉及技術(shù),只是后面出案例會以此為案例而已,看技術(shù)可以跳過
我這個(gè)活動叫做《周期循環(huán)購》,這個(gè)活動可以選擇參與的商品,活動支持設(shè)置活動總的有效時(shí)間(比如2022-02-02 12:21~2033-12-02 13:22`),另外我們還支持讓你選擇周一到周日哪幾天是開團(tuán)日(比如周三,周五),每個(gè)開團(tuán)日再進(jìn)行設(shè)置哪幾個(gè)時(shí)間段為開團(tuán)時(shí)段(比如00:00~01:00,12:00:02:00),然后還支持設(shè)置商品在每個(gè)時(shí)段的庫存是多少那么其實(shí)在這里看,團(tuán)購的商品對于我來說就是一個(gè)產(chǎn)品的概念(SPU),而我的營銷活動限制的庫存才是真正的庫存(SKU),另外這個(gè)又涉及到周期性庫存的概念,每天每個(gè)時(shí)間段都有一個(gè)自己的獨(dú)立庫存,這比單一的商品庫存要復(fù)雜許多許多。
正文:
一.明確團(tuán)購秒殺為什么不好做?
不好做,說道理是由于團(tuán)購屬于折扣商品,通常價(jià)格比較低,流量真的極大,并發(fā)訪問下容易造成各種并發(fā)癥,舉幾個(gè)例子如下:
- 服務(wù)器流量,負(fù)載極高,萬一掛了就玩球了,這里可以指業(yè)務(wù)、中間件、數(shù)據(jù)庫等服務(wù)器,比如數(shù)據(jù)庫我們都知道抗壓能力不咋地;
- 流量不均勻,有些時(shí)候會有部分熱門商品(比如首發(fā)的iphone)特別高頻訪問,把服務(wù)器資源全給占用了其他商品就被擱置排隊(duì)了;
- 邏輯復(fù)雜,下單和回滾或者取消訂單要保證冪等,防止數(shù)據(jù)不一致以及超買超賣的可能,萬一造成資損,大家都知道這個(gè)的嚴(yán)重性;
二.如何解決秒殺可能帶來的問題呢?我這里提出一些我在項(xiàng)目里使用的經(jīng)驗(yàn)和技巧
2.1.解決高流量問題
首先流量過高,處理速度慢是根本原因,因此我們最先解決這部分問題;

2.1.1 訪問類請求;
-
隔離動態(tài)數(shù)據(jù)和靜態(tài)數(shù)據(jù),靜態(tài)數(shù)據(jù)可以前置到用戶端或者CDN里,比如商品圖片這玩意,這樣非必要的請求就可以攔截一部分了; - 后端數(shù)據(jù)加
緩存,讓高性能的玩意直面高并發(fā),比如我上面提到的營銷活動,目前基本上做到了查詢?nèi)烤彺婊?,所有查詢?nèi)孔逺edis緩存,大家都知道Redis的性能吧; -
限頻,比如限制用戶對于同一商品詳情頁的刷新次數(shù),大家是不是經(jīng)常會在活動開始的時(shí)候瘋狂刷新....到刷新頻次的時(shí)候可以顯示的提示,也可以默默的請求無效化,另外這種限制盡量前置化, -
層層過濾,另外涉及到數(shù)據(jù)校驗(yàn)的,如果大家的系統(tǒng)有多個(gè)層次(比如前端->api站點(diǎn)等->server),最好做好層層過濾,每個(gè)層次都將一部分?jǐn)?shù)據(jù)攔截了,比如前端可以做限頻,api站點(diǎn)層做風(fēng)控,數(shù)據(jù)校驗(yàn)等等,這樣最后有效請求就少了許多了; -
任務(wù)拆分,尤其IO型,很多時(shí)候我們有些調(diào)用是不需要嚴(yán)格保障串行化的,比如獲取商品詳情接口,可能里面需要展示優(yōu)惠券信息,以及該用戶購買該商品的價(jià)格,庫存余量,那么我們這里可能需要調(diào)用券服務(wù)A,計(jì)價(jià)服務(wù)B,庫存服務(wù)C三個(gè)服務(wù)的接口,這三接口就沒有先后順序,并且都是比較耗時(shí)的服務(wù),串行調(diào)用耗時(shí)A+B+C,比如0.4+0.5+0.5=1.4秒,那么我們多線程分發(fā)任務(wù)去調(diào)用的話,總耗時(shí)就是最長的服務(wù)時(shí)間即0.5秒。
2.2.2 下單類請求(很多和上面一樣的就不說了,比如層層過濾)
削峰,如果我們的流量極大,我們可以進(jìn)行削峰限流,推薦大家采用異步下單的方式,安全可靠,所謂削峰限流我的理解是可以看做銀行柜體,就算流量再大,你都要排隊(duì),能處理多少看銀行的能力,我有一個(gè)銀行就處理慢點(diǎn),有十個(gè)銀行就處理快點(diǎn),沒啥問題,大不了你就辦不成功唄,當(dāng)然這里注意防止丟單,可以采用分布式事務(wù);延緩請求我們可以訂閱系統(tǒng)負(fù)載,或者一些監(jiān)控服務(wù),在系統(tǒng)流量極大的時(shí)候,觸發(fā)答題系統(tǒng),在大家進(jìn)行下單的時(shí)候進(jìn)行答題,那么有的人答題快有的人答題慢,大家拼的就不全是手速了,還有腦速,哈哈,最主要的是把開團(tuán)那一瞬間的流量均勻到答題的時(shí)間了,這點(diǎn)效果顯著;-
精準(zhǔn)限流,比如我們可以采用付定金預(yù)購的方式先把購買人群鎖死,有幾個(gè)好處:- 第一看的人少了,你沒付定金到時(shí)候你看他干啥?
- 第二 買的時(shí)候嘗試下單的人少了,只有付了定金的允許下單
- 第三呢由于定金的存在大家買的時(shí)候會慎重一些減輕了退貨啥的造成無用訂單以及商家賣不出去情況;
-
庫存緩存化,我們可以把庫存做到redis里,我們先采用lua(預(yù)查redis庫存->再預(yù)扣redis的方式)->樂觀鎖扣除mysql庫存- 為什么這里不直接decr預(yù)扣redis要用lua先查redis再預(yù)扣?主要是考慮到由于秒殺團(tuán)購等屬于賠錢引流的生意,所以大部分請求都是遠(yuǎn)遠(yuǎn)大于庫存的,因此極有可能買不到,如果我們直接扣redis,那扣失敗了,咱們不還是要還庫存加回去嗎
- 另外呢,要注意由于庫存的特殊性,我們要保證redis里的數(shù)據(jù)和mysql 的數(shù)據(jù)的一致性,因此我們要使用一些事務(wù)來保證,比如TCC或者M(jìn)Q事務(wù)實(shí)現(xiàn);
- 這里咱們將mysql庫存扣減放到了最后一步也減少了mysql鎖競爭的過程了,這性能飛升??;
2.2.3 一些別的優(yōu)化以及線上環(huán)境積累的服務(wù)高可靠經(jīng)驗(yàn)
-
引入
熱點(diǎn)發(fā)現(xiàn),數(shù)據(jù)隔離,精準(zhǔn)路由機(jī)制,正如我前面所說某些商品確實(shí)牛逼,流量極大,雖然這部分?jǐn)?shù)據(jù)極少,但是卻有可能會擠壓正常服務(wù);- 熱點(diǎn)發(fā)現(xiàn)又分為
靜態(tài)發(fā)現(xiàn)和動態(tài)發(fā)現(xiàn),所謂靜態(tài)發(fā)現(xiàn)呢就是在商品還沒賣,我們就知道這個(gè)是不是熱點(diǎn),比如京東這邊呢就有根據(jù)店鋪流量,過往同類商品售賣情況啥啥的智能打標(biāo)簽服務(wù),有個(gè)預(yù)判,一下就知道你是不是熱點(diǎn)了,動態(tài)發(fā)現(xiàn)是某些商品咱們不知道他這么牛逼,然后賣的時(shí)候一下就火爆了(商家都驚喜的笑醒了); -
數(shù)據(jù)隔離、精準(zhǔn)路由,數(shù)據(jù)隔離,這部分極其熱點(diǎn)的數(shù)據(jù)呢可以把他預(yù)熱到指定的熱點(diǎn)緩存和熱點(diǎn)數(shù)據(jù)庫避免影響普通商品,另外為了避免影響呢,我們還要將這部分商品路由到指定服務(wù)器進(jìn)行操作,路由到指定服務(wù)器還不夠,甚至我們還要加隊(duì)列進(jìn)行排隊(duì),引入極熱點(diǎn)數(shù)據(jù)的異步下單過程,另外優(yōu)化的點(diǎn)就是如果前面已經(jīng)賣完了就不要傻傻的再一個(gè)個(gè)排隊(duì)嘗試了,直接提前結(jié)束,提前通知;
- 熱點(diǎn)發(fā)現(xiàn)又分為
多級緩存機(jī)制,雖然說Redis單機(jī)就十來萬的并發(fā)就比較牛逼了,但終歸還是個(gè)第三方服務(wù)的網(wǎng)絡(luò)查詢,有的時(shí)候redis使用率過高也可能造成瓶頸,如果我們可以將部分不會變的數(shù)據(jù)進(jìn)行本地化,進(jìn)行本地緩存,那這樣是效率最高的,用啥都沒這個(gè)好使,另外也分擔(dān)了redis的壓力。啥是不會變的呢?比如活動信息,很多參與618,1111的秒殺大促活動是不允許隨便變更數(shù)據(jù)的;降級次要請求,在下單等重要服務(wù)壓力極大的情況下,我們可以降級次要請求,將服務(wù)器性能給重要請求用,比如說降級評論查詢,只允許查幾頁,只允許查一層。另外像雙十一這樣的超級活動日,可能會降級很多服務(wù),比如不允許活動日退貨啊啥的;限流比如某接口服務(wù)壓測的極限值是5W qps,我們就設(shè)置為4.5W為限流值,防止服務(wù)突破系統(tǒng)瓶頸,并做到既可以人工執(zhí)行開關(guān),也支持自動化保護(hù)的措施。這個(gè)一般公司的網(wǎng)關(guān)服務(wù)或者混沌工程里都會提供該服務(wù);有一點(diǎn)需要注意的是,這里的限流其實(shí)也不只是保護(hù)自己的服務(wù),其他作為被調(diào)用測的下游小伙伴的服務(wù)這里也起到的保護(hù)作用。兜底拒絕服務(wù),根據(jù)系統(tǒng)資源的情況,為了防止系統(tǒng)產(chǎn)生不可控的情況,要做到考慮最壞的情況,那么最后一招就是直接拒絕服務(wù)了。
當(dāng)系統(tǒng)負(fù)載達(dá)到一定閾值時(shí),例如 CPU 使用率達(dá)到 90% 或者系統(tǒng) load 值達(dá)到 2*CPU 核數(shù)時(shí),系統(tǒng)直接拒絕所有請求,這種方式是最暴力但也最有效的系統(tǒng)保護(hù)方式。例如秒殺系統(tǒng),我們在如下幾個(gè)環(huán)節(jié)設(shè)計(jì)過載保護(hù):機(jī)會檢查,庫存檢查,下單
在最前端的網(wǎng)關(guān)上設(shè)置過載保護(hù),當(dāng)機(jī)器負(fù)載達(dá)到某個(gè)值時(shí)直接拒絕 HTTP 請求并返回 503 錯(cuò)誤碼,在 Java 層同樣也可以設(shè)計(jì)過載保護(hù)。
拒絕服務(wù)可以說是一種不得已的兜底方案,用以防止最壞情況發(fā)生,防止因把服務(wù)器壓跨而長時(shí)間徹底無法提供服務(wù)。像這種系統(tǒng)過載保護(hù)雖然在過載時(shí)無法提供服務(wù),但是系統(tǒng)仍然可以運(yùn)作,當(dāng)負(fù)載下降時(shí)又很容易恢復(fù),所以每個(gè)系統(tǒng)和每個(gè)環(huán)節(jié)都應(yīng)該設(shè)置這個(gè)兜底方案,對系統(tǒng)做最壞情況下的保護(hù)。依賴隔離-熔斷
熔斷器與保險(xiǎn)絲有些類似,當(dāng)電流過大時(shí),保險(xiǎn)絲自動熔斷以保護(hù)我們的電器。這里的熔斷也是一樣,當(dāng)?shù)谌椒?wù)長期不可用,我們要制定快速失敗策略,防止影響或者拖垮我方服務(wù). 這也是一個(gè)十分重要的點(diǎn)!
假設(shè)在沒有熔斷機(jī)制保護(hù)下,我們可能會無數(shù)次的重試或慢請求,勢必持續(xù)加大服務(wù)端壓力或系統(tǒng)耗盡業(yè)務(wù)線程,造成惡性循環(huán);如果直接關(guān)閉重試功能,當(dāng)服務(wù)端又可用的時(shí)候,我們?nèi)绾位謴?fù)?
當(dāng)請求失敗比率(失敗/總數(shù))達(dá)到一定閾值后,熔斷器開啟,并休眠一段時(shí)間,這段休眠期過后熔斷器將處與半開狀態(tài)(half-open),在此狀態(tài)下將試探性的放過一部分流量(Hystrix只支持single request),如果這部分流量調(diào)用成功后,再次將熔斷器閉合,否則熔斷器繼續(xù)保持開啟并進(jìn)入下一輪休眠周期。
建議使用場景:Client端直接調(diào)用遠(yuǎn)程的Server端(server端由于某種原因不可用,從client端發(fā)出請求到server端超時(shí)響應(yīng)之間占用了系統(tǒng)資源,如內(nèi)存,數(shù)據(jù)庫連接等)或共享資源。-
異常報(bào)警,異常告警一定要有,這個(gè)屬于危機(jī)處理,有哪些方面需要關(guān)注的呢?- 服務(wù)器資源告警,涉及CPU、內(nèi)存、帶寬、線程、磁盤空間等
- 服務(wù)質(zhì)量告警,涉及tp99閾值、FullGC、異常code、超時(shí)時(shí)間、qps閾值等等
- 中間件質(zhì)量告警,使用了啥一般都需要添加告警規(guī)則,我這里說一些常見的
- Redis(內(nèi)存使用率(尤其設(shè)置了拒絕淘汰一定要關(guān)注),tps,相應(yīng)時(shí)間,bigkey,流入流出流量,hotkey訂閱和告警)
- MQ(主要關(guān)注擠壓消息量,內(nèi)存,CPU,消費(fèi)者數(shù)量異常變動告警)
- Mysql(內(nèi)存,CPU,磁盤空間,連接數(shù),阻塞線程,等待線程,超時(shí)線程等等)
- 分布式任務(wù)調(diào)度平臺(時(shí)間,積壓數(shù)據(jù),超時(shí)等)
- ES,銀河,混沌,鷹眼等等等其他平臺類的或者不常用的這里不說了,自己衡量吧.........
三、應(yīng)急處理原則
- 1、保證自己能存活,死道友不死貧道(我們也只能做到自己的可用問題)
- 2、若已雪崩,快速止損,恢復(fù)可用服務(wù)
- 3、若自己屬于下游,接近瓶頸,聯(lián)系調(diào)用方,協(xié)商限流。
題外話:真正優(yōu)化的地方還有很多很多很多,服務(wù)架構(gòu)也是非常復(fù)雜的,我做項(xiàng)目請教了下這邊做商品服務(wù)的架構(gòu)師,據(jù)他給我說的雙十一商品詳情頁服務(wù),光CPU就用了幾萬核。。。讓我當(dāng)場就震驚了。。。也給我看了下架構(gòu)和服務(wù)的圖,確實(shí)非常非常牛逼!目前京東這邊在備戰(zhàn)春晚紅包,流量按照雙十一的十倍以上去備戰(zhàn),真心期待后面有關(guān)于這次備戰(zhàn)的分享~~
今天關(guān)于應(yīng)對高流量,我就說到這了,后面有時(shí)間還會補(bǔ)點(diǎn)細(xì)節(jié)
關(guān)于下單,回滾訂單,定時(shí)取消訂單等可以看我后面寫的這個(gè)http://www.itdecent.cn/p/a844a1592902
關(guān)于有效訂單的高并發(fā)問題可以看http://www.itdecent.cn/p/552c4093832e (需要先看上面再看這個(gè))
