完整秒殺架構的設計
掃描二維碼
隨時隨地手機看文章
作者:浩宇天尚
秒殺系統(tǒng)-情報背景
相信大家都接觸過新浪微博、淘寶、京東等等這些訪問量較為巨大的平臺以及網(wǎng)站,針對于“高流量”、“高并發(fā)”來講,更是我們【技術開發(fā)者】都要面臨的的一個很難的“包袱”難題。哎,看來如果要在這行混下去,即使你可能沒有接觸高并發(fā)場景,也要自己創(chuàng)造“高并發(fā)”進行迎難而上,因為只有這樣子我們才可以更進一步??!
秒殺系統(tǒng)-情報介紹
對于今天我們要介紹的內(nèi)容就屬于高并發(fā)的一個最極端的場景之一:“秒殺”,這個名詞一般會在“大促”的時候出現(xiàn),當然也會在某些平臺活動上出現(xiàn),那么肯定會有小伙伴會說,秒殺系統(tǒng)要注意哪些問題??!為啥會比較難呢,難在哪里啊!
秒殺系統(tǒng)- 特點分析
-
瞬時劇增:在某一個時刻開始進入流量(很少會有熱身以及緩慢增長機制),秒殺時大量用戶會在同一時間,搶購同一商品,網(wǎng)站瞬時流量激增。
-
僧多粥少:商品的庫存是有限的,秒殺請求下的訂單數(shù)量會遠遠大于庫存數(shù)量,只有少部分用戶能夠秒殺成功。
-
資源鎖定:秒殺業(yè)務流程比較簡單,一般就是下訂單減庫存。庫存就是用戶爭奪的“資源”,實際被消費的“資源”不能超過計劃要售出的“資源”,也就是不能被“超賣”。
秒殺系統(tǒng)- 難度分析
它的難度就在于要完成一個“60-100分”的秒殺系統(tǒng),那么它必須要要至少兼顧以下這三個方面,才算合格,這三個“惡魔”分別叫“服務可用性”、“數(shù)據(jù)一致性”和“快速響應性”,有點“苛刻”!
CAP理論又稱CAP定理,它說的是在一個分布式系統(tǒng)中,服務(數(shù)據(jù))層面的一致性(Consistency)、服務自身的可用性(Availability)、網(wǎng)絡不同節(jié)點分區(qū)容錯性(Partition tolerance)。A和C相信大家從字面上都可以理解了,這里要聲明一下比較陌生的P:它代表如果要保證不同的節(jié)點即使在網(wǎng)絡出現(xiàn)問題的時候仍能夠訪問到數(shù)據(jù),那么最直接的辦法就是冗余賦值節(jié)點,否則一切都是空談,所以作為一個分布式系統(tǒng)而言,無法忽略P,我們可以理解它就是A和C的基礎。
CAP體系總結
-
只保證AC就是一個單體應用,根本不是分布式。意義當然有,在分布式出現(xiàn)之前都是這么搭系統(tǒng)。倘若這個系統(tǒng)的節(jié)點之一掛了,不會發(fā)生腦裂而是整個系統(tǒng)直接宕掉。
-
進一步說如果網(wǎng)絡中存在的節(jié)點越多,分區(qū)容忍性越高,但要復制更新的數(shù)據(jù)就越多,一致性就越難保證。
-
為了保證一致性,更新所有節(jié)點數(shù)據(jù)所需要的時間就越長,可用性就會降低。
服務的可用性(Availability)
服務可用性,是在于高并發(fā)流量的沖擊下,仍然可以保持服務的可用性并且還要保證一直可以輸出對外界的服務能力,不會造成宕機以及資源損壞,即使在內(nèi)存和網(wǎng)絡甚至硬件資源有限的情況下,也不會被擊垮“死亡”。
比如就像你養(yǎng)魚,你玩命的給魚放飼料,而超過了魚能夠承受的量,它受不了了活活被噎死或者撐死了,這魚就像你的系統(tǒng)一樣,一定要保證魚的健康??!
數(shù)據(jù)的一致性(Consistency)
都知道,我們開發(fā)的程序以及現(xiàn)在多數(shù)的服務器,比如數(shù)據(jù)庫,他們在處理數(shù)據(jù)的時候,很有可能會存在多個線程同時在修改同一行數(shù)據(jù)或者同一塊內(nèi)存,在Java角度而言本身也會存在不一致的問題,而在程序和中間件的角度而言,也是一樣,會出現(xiàn)同一時刻在數(shù)據(jù)修改順序的亂序化,以及數(shù)據(jù)的紊亂,造成數(shù)據(jù)的重復操作,造成與我們預期的設想不同。
-
除非你可以實現(xiàn)串行化,一條一條處理,不讓它們同一時刻就行修改或者操作數(shù)據(jù),這個是最本質且最安全的辦法,但是也是最影響性能的辦法。(悲觀鎖、同步隊列)。
-
此外還有一種辦法就是,時時刻刻在原子層級,也就是最接近底層的計算機修改數(shù)據(jù)的時候,或者在所有節(jié)點之間建立一個應用層級的中間匯總干路點(redis或者database的主干點),上面加入寫屏障和讀屏障,在修改之前,在進行一次校驗判斷,如果數(shù)據(jù)與預期不同,就不進行修改。這就是著名的樂觀鎖!
服務快速響應性(Quick Response)
一般來講這個屬于用戶體驗,一個較為合格的秒殺系統(tǒng),是不應該讓用戶漫長的等待而是最好盡可能快速反饋結果。
-
要做成快速響應,就不需要是異步返回,直接快速響應。
-
此外還需要盡快幫助用戶計算數(shù)據(jù),直接返回。
總結一下
-
(異步返回 同步處理)總結一下就是異步中套用者同步進行計算,既可以保證快速響應,又可以保證數(shù)據(jù)的一致性。
-
(異步返回 樂觀鎖處理)總結一下就是異步中套用者樂觀鎖進行計算,既可以保證快速響應,又可以保證數(shù)據(jù)的一致性。
情報分析結束后,我們要重頭戲!進行技術分析了。
秒殺系統(tǒng)-架構設計
我們將秒殺架構進行一下劃分,大體分為三個層級進行分析:由外到內(nèi)進行分析,分別是:應用層、服務層、數(shù)據(jù)訪問層。
秒殺架構設計點
應用層架構設計
動靜分離 CDN技術
# 動靜分離分析
-
場景分析:在秒殺活動開啟之前,用戶一般都會嘗試不斷的刷新瀏覽器頁面(俗稱F5)以保證不會錯過秒殺活動的商品。
-
按照常用的網(wǎng)站應用架構:
-
我們假設,如果這些無用的請求,頻繁的沖擊我們的后臺服務器,比如說經(jīng)過:Web服務器(LVS、Nginx等)->應用服務器(tomcat或者Jetty等)、連接數(shù)據(jù)庫(MySQL),者無疑會對后端服務以及服務器造成非常大的壓力。
-
解決方案:重新設計秒殺商品頁面,不使用網(wǎng)站原來的商品詳細頁面,頁面內(nèi)容靜態(tài)化,減少/隔絕無用的請求經(jīng)過后端服務。
# CDN技術分析
# 突然增加的網(wǎng)絡及服務器帶寬
網(wǎng)站的靜態(tài)頁面數(shù)據(jù)大小100K,那么需要的網(wǎng)絡和服務器帶寬是2G(100K×10000),這些網(wǎng)絡帶寬是因為秒殺活動新增的,超過網(wǎng)站平時使用的帶寬。
# 防止緩存干擾頁面刷新為秒殺頁面
# 通過javascript文件進行傳遞隨機號 狀態(tài)位!
在秒殺商品靜態(tài)頁面中加入一個JavaScript文件引用,該JavaScript文件中包含秒殺開始標志為否;
-
當秒殺開始的時候生成一個新的JavaScript文件(文件名保持不變,只是內(nèi)容不一樣),更新秒殺開始標志為是,加入下單頁面的URL及隨機數(shù)參數(shù)(這個隨機數(shù)只會產(chǎn)生一個,即所有人看到的URL都是同一個,服務器端可以用redis這種分布式緩存服務器來保存隨機數(shù)),并被用戶瀏覽器加載,控制秒殺商品頁面的展示。
-
這個JavaScript文件的加載可以加上隨機版本號(例如xx.js?v=32353823),這樣就不會被瀏覽器、CDN和反向代理服務器緩存。
-
這個JavaScript文件非常小,即使每次瀏覽器刷新都訪問JavaScript文件服務器也不會對服務器集群和網(wǎng)絡帶寬造成太大壓力。
總結一下:前端秒殺頁面使用專門的頁面,這些頁面包括靜態(tài)的 HTML 和動態(tài)的 JS,他們都需要在 CDN 上緩存。
根據(jù)UID限制頻率熱度
為了控制公平性原則,由于黃?;蛘咭恍┖诳瓦_人,會采用”高科技“,比如說,采用爬蟲腳本操作,瘋狂的去刷新頁面。為了防止一些人的破壞以及公平分散,所以采用同一個標準去控制UID(用戶ID)去訪問頻率信息,當超過每個人所需要達到的頻率閾值,就要進行限制互動窗口內(nèi)能夠訪問刷新的數(shù)據(jù)量!
例如:可以用Redis給每個用戶做訪問統(tǒng)計,根據(jù)用戶的ID和商品的標識雙方面進行對用戶對某一個商品的訪問頻率控制,超過訪問頻率后,就會將他的請求暫時性熔斷。
反向代理 負載均衡
-
秒殺系統(tǒng)必然是一個集群系統(tǒng),在硬件不提升的情況下利用nginx做負載均衡也是不錯的選擇。
負載均衡(Load Balance)是集群技術(Cluster)的一種應用,可以將工作任務分攤到多個處理單元,從而提高并發(fā)處理能力,有利于提升中大型網(wǎng)站的性能。
需要使用服務集群和水平擴展,讓“高峰”請求分流到不同的服務器進行處理。
# http重定向協(xié)議實現(xiàn)負載均衡
根據(jù)用戶的http請求的DNAT計算出一個真實的web服務器地址,并將該web服務器地址寫入http重定向響應中返回給瀏覽器,由瀏覽器重新進行訪問。該方式比較簡單,但性能較差。
一般來講經(jīng)常用的SpringCloud-Gateway或者Neflix的Zuul等就屬于該類型。
# 協(xié)議層:DNS域名解析負載均衡
DNS服務器上配置多個域名對應IP的記錄。該方式直接將負載均衡的工作交給了DNS,為網(wǎng)站管理維護省掉了很多麻煩,訪問速度快,有效改善性能。
一般來講經(jīng)常用的DNS服務器或者國內(nèi)的DNS服務器等就屬于該類型。
# 協(xié)議層:反向代理負載均衡
反向代理服務器在提供負載均衡功能的同時,管理著一組web服務器,根據(jù)負載均衡算法將請求的瀏覽器訪問轉發(fā)到不同的web服務器處理,處理結果經(jīng)過反向服務器返回給瀏覽器。
一般來講經(jīng)常用的Nginx或者HaProxy等就屬于該類型。
# 網(wǎng)絡層IP負載均衡
網(wǎng)絡層通過修改目標地址進行負載均衡,該方式在響應請求時速度較反向服務器負載均衡要快,但是,當請求數(shù)據(jù)較大(大型視頻或文件)時,速度反應就會變慢。
一般來講經(jīng)常用的Nginx或者HaProxy等就屬于該類型。
# 數(shù)據(jù)鏈路層負載均衡
數(shù)據(jù)鏈路層修改Mac地址進行負載均衡,負載均衡服務器的IP和它所管理的web 服務群的虛擬IP一致。它不需要負載均衡服務器進行地址的轉換,但是對負載均衡服務器的網(wǎng)卡帶寬要求較高。
一般來講經(jīng)常用的LVS等就屬于該類型。
# F5 和 A10 負載均衡器
F5的全稱是F5-BIG-IP-GTM,硬件負載均衡設備,其并發(fā)能力達到。該方式能夠實現(xiàn)多鏈路的負載均衡和冗余,可以接入多條ISP鏈路,在鏈路之間實現(xiàn)負載均衡和高可用。
服務層架構設計
緩存技術分析
硬盤持久化的IO操作將耗費大量資源。所以決定采用基于內(nèi)存操作的redis,redis的密集型io。
分批放行 排隊處理
-
即使我們擴展再多的應用,使用再多的應用服務器,部署再多的負載均衡器,都會遇到支撐不住海量請求的時候。
-
所以,在這一層我們要考慮的是如何做好限流,當超過系統(tǒng)承受范圍的時候,需要果斷阻止請求的涌入。
# 排隊處理
排隊處理機制,正如,我們?nèi)粘YI東西排隊一樣的道理,這樣子就不會處理不過來,并且也可以保證數(shù)據(jù)執(zhí)行的正確性!
它直接將請求放入隊列中的,采用FIFO(First Input First Output,先進先出),這樣的話,我們就不會導致某些請求永遠獲取不到鎖??吹竭@里,有一些將多線程處理方式變成單線程處理機制,會大大影響數(shù)據(jù)的效率和性能!
# Java的三個常用的并發(fā)隊列
-
ArrayBlockingQueue是初始容量固定的阻塞隊列,可以用來作為數(shù)據(jù)庫成功競拍的隊列,比如有10個商品,那么我們就設定一個10大小的數(shù)組隊列。
-
ConcurrentLinkedQueue使用的是CAS原語無鎖隊列實現(xiàn),是一個異步隊列,入隊的速度很快,出隊進行了加鎖,性能稍慢。
-
LinkedBlockingQueue也是阻塞的隊列,入隊和出隊都用了加鎖,當隊空的時候線程會暫時阻塞。
在請求預處理階段,由于系統(tǒng)入隊需求要遠大于出隊需求,一般不會出現(xiàn)隊空的情況,所以我們可以選擇ConcurrentLinkedQueue來作為我們的請求隊列實現(xiàn),甚至可以采用Disruptor異步處理框架機制。
# 分批放行
在同步排隊的基礎上,我們可以在加入一個分批放行執(zhí)行處理機制。
顧名思義的就是,為了提高性能,我們可以考慮達到預定閾值以后,在進行相關的執(zhí)行后端服務,這樣子可以提高一定的性能以及減少后端請求的次數(shù)和壓力,如下圖所示:還會利用緩存和隊列技術減輕應用處理的壓力,通過異步請求的方式做到最終一致性。
限流
漏桶算法
漏桶算法思路很簡單,水(請求)先進入到漏桶里,漏桶以一定的速度出水,當水流入速度過大會直接溢出,可以看出漏桶算法能強行限制數(shù)據(jù)的傳輸速率。
-
設定漏桶流出速度及漏桶的總容量,在請求到達時判斷當前漏桶容量是否已滿,不滿則可將請求存入桶中,否則拋棄請求。
-
采用一個線程以設定的速率取出請求進行處理。
# 算法弊端
-
由于其只能以特定速率處理請求,則如何確定該速率就是核心問題,如果速率設置太小則會浪費性能資源,設置太大則會造成資源不足。
速度執(zhí)行敏感度不高!無論輸入速率如何波動,均不會體現(xiàn)在服務端,即使資源有空余,對于突發(fā)請求也無法及時處理,故對有突發(fā)請求處理需求時,不宜選擇該方法。
令牌桶算法
令牌桶算法的原理是系統(tǒng)會以一個恒定的速度往桶里放入令牌,而如果請求需要被處理,則需要先從桶里獲取一個令牌,當桶里沒有令牌可取時,則拒絕服務。
# 實現(xiàn)原理
設定令牌桶中添加令牌的速率,并且設置桶中最大可存儲的令牌,當請求到達時,向桶中請求令牌(根據(jù)應用需求,可能為1個或多個),若令牌數(shù)量滿足要求,則刪除對應數(shù)量的令牌并通過當前請求,若桶中令牌數(shù)不足則觸發(fā)限流規(guī)則。為解決固定窗口計數(shù)帶來的周期切換處流量突發(fā)問題,可以使用滑動窗口計數(shù)?;瑒哟翱谟嬎惚举|上也是固定窗口計數(shù),區(qū)別在于將計數(shù)周期進行細化。
滑動窗口
滑動窗口計數(shù)法與固定窗口計數(shù)法相比較,除了計數(shù)周期T及周期內(nèi)最大訪問(調用)數(shù)N兩個參數(shù),增加一個參數(shù)M,用于設置周期T內(nèi)的滑動窗口數(shù)。
數(shù)據(jù)訪問層
由于要承受高并發(fā),mysql在高并發(fā)情況下的性能下降尤其嚴重。
數(shù)據(jù)更新點(庫存扣除)
悲觀鎖更新數(shù)據(jù)
可以從“悲觀鎖”的方向
-
悲觀鎖,也就是在修改數(shù)據(jù)的時候,采用鎖定狀態(tài),排斥外部請求的修改。遇到加鎖的狀態(tài),就必須等待。
-
雖然它解決了線程安全的問題,但是“高并發(fā)”場景下,會很多這樣的修改請求,每個請求都需要等待“鎖”,某些線程可能永遠都沒有機會搶到這個“鎖”,這種請求就會死在那里。
-
同時,這種請求會很多,瞬間增大系統(tǒng)的平均響應時間,結果是可用連接數(shù)被耗盡,系統(tǒng)陷入異常。
行鎖、頁鎖、表鎖、同步鎖、分布式鎖、分布式隊列、意向所等。
樂觀鎖更新數(shù)據(jù)
討論一下“樂觀鎖”的思路了。
-
樂觀鎖,是相對于“悲觀鎖”采用更為寬松的加鎖機制,大都是采用帶版本號(Version)更新/通過狀態(tài)化進行更新操作機制。
-
實現(xiàn)就是,這個數(shù)據(jù)所有請求都有資格去修改,但會獲得一個該數(shù)據(jù)的版本號,只有版本號符合的才能更新成功,其他的返回搶購失敗。
-
這樣的話,我們就不需要考慮隊列的問題,不過,它會增大CPU的計算開銷。但是在沖突較小的時候,這是一個比較好的解決方案。
緩存樂觀鎖、數(shù)據(jù)庫樂觀鎖。(判斷更新行數(shù)是否>0),CAS機制
姊妹篇【「絕密檔案」“爆料”完整秒殺架構的設計到技術關鍵點的“八卦資料”】
再此會進行擴展技術介紹,以下內(nèi)容:
-
熱點分離
-
熱點識別技術
-
熱點隔離技術
-
熱點優(yōu)化技術
-
事務完整性
-
接口冪等性
-
分布式事務系統(tǒng)
-
事務處理去重法
-
事務處理冪等性
-
數(shù)據(jù)高可用
-
讀寫分離
-
分庫分表
-
數(shù)據(jù)庫集群
-
優(yōu)化數(shù)據(jù)庫
-
DB層面的單行記錄做并發(fā)排隊機制
-
服務降級分析
-
降低沖擊法(延時處理)
-
延時隊列機制(單點法)
-
延時隊列機制(分布式)