完整秒殺架構的設計到技術關鍵點的“情報資訊”
theme: smartblue
小知識,大挑戰!本文正在參與“程式設計師必備小知識”創作活動。
前提宣告
本篇內容完全是筆者自己對技術分析和總結沉澱,由於筆者技術和能力有限,如果有不對的地方,還望大家多多見諒和包涵,並且多多指正留言,謝謝。
秒殺系統-情報背景
相信大家都接觸過新浪微博、淘寶、京東等等這些訪問量較為巨大的平臺以及網站,針對於“高流量”、“高併發”來講,更是我們【技術開發者】都要面臨的的一個很難的“包袱”難題。哎,看來如果要在這行混下去,即使你可能沒有接觸高併發場景,也要自己創造“高併發”進行迎難而上,因為只有這樣子我們才可以更進一步啊!
秒殺系統-情報介紹
對於今天我們要介紹的內容就屬於高併發的一個最極端的場景之一:“秒殺”,這個名詞一般會在“大促”的時候出現,當然也會在某些平臺活動上出現,那麼肯定會有小夥伴會說,秒殺系統要注意哪些問題啊!為啥會比較難呢,難在哪裡啊!
秒殺系統- 特點分析
-
瞬時劇增:在某一個時刻開始進入流量(很少會有熱身以及緩慢增長機制),秒殺時大量使用者會在同一時間,搶購同一商品,網站瞬時流量激增。
-
僧多粥少:商品的庫存是有限的,秒殺請求下的訂單數量會遠遠大於庫存數量,只有少部分使用者能夠秒殺成功。
-
資源鎖定:秒殺業務流程比較簡單,一般就是下訂單減庫存。庫存就是使用者爭奪的“資源”,實際被消費的“資源”不能超過計劃要售出的“資源”,也就是不能被“超賣”。
秒殺系統- 難度分析
它的難度就在於要完成一個“60-100分”的秒殺系統,那麼它必須要要至少兼顧以下這三個方面,才算合格,這三個“惡魔”分別叫“服務可用性”、“資料一致性”和“快速響應性”,有點“苛刻”!
在我們現在的場景下,很難再去考慮一個非分散式系統的架構了。(分散式架構)相信大家都知道CAP理論吧!沒事不知道也沒關係,可見內容:
CAP理論又稱CAP定理,它說的是在一個分散式系統中,服務(資料)層面的一致性(Consistency)、服務自身的可用性(Availability)、網路不同節點分割槽容錯性(Partition tolerance)。
A和C相信大家從字面上都可以理解了,這裡要宣告一下比較陌生的P:它代表如果要保證不同的節點即使在網路出現問題的時候仍能夠訪問到資料,那麼最直接的辦法就是冗餘賦值節點,否則一切都是空談,所以作為一個分散式系統而言,無法忽略P,我們可以理解它就是A和C的基礎。
CAP體系總結
-
只保證AC就是一個單體應用,根本不是分散式。意義當然有,在分散式出現之前都是這麼搭系統。倘若這個系統的節點之一掛了,不會發生腦裂而是整個系統直接宕掉。
-
進一步說如果網路中存在的節點越多,分割槽容忍性越高,但要複製更新的資料就越多,一致性就越難保證。
-
為了保證一致性,更新所有節點資料所需要的時間就越長,可用性就會降低。
以上三者成為了“矛盾論”,而CAP原則指的是,這三個要素最多隻能同時實現兩點,不可能三者兼顧。
回到我們的主體:秒殺三要素,它們三個可不完全等同於CAP三要素,甚至比它們的要求更高,甚至是基於前三者的一個更高層次的水平要求。
服務的可用性(Availability)
服務可用性,是在於高併發流量的衝擊下,仍然可以保持服務的可用性並且還要保證一直可以輸出對外界的服務能力,不會造成宕機以及資源損壞,即使在記憶體和網路甚至硬體資源有限的情況下,也不會被擊垮“死亡”。
比如就像你養魚,你玩命的給魚放飼料,而超過了魚能夠承受的量,它受不了了活活被噎死或者撐死了,這魚就像你的系統一樣,一定要保證魚的健康啊!
資料的一致性(Consistency)
都知道,我們開發的程式以及現在多數的伺服器,比如資料庫,他們在處理資料的時候,很有可能會存在多個執行緒同時在修改同一行資料或者同一塊記憶體,在Java角度而言本身也會存在不一致的問題,而在程式和中介軟體的角度而言,也是一樣,會出現同一時刻在資料修改順序的亂序化,以及資料的紊亂,造成資料的重複操作,造成與我們預期的設想不同。
-
除非你可以實現序列化,一條一條處理,不讓它們同一時刻就行修改或者操作資料,這個是最本質且最安全的辦法,但是也是最影響效能的辦法。(悲觀鎖、同步佇列)。
-
此外還有一種辦法就是,時時刻刻在原子層級,也就是最接近底層的計算機修改資料的時候,或者在所有節點之間建立一個應用層級的中間彙總幹路點(redis或者database的主幹點),上面加入寫屏障和讀屏障,在修改之前,在進行一次校驗判斷,如果資料與預期不同,就不進行修改。這就是著名的樂觀鎖!
服務快速響應性(Quick Response)
一般來講這個屬於使用者體驗,一個較為合格的秒殺系統,是不應該讓使用者漫長的等待而是最好儘可能快速反饋結果。
-
要做成快速響應,就不需要是非同步返回,直接快速響應。
-
此外還需要儘快幫助使用者計算資料,直接返回。
總結一下
-
(非同步返回+同步處理)總結一下就是非同步中套用者同步進行計算,既可以保證快速響應,又可以保證資料的一致性。
-
(非同步返回+樂觀鎖處理)總結一下就是非同步中套用者樂觀鎖進行計算,既可以保證快速響應,又可以保證資料的一致性。
情報分析結束後,我們要重頭戲!進行技術分析了。
秒殺系統-架構設計
我們將秒殺架構進行一下劃分,大體分為三個層級進行分析:由外到內進行分析,分別是:應用層、服務層、資料訪問層。
秒殺架構設計點
應用層架構設計
動靜分離+CDN技術
動靜分離分析
-
場景分析:在秒殺活動開啟之前,使用者一般都會嘗試不斷的重新整理瀏覽器頁面(俗稱F5)以保證不會錯過秒殺活動的商品。
-
按照常用的網站應用架構:
- 我們假設,如果這些無用的請求,頻繁的衝擊我們的後臺伺服器,比如說經過:Web伺服器(LVS、Nginx等)->應用伺服器(tomcat或者Jetty等)、連線資料庫(MySQL),者無疑會對後端服務以及伺服器造成非常大的壓力。
-
解決方案:重新設計秒殺商品頁面,不使用網站原來的商品詳細頁面,頁面內容靜態化,減少/隔絕無用的請求經過後端服務。
CDN技術分析
突然增加的網路及伺服器頻寬
網站的靜態頁面資料大小100K,那麼需要的網路和伺服器頻寬是2G(100K×10000),這些網路頻寬是因為秒殺活動新增的,超過網站平時使用的頻寬。
即使將動態業務轉換為靜態化頁面,但是秒殺活動會非常劇烈的增加的網路頻寬的消耗,同時並不會減輕前端網站伺服器的壓力,所以如果可以的話,需要再進一步將秒殺商品頁面快取在CDN,而不在是單純的我們的前端Nginx伺服器層面,所以需要和CDN服務商臨時租借新增的出口頻寬。
防止快取干擾頁面重新整理為秒殺頁面
通過javascript檔案進行傳遞隨機號+狀態位!
在秒殺商品靜態頁面中加入一個JavaScript檔案引用,該JavaScript檔案中包含秒殺開始標誌為否;
-
當秒殺開始的時候生成一個新的JavaScript檔案(檔名保持不變,只是內容不一樣),更新秒殺開始標誌為是,加入下單頁面的URL及隨機數引數(這個隨機數只會產生一個,即所有人看到的URL都是同一個,伺服器端可以用redis這種分散式快取伺服器來儲存隨機數),並被使用者瀏覽器載入,控制秒殺商品頁面的展示。
-
這個JavaScript檔案的載入可以加上隨機版本號(例如xx.js?v=32353823),這樣就不會被瀏覽器、CDN和反向代理伺服器快取。
-
這個JavaScript檔案非常小,即使每次瀏覽器重新整理都訪問JavaScript檔案伺服器也不會對伺服器叢集和網路頻寬造成太大壓力。
總結一下:前端秒殺頁面使用專門的頁面,這些頁面包括靜態的 HTML 和動態的 JS,他們都需要在 CDN 上快取。
根據UID限制頻率熱度
為了控制公平性原則,由於黃牛或者一些黑客達人,會採用”高科技“,比如說,採用爬蟲指令碼操作,瘋狂的去重新整理頁面。為了防止一些人的破壞以及公平分散,所以採用同一個標準去控制UID(使用者ID)去訪問頻率資訊,當超過每個人所需要達到的頻率閾值,就要進行限制互動視窗內能夠訪問重新整理的資料量!
例如:可以用Redis給每個使用者做訪問統計,根據使用者的ID和商品的標識雙方面進行對使用者對某一個商品的訪問頻率控制,超過訪問頻率後,就會將他的請求暫時性熔斷。
反向代理+負載均衡
- 秒殺系統必然是一個集群系統,在硬體不提升的情況下利用nginx做負載均衡也是不錯的選擇。
負載均衡(Load Balance)是叢集技術(Cluster)的一種應用,可以將工作任務分攤到多個處理單元,從而提高併發處理能力,有利於提升中大型網站的效能。 需要使用服務叢集和水平擴充套件,讓“高峰”請求分流到不同的伺服器進行處理。
http重定向協議實現負載均衡
根據使用者的http請求的DNAT計算出一個真實的web伺服器地址,並將該web伺服器地址寫入http重定向響應中返回給瀏覽器,由瀏覽器重新進行訪問。該方式比較簡單,但效能較差。
一般來講經常用的SpringCloud-Gateway或者Neflix的Zuul等就屬於該型別。
協議層:DNS域名解析負載均衡
DNS伺服器上配置多個域名對應IP的記錄。該方式直接將負載均衡的工作交給了DNS,為網站管理維護省掉了很多麻煩,訪問速度快,有效改善效能。
一般來講經常用的DNS伺服器或者國內的DNS伺服器等就屬於該型別。
協議層:反向代理負載均衡
反向代理伺服器在提供負載均衡功能的同時,管理著一組web伺服器,根據負載均衡演算法將請求的瀏覽器訪問轉發到不同的web伺服器處理,處理結果經過反向伺服器返回給瀏覽器。
該方式部署簡單,web伺服器地址不能直接暴露在外,不需要使用外部IP地址,而反向代理服務作為溝通橋樑就需要配置雙網絡卡、外部內部兩套IP地址。
一般來講經常用的Nginx或者HaProxy等就屬於該型別。
網路層IP負載均衡
網路層通過修改目標地址進行負載均衡,該方式在響應請求時速度較反向伺服器負載均衡要快,但是,當請求資料較大(大型視訊或檔案)時,速度反應就會變慢。
一般來講經常用的Nginx或者HaProxy等就屬於該型別。
資料鏈路層負載均衡
資料鏈路層修改Mac地址進行負載均衡,負載均衡伺服器的IP和它所管理的web 服務群的虛擬IP一致。它不需要負載均衡伺服器進行地址的轉換,但是對負載均衡伺服器的網絡卡頻寬要求較高。
一般來講經常用的LVS等就屬於該型別。
F5 和 A10 負載均衡器
F5的全稱是F5-BIG-IP-GTM,硬體負載均衡裝置,其併發能力達到。該方式能夠實現多鏈路的負載均衡和冗餘,可以接入多條ISP鏈路,在鏈路之間實現負載均衡和高可用。
服務層架構設計
快取技術分析
硬碟持久化的IO操作將耗費大量資源。所以決定採用基於記憶體操作的redis,redis的密集型io。
分批放行+排隊處理
- 即使我們擴充套件再多的應用,使用再多的應用伺服器,部署再多的負載均衡器,都會遇到支撐不住海量請求的時候。
- 所以,在這一層我們要考慮的是如何做好限流,當超過系統承受範圍的時候,需要果斷阻止請求的湧入。
排隊處理
排隊處理機制,正如,我們日常買東西排隊一樣的道理,這樣子就不會處理不過來,並且也可以保證資料執行的正確性!
它直接將請求放入佇列中的,採用FIFO(First Input First Output,先進先出),這樣的話,我們就不會導致某些請求永遠獲取不到鎖。看到這裡,有一些將多執行緒處理方式變成單執行緒處理機制,會大大影響資料的效率和效能!
Java的三個常用的併發佇列
-
ArrayBlockingQueue是初始容量固定的阻塞佇列,可以用來作為資料庫成功競拍的佇列,比如有10個商品,那麼我們就設定一個10大小的陣列佇列。
-
ConcurrentLinkedQueue使用的是CAS原語無鎖佇列實現,是一個非同步佇列,入隊的速度很快,出隊進行了加鎖,效能稍慢。
-
LinkedBlockingQueue也是阻塞的佇列,入隊和出隊都用了加鎖,當隊空的時候執行緒會暫時阻塞。
在請求預處理階段,由於系統入隊需求要遠大於出隊需求,一般不會出現隊空的情況,所以我們可以選擇ConcurrentLinkedQueue來作為我們的請求佇列實現,甚至可以採用Disruptor非同步處理框架機制。
分批放行
在同步排隊的基礎上,我們可以在加入一個分批放行執行處理機制。
顧名思義的就是,為了提高效能,我們可以考慮達到預定閾值以後,在進行相關的執行後端服務,這樣子可以提高一定的效能以及減少後端請求的次數和壓力,如下圖所示:
還會利用快取和佇列技術減輕應用處理的壓力,通過非同步請求的方式做到最終一致性。
限流
漏桶演算法
漏桶演算法思路很簡單,水(請求)先進入到漏桶裡,漏桶以一定的速度出水,當水流入速度過大會直接溢位,可以看出漏桶演算法能強行限制資料的傳輸速率。
- 設定漏桶流出速度及漏桶的總容量,在請求到達時判斷當前漏桶容量是否已滿,不滿則可將請求存入桶中,否則拋棄請求。
- 採用一個執行緒以設定的速率取出請求進行處理。
演算法弊端
- 由於其只能以特定速率處理請求,則如何確定該速率就是核心問題,如果速率設定太小則會浪費效能資源,設定太大則會造成資源不足。
速度執行敏感度不高!無論輸入速率如何波動,均不會體現在服務端,即使資源有空餘,對於突發請求也無法及時處理,故對有突發請求處理需求時,不宜選擇該方法。
令牌桶演算法
令牌桶演算法的原理是系統會以一個恆定的速度往桶裡放入令牌,而如果請求需要被處理,則需要先從桶裡獲取一個令牌,當桶裡沒有令牌可取時,則拒絕服務。
實現原理
設定令牌桶中新增令牌的速率,並且設定桶中最大可儲存的令牌,當請求到達時,向桶中請求令牌(根據應用需求,可能為1個或多個),若令牌數量滿足要求,則刪除對應數量的令牌並通過當前請求,若桶中令牌數不足則觸發限流規則。
為解決固定視窗計數帶來的週期切換處流量突發問題,可以使用滑動視窗計數。滑動視窗計算本質上也是固定視窗計數,區別在於將計數週期進行細化。
滑動視窗
滑動視窗計數法與固定視窗計數法相比較,除了計數週期T及週期內最大訪問(呼叫)數N兩個引數,增加一個引數M,用於設定週期T內的滑動視窗數。
資料訪問層
由於要承受高併發,mysql在高併發情況下的效能下降尤其嚴重。
資料更新點(庫存扣除)
悲觀鎖更新資料
可以從“悲觀鎖”的方向
-
悲觀鎖,也就是在修改資料的時候,採用鎖定狀態,排斥外部請求的修改。遇到加鎖的狀態,就必須等待。
-
雖然它解決了執行緒安全的問題,但是“高併發”場景下,會很多這樣的修改請求,每個請求都需要等待“鎖”,某些執行緒可能永遠都沒有機會搶到這個“鎖”,這種請求就會死在那裡。
-
同時,這種請求會很多,瞬間增大系統的平均響應時間,結果是可用連線數被耗盡,系統陷入異常。
行鎖、頁鎖、表鎖、同步鎖、分散式鎖、分散式佇列、意向所等。
樂觀鎖更新資料
討論一下“樂觀鎖”的思路了。
-
樂觀鎖,是相對於“悲觀鎖”採用更為寬鬆的加鎖機制,大都是採用帶版本號(Version)更新/通過狀態化進行更新操作機制。
-
實現就是,這個資料所有請求都有資格去修改,但會獲得一個該資料的版本號,只有版本號符合的才能更新成功,其他的返回搶購失敗。
-
這樣的話,我們就不需要考慮佇列的問題,不過,它會增大CPU的計算開銷。但是在衝突較小的時候,這是一個比較好的解決方案。
快取樂觀鎖、資料庫樂觀鎖。(判斷更新行數是否>0),CAS機制
姊妹篇【「絕密檔案」“爆料”完整秒殺架構的設計到技術關鍵點的“八卦資料”】
再此會進行擴充套件技術介紹,以下內容:
-
熱點分離
- 熱點識別技術
- 熱點隔離技術
- 熱點優化技術
-
事務完整性
- 介面冪等性
-
分散式事務系統
- 事務處理去重法
- 事務處理冪等性
-
資料高可用
- 讀寫分離
- 分庫分表
- 資料庫叢集
- 優化資料庫
-
DB層面的單行記錄做併發排隊機制
-
服務降級分析
- 降低衝擊法(延時處理)
- 延時佇列機制(單點法)
- 延時佇列機制(分散式)
- 完整秒殺架構的設計到技術關鍵點的“情報資訊”
- 獨一無二的「MySQL調優金字塔」相信也許你擁有了它,你就很可能擁有了全世界。
- 【MySQL技術之旅】(5)該換換你的資料庫版本了,讓我們一同迎接8.0的到來哦!(初探篇)
- ☕【Java技術指南】「Java8程式設計專題」讓你真正會用對Java新版日期時間API程式設計指南
- 【Fegin技術專題】「原生態」開啟Fegin之RPC技術的開端,你會使用原生態的Fegin嗎?(高階用法)
- 【優化技術專題】「執行緒間的高效能訊息框架」終極關注Disruptor的核心原始碼和Java8的@Contended偽共享指南
- 【優化技術專題】「執行緒間的高效能訊息框架」再次細節領略Disruptor的底層原理和優勢分析
- 【Zookeeper核心原理】Paxos協議的原理和實際執行中的應用流程分析
- ☕【Java技術指南】「JPA程式設計專題」讓你不再對JPA技術中的“持久化型註解”感到陌生了!
- Java技術開發專題系列之【Guava RateLimiter】針對於限流器的入門到精通(含原始碼分析介紹)
- ☕【Java技術指南】「JPA程式設計專題」讓你不再對JPA技術中的“持久化型註解”感到陌生了!
- 【Eureka技術指南】「SpringCloud」從原始碼層面讓你認識Eureka工作流程和運作機制(下)
- MySQL技術專題(6)這也許是你的知識盲區-MySQL主從架構以及[半同步機制]
- 優化技術專題-執行緒間的高效能訊息框架-深入淺出Disruptor的使用和原理
- ☕【Java技術指南】「併發程式設計專題」Fork/Join框架基本使用和原理探究(原理篇)
- ☕【Java技術指南】「併發程式設計專題」Guava RateLimiter針對於限流器的入門到精通(含原始碼分析介紹)
- 【優化技術專題】「溫故而知新」基於Quartz系列的任務排程框架的動態化任務實現分析
- ☕【Java技術指南】「併發程式設計專題」Guava RateLimiter針對於限流器的入門到精通(含實戰和原理分析)
- 【MySQL技術之旅】(4)這也許是你的知識盲區-[MySQL主從架構]之半同步機制
- ☕【Java技術指南】「併發程式設計專題」CompletionService框架基本使用和原理探究(基礎篇)