一天清晨,我被一個客戶電話驚醒,客戶異常焦急,尋問CDN能不能幫助他們解決「秒殺」的問題,他們昨天剛剛進行了「整點秒殺活動」,結果併發量過大,致使服務宕機,用戶投訴。php
爲了理清思路,我問了對方三個問題:前端
(1)服務宕機的表現是什麼?java
(2)業務的基本架構什麼樣?程序員
(3)秒殺的峯值併發到多少?面試
順着這些線索,咱們先一塊兒還原了應用場景:redis
某電商業務架構圖算法
該公司是一家P2P理財網站,常有用戶在整點搶購高利率理財產品的「整點秒殺活動」。如上圖所示,終端用戶請求先經過前端負載均衡,而後到達運行實際電商邏輯的Web Server;再下層是運行在VM上的8臺Redis,負責存儲與業務相關的Cache數據,如用戶Profile、理財產品信息、用戶帳單信息等。實際落地數據存儲在MySQL中,該MySQL只進行了簡單的分庫分表及讀寫分離。sql
進行「秒殺」時,先由風控和運營人員選好理財產品,而後標記到數據庫中;活動開始由產品人員放開,終端用戶搶購。數據庫
該公司的業務主要來自移動端,平時流量較少,但「秒殺」活動時會瞬間產生大量流量,峯值併發達到10萬以上(其中可能包括bot),如此大的併發主要是集中在如下兩類接口:後端
對於理財產品的刷新接口,相似GET /get_fprod.php?uid={$1}&pid={$2}&sid={$3},此類接口的請求量最多,佔比90%。
對於理財產品的下單接口,相似 GET /order_fprod?uid={$1}&pid={$2}&oid={$3}&sid={$4},此類接口的請求量較少,佔比不到1%,但存在大量504超時。
其中uid是用戶ID,pid是理財產品ID,oid是訂單號,sid是隨着客戶端用戶變化的隨機token標識。
根據與客戶溝通獲得的場景,初步獲得瞭如下結論:
(1)客戶以移動業務爲主,產品經過API在客戶端渲染UI,產品中幾乎沒有靜態資源,帶寬流量不高,傳統CDN沒法達到卸載壓力的做用;
(2)秒殺時,產生大量502/504超時請求,說明此時用戶請求已超過服務端的業務承載能力,急需擴容。
基於以上兩點,我沒有建議該公司採購CDN服務,而是推薦服務擴容,但隨着我方對於業務更深層次的分析,逐漸發現了一些詭異的事情。
(1) 數據庫主從負載極不均衡,經過MySQL管理工具,發現主庫的Query量高達80%;
(2)Redis Cache節點負載極不均衡,經過查看redis info發現,秒殺時,其中一臺Redis請求量極大,佔比達90%以上,其餘Redis請求量卻很低。
上述反常現象激起了雙方技術人員的興趣,這也許就是問題的關鍵!隨着分析深刻,第一個現象的緣由浮出水面:該公司在使用數據庫時,並未如某些大型電商平臺同樣使用數據庫中間件層進行MySQL請求的路由分發,而是在業務代碼端,使用語言層面的框架完成讀寫分離工做。這帶來了兩個弊端:
程序員繞過語言層框架開發,並未真正實施讀寫分離; 產品人員要求展示效果實時,倒逼開發人員修改業務邏輯,會犧牲讀寫分離,使數據都在主庫讀寫。
Cache穿透示意圖
接着,第二個現象的緣由也逐漸清晰:秒殺時,大量用戶訪問極少數理財產品,當這幾個產品的pid剛好被hash到同一個Redis上,就會致使Cache節點熱點失衡,全部請求最終集中在一個Redis,而這個Redis就是業務的瓶頸!
使用數據庫中間件能夠帶來諸多好處,其中最重要的是可對業務層隱藏部分數據庫細節,更好地控制業務。固然,引入數據庫中間層也存在明顯缺點,在業務總體架構中增長一層組件,違反了「簡單有效」的設計原則。對於不少互聯網公司,在早期甚至中期沒有數據庫中間層也很正常。 但當業務發展到必定階段,引入數據庫中間層是利大於弊的。
基於經驗,我方推薦客戶使用MySQL Route,基本能夠知足簡單需求,如:鏈接複用;負載均衡;讀寫分離。
MySQL Route架構圖
上圖是MySQL Router官方架構圖,能夠看到,MySQL Router優點在於插件化設計,官方提供了一系列插件供使用。
除MySQL Router,國內還有不少開源數據庫中間件能夠採用,如阿里、美團等。 使用數據庫中間層,不只能夠解決性能問題,還能在安全方面起到做用,如審計、流量限制等,甚至攔截SQL注入、劣質SQL語句等。
2. 使用API加速服務緩解服務端壓力
Cache服務失衡是比較棘手的問題。「秒殺」時,用戶高頻訪問少數幾個理財產品信息,當其Cache數據恰巧分配在同一節點,大量請求會瞬間集中到一臺或少數幾臺節點,這就是Cache服務失衡的本質緣由。不只在電商「秒殺」場景中,其餘有瞬間熱點訪問的業務類型也會存在這個問題。以微博爲例,曾因明星熱點事件致使接口緩慢甚至服務宕機,歸根到底也是這個緣由。「爆料」的瞬間,一個微博會在短期內海量傳播,該微博ID被同時打開,全部流量會集中到一個Redis節點。
這個問題如何解決?首先,Cache一般以某個數據結構的key爲維度進行hash存儲,大量用戶只訪問一個或幾個key時,將致使Redis Cache節點負載不均衡,這是否必定對服務產生影響,則視併發狀況而定,但這是一個巨大隱患。針對這個問題,客戶提出了一種解決方案:把一個理財產品的Cache數據再拆散,1個key變成多個,下降key被分配到同一Cache節點的機率。但這種方法存在很大弊端:
(1)須要修改代碼,本來一條get請求就能夠完成的邏輯,要換成多條才能拼成;
(2)平常全部get/set操做的時間消耗都將成倍增長,由於1%的熱點事件增長99%常規操做的時間,嚴重違背二八法則。
基於以上問題,咱們推薦客戶使用白山雲聚合CLN-X的「API加速」來解決這個問題。
API加速徹底不一樣於傳統CDN的鏈路加速,經過緩存API返回內容並結合TCP廣域網優化技術,對API請求進行優化。白山API加速將每一個API的response數據毫秒級緩存在全網邊緣節點,節點內存中的response數據以LRU(Least Recently Used)算法交換。在「熱點事件」時,最熱的信息持續保存在邊緣節點,當客戶端訪問該API時,邊緣節點可直接返回結果,沒必要返回源站。整個架構以下:
API加速架構圖
API加速服務在網絡邊緣節點提供對API的加速能力,包括:API返回結果緩存能力、API請求回源網絡加速能力。
傳統觀點認爲,動態資源(API)沒法緩存,但白山提出「任何資源均可以被緩存,只是過時時間不一樣」。對於常見的靜態資源,緩存過時時間較長;而API並不是不能被緩存,只是過時時間很短。如一個查詢股價的API,可設定過時時間爲50毫秒;百米運動員起跑反應時間爲100-200毫秒,50毫秒對於PC端或移動端的用戶體驗並不會形成影響。
沒有緩存時,1秒內若有10000個用戶同時訪問,後端承受10000個併發;若是設置50毫秒的緩存時間,理論上可將後端併發下降到20個(1秒/50毫秒=20),後端負載下降至五百分之一,其餘請求由緩存服務器直接返回給用戶。
綜上所述,白山API加速爲客戶提供毫秒級緩存,在不影響用戶體驗的前提下提升終端用戶響應速度,同時下降服務端的業務負載壓力。
API加速還支持自定義緩存規則,使其更貼近業務,包括QueryString、Header、Path三種類型,針對場景,設定以下規則:
GET /get_fprod.php?uid={$1}&pid={$2}&sid={$3},每一個理財產品都有獨立ID,產品信息不隨用戶ID和客戶端隨機信息變化,所以Cache key可忽略URI中參數的{$1}和{$3},/get_fprod.php?pid={$2}就是在邊緣節點存儲毫秒級的Cache key。
緩存的過時時間如何肯定呢?與業務相關,這須要對客戶提供的脫敏日誌進行分析,可初步設定過時時間爲500毫秒,最後還需考慮RTT修正值,以適應廣域網環境;RTT則由API加速服務自動捕捉並實時更新。
經過爲客戶主要的瓶頸接口配置API加速服務,並在峯值時間,從如下兩個維度對比API加速服務開啓與關閉時的效果:
終端用戶請求平均響應時間和響應碼200比例 服務集羣平均負載 最終效果以下:
如圖A所示,峯值期間終端用戶請求平均響應時間,從3秒左右壓縮至40毫秒之內;如圖B所示,峯值期間全部請求響應碼200的比例從70%左右提高至100%;圖C表示,峯值期間,後端CPU Idle從10%左右提升至97%左右。實測對比數據代表,API加速對下降平均響應時間、提高用戶體驗效果十分顯著,在下降後端服務器負載方面效果更加明顯,使用API加速的後端CPU Idle可保持在91%以上。
針對上面的技術我特地整理了一下,有不少技術不是靠幾句話能講清楚,因此乾脆找朋友錄製了一些視頻,不少問題其實答案很簡單,可是背後的思考和邏輯不簡單,要作到知其然還要知其因此然。若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java進階羣:680130298,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。
具備1-5工做經驗的,面對目前流行的技術不知從何下手,須要突破技術瓶頸的能夠加羣。
在公司待久了,過得很安逸,但跳槽時面試碰壁。須要在短期內進修、跳槽拿高薪的能夠加羣。
若是沒有工做經驗,但基礎很是紮實,對java工做機制,經常使用設計思想,經常使用java開發框架掌握熟練的能夠加羣。
數據庫失衡和緩存Redis失衡問題已經解決,但除上述問題,還有不少環節能夠改善:
1. 隊列服務異步化請求
目前客戶最終落地數據庫請求直接請求到MySQL,未經隊列緩衝,建議使用隊列服務排隊處理峯值請求,其好處在於能在大訪問量時對請求進行調度,並可控制實際到達數據庫的併發,從而有效保護數據庫後端。
2. API防火牆屏蔽惡意Bot
用戶日誌中含有大量明顯且規律的掃描軟件痕跡,如sqlmap、fimap等,雖然還沒有對業務形成較大影響,但卻使服務端資源被佔用。建議在負載均衡最前端對掃描行爲予以屏蔽,以提升安全性,同時提高服務效率。除惡意Bot,搶單、刷單等行爲也會對服務產生影響,建議使用API防禦服務識別與攔截。
3. 產品層考慮服務降級設計
該客戶在總體業務上,沒有服務降級設計,產品功能優先級未作劃分,致使重要的數據庫、Cache等衆多基礎服務混雜。一旦「秒殺」致使數據庫穿透等嚴重問題時,總體服務將不可用。這種狀況應從新梳理業務單元,按照優先級切分基礎服務,首屏、產品列表、購買、訂單等信息優先級最高;其次是非重要功能,如評論、帳單等;若是後端負載較大,必要時可直接捨棄次要功能,從而下降後端負載,保證服務穩定。
解決相似「整點秒殺活動」的情景,是一個系統複雜的工程,就文中客戶暴露出來的數據庫負載不均勻、Cache緩存負載不均勻等問題,可經過採用數據庫中間層和API加速等技術解決,最終可取得理想效果。
上述「秒殺」案例,只是API加速的一個典型應用場景,接下來我還會撰文對API加速問題進行更爲系統的剖析。