秒殺系統(tǒng)設(shè)計總結(jié)(持續(xù)更新中。。)

一、前言

秒殺系統(tǒng)其實是一個比較復(fù)雜的設(shè)計,文章先介紹設(shè)計秒殺系統(tǒng)的思路脈絡(luò)和設(shè)計系統(tǒng)的原則。后面章節(jié)再詳細(xì)介紹使用中的工具、中間件、設(shè)計方案。由于本人并沒有真正實踐過秒殺系統(tǒng)落地,了解過和實踐過完全是兩個概念,實踐才能更好的了解某個知識,所以收集過來的觀點很有可能不太正確,還請見諒,整理這篇文章的目的還是出于自身學(xué)習(xí),也由于沒有實戰(zhàn)經(jīng)驗導(dǎo)致我挺難通過一個案例把這些知識點統(tǒng)一整合,知識看上去是碎片化的,但是多少能起到幫助作用。

二、秒殺系統(tǒng)需要解決的常見問題

1)瞬時:一提到秒殺系統(tǒng)給人最深刻的印象是超大的瞬時并發(fā),如果系統(tǒng)沒有經(jīng)過限流或者熔斷處理,那么系統(tǒng)瞬間就會崩掉,就好像被DDos攻擊一樣。
2)超賣:秒殺除了大并發(fā)這樣的難點,還有一個所有電商都會遇到的痛,那就是超賣,電商搞大促最怕什么?最怕的就是超賣,產(chǎn)生超賣了以后會影響到用戶體驗,會導(dǎo)致訂單系統(tǒng)、庫存系統(tǒng)、供應(yīng)鏈等等,產(chǎn)生的問題是一系列的連鎖反應(yīng),所以電商都不希望超賣發(fā)生,但是在大并發(fā)的場景最容易發(fā)生的就是超賣,不同線程讀取到的當(dāng)前庫存數(shù)據(jù)可能下個毫秒就被其他線程修改了,如果沒有一定的鎖庫存機(jī)制那么庫存數(shù)據(jù)必然出錯,都不用上萬并發(fā),幾十并發(fā)就可以導(dǎo)致商品超賣。
3)性能:當(dāng)遇到大并發(fā)和超賣問題后,必然會引出另一個問題,那就是性能問題,如何保證在大并發(fā)請求下,系統(tǒng)能夠有好的性能,讓用戶能夠有更好的體驗,不然每個用戶都等幾十秒才能知道結(jié)果,那體驗必然是很糟糕的。

三、設(shè)計系統(tǒng)的整體思路

從整體上看設(shè)計秒殺系統(tǒng)需要解決的兩個核心問題,一個是并發(fā)讀,一個是并發(fā)寫,優(yōu)化時也可以基于這兩個核心問題進(jìn)行優(yōu)化。

下面是系統(tǒng)設(shè)計時的幾個戰(zhàn)術(shù)要點:

  • 并發(fā)讀的核心優(yōu)化理念是盡量減少用戶到服務(wù)端來“讀”數(shù)據(jù),或者讓他們讀更少的數(shù)據(jù)
  • 并發(fā)寫的處理原則也一樣,它要求我們在數(shù)據(jù)庫層面獨立出來一個庫,做特殊的處理
  • 針對意料之外的情況設(shè)計的planB兜底方案
  • 需要遵循幾個原則:用戶請求的數(shù)據(jù)盡量少、請求數(shù)盡量少、路徑盡量短、依賴盡量少,并且不要有單點

另外我們也可以圍繞高性能一致性,高可用 這幾個角度來展開架構(gòu)設(shè)計

  • 高性能設(shè)計:動靜分離方案、熱點的發(fā)現(xiàn)與隔離、請求的削峰與分層過濾、服務(wù)端的極致優(yōu)化
  • 一致性設(shè)計:秒殺減庫存的方案設(shè)計保證一致性
  • 高可用設(shè)計:兜底planB方案

四、架構(gòu)原則

一、用戶請求的數(shù)據(jù)盡量少

所謂“數(shù)據(jù)要盡量少”,首先是指用戶請求的數(shù)據(jù)能少就少。請求的數(shù)據(jù)包括上傳給系統(tǒng)的數(shù)據(jù)和系統(tǒng)返回給用戶的數(shù)據(jù)(通常就是網(wǎng)頁)。

為啥“數(shù)據(jù)要盡量少”呢?因為首先這些數(shù)據(jù)在網(wǎng)絡(luò)上傳輸需要時間,其次不管是請求數(shù)據(jù)還是返回數(shù)據(jù)都需要服務(wù)器做處理,而服務(wù)器在寫網(wǎng)絡(luò)時通常都要做壓縮(gzip)和字符編碼,這些都非常消耗 CPU,所以減少傳輸?shù)臄?shù)據(jù)量可以顯著減少 CPU 的使用。例如,我們可以簡化秒殺頁面的大小,去掉不必要的頁面裝修效果,等等。

其次,“數(shù)據(jù)要盡量少”還要求系統(tǒng)依賴的數(shù)據(jù)能少就少,包括系統(tǒng)完成某些業(yè)務(wù)邏輯需要讀取和保存的數(shù)據(jù),這些數(shù)據(jù)一般是和后臺服務(wù)以及數(shù)據(jù)庫打交道的。調(diào)用其他服務(wù)會涉及數(shù)據(jù)的序列化和反序列化,而這也是 CPU 的一大殺手,同樣也會增加延時。而且,數(shù)據(jù)庫本身也容易成為一個瓶頸,所以和數(shù)據(jù)庫打交道越少越好,數(shù)據(jù)越簡單、越小則越好。

二、請求數(shù)要盡量少

用戶請求的頁面返回后,瀏覽器渲染這個頁面還要包含其他的額外請求,比如說,這個頁面依賴的 CSS/JavaScript、圖片,以及 Ajax 請求等等都定義為“額外請求”,這些額外請求應(yīng)該盡量少。因為瀏覽器每發(fā)出一個請求都多少會有一些消耗,例如建立連接要做三次握手,有的時候有頁面依賴或者連接數(shù)限制,一些請求(例如 JavaScript)還需要串行加載等。另外,如果不同請求的域名不一樣的話,還涉及這些域名的 DNS 解析,可能會耗時更久。所以你要記住的是,減少請求數(shù)可以顯著減少以上這些因素導(dǎo)致的資源消耗。

例如,減少請求數(shù)最常用的一個實踐就是合并 CSS 和 JavaScript 文件,把多個 JavaScript 文件合并成一個文件,在 URL 中用逗號隔開(https://g.xxx.com/tm/xx-b/4.0.94/mods/??module-preview/index.xtpl.js,module-jhs/index.xtpl.js,module-focus/index.xtpl.js)。這種方式在服務(wù)端仍然是單個文件各自存放,只是服務(wù)端會有一個組件解析這個 URL,然后動態(tài)把這些文件合并起來一起返回。

三. 路徑要盡量短

所謂“路徑”,就是用戶發(fā)出請求到返回數(shù)據(jù)這個過程中,需求經(jīng)過的中間的節(jié)點數(shù)。

通常,這些節(jié)點可以表示為一個系統(tǒng)或者一個新的 Socket 連接(比如代理服務(wù)器只是創(chuàng)建一個新的 Socket 連接來轉(zhuǎn)發(fā)請求)。每經(jīng)過一個節(jié)點,一般都會產(chǎn)生一個新的 Socket 連接。

然而,每增加一個連接都會增加新的不確定性。從概率統(tǒng)計上來說,假如一次請求經(jīng)過 5 個節(jié)點,每個節(jié)點的可用性是 99.9% 的話,那么整個請求的可用性是:99.9% 的 5 次方,約等于 99.5%。

所以縮短請求路徑不僅可以增加可用性,同樣可以有效提升性能(減少中間節(jié)點可以減少數(shù)據(jù)的序列化與反序列化),并減少延時(可以減少網(wǎng)絡(luò)傳輸耗時)。

要縮短訪問路徑有一種辦法,就是多個相互強依賴的應(yīng)用合并部署在一起,把遠(yuǎn)程過程調(diào)用(HTTP/RPC)變成 JVM 內(nèi)部之間的方法調(diào)用。

四. 依賴要盡量少

所謂依賴,指的是要完成一次用戶請求必須依賴的系統(tǒng)或者服務(wù),這里的依賴指的是強依賴。

舉個例子,比如說你要展示秒殺頁面,而這個頁面必須強依賴商品信息、用戶信息,還有其他如優(yōu)惠券、成交列表等這些對秒殺不是非要不可的信息(弱依賴),這些弱依賴在緊急情況下就可以去掉。

要減少依賴,我們可以給系統(tǒng)進(jìn)行分級,比如 0 級系統(tǒng)、1 級系統(tǒng)、2 級系統(tǒng)、3 級系統(tǒng),0 級系統(tǒng)如果是最重要的系統(tǒng),那么 0 級系統(tǒng)強依賴的系統(tǒng)也同樣是最重要的系統(tǒng),以此類推。注意,0 級系統(tǒng)要盡量減少對 1 級系統(tǒng)的強依賴,防止重要的系統(tǒng)被不重要的系統(tǒng)拖垮。例如支付系統(tǒng)是 0 級系統(tǒng),而優(yōu)惠券是 1 級系統(tǒng)的話,在極端情況下可以把優(yōu)惠券給降級,防止支付系統(tǒng)被優(yōu)惠券這個 1 級系統(tǒng)給拖垮。

五. 不要有單點

關(guān)鍵點是避免將服務(wù)的狀態(tài)和機(jī)器綁定,即把服務(wù)無狀態(tài)化,這樣服務(wù)就可以在機(jī)器中隨意移動。如何那把服務(wù)的狀態(tài)和機(jī)器解耦呢?這里也有很多實現(xiàn)方式。例如把和機(jī)器相關(guān)的配置動態(tài)化,這些參數(shù)可以通過配置中心來動態(tài)推送,在服務(wù)啟動時動態(tài)拉取下來,我們在這些配置中心設(shè)置一些規(guī)則來方便地改變這些映射關(guān)系。

參考樣例介紹

淘寶早期架構(gòu)

秒殺詳情成為了一個獨立的新系統(tǒng),另外核心的一些數(shù)據(jù)放到了緩存(Cache)中,其他的關(guān)聯(lián)系統(tǒng)也都以獨立集群的方式進(jìn)行部署。


淘寶早期秒殺系統(tǒng)

這個系統(tǒng)架構(gòu)有下面幾個要點:

  • 把秒殺系統(tǒng)獨立出來單獨打造一個系統(tǒng),這樣可以有針對性地做優(yōu)化,例如這個獨立出來的系統(tǒng)就減少了店鋪裝修的功能,減少了頁面的復(fù)雜度;
  • 在系統(tǒng)部署上也獨立做一個機(jī)器集群,這樣秒殺的大流量就不會影響到正常的商品購買集群的機(jī)器負(fù)載;
  • 將熱點數(shù)據(jù)(如庫存數(shù)據(jù))單獨放到一個緩存系統(tǒng)中,以提高“讀性能”;
  • 增加秒殺答題,防止有秒殺器搶單。

淘寶進(jìn)一步升級的架構(gòu)

為了滿足更高的性能,淘寶對秒殺系統(tǒng)做了進(jìn)一步升級來滿足更高的性能,但是事實上也造成了系統(tǒng)的不通用性增加,系統(tǒng)復(fù)雜度升高。
進(jìn)一步升級架構(gòu)主要改造點有:

  • 對頁面進(jìn)行徹底的動靜分離,使得用戶秒殺時不需要刷新整個頁面,而只需要點擊搶寶按鈕,借此把頁面刷新的數(shù)據(jù)降到最少。
  • 在服務(wù)端對秒殺商品進(jìn)行本地緩存,不需要再調(diào)用依賴系統(tǒng)的后臺服務(wù)獲取數(shù)據(jù),甚至不需要去公共的緩存集群中查詢數(shù)據(jù),這樣不僅可以減少系統(tǒng)調(diào)用,而且能夠避免壓垮公共緩存集群 。
  • 增加系統(tǒng)限流保護(hù),防止最壞情況發(fā)生。
進(jìn)一步升級后的系統(tǒng)

實戰(zhàn)演練 - 疫情期間秒殺口罩

我們設(shè)計秒殺系統(tǒng)時,需要根據(jù)不同級別的流量,由簡單到復(fù)雜的場景打造的不同的系統(tǒng)架構(gòu),同時要平衡到系統(tǒng)的通用性和維護(hù)性,降低影響性,使系統(tǒng)或者其中的模塊能夠更好的被復(fù)用,運維簡單也是需要考慮的事項。

在設(shè)計系統(tǒng)前我們需要先收集下面這些信息:

  1. 場景用例:
    • 誰用這個系統(tǒng)?
    • 用戶如何用這個系統(tǒng)?
  2. 量級規(guī)模:
    • 每秒查詢請求?
    • 每個查詢請求多少數(shù)據(jù)?
    • 每秒處理多少個訂單?
    • 高峰流量是多少?
  3. 性能:
    • 預(yù)期從寫入到讀取數(shù)據(jù)的延遲?
    • 預(yù)期99%請求的延遲是多少?
    • 高可用性要求?
  4. 成本:
    • 公司現(xiàn)有的可復(fù)用的類似需求的成品資產(chǎn)有哪些?
    • 開發(fā)成本有沒有限制,物理資源,開發(fā)人員,測試人員有沒有限制?
    • 運維成本有沒有限制?

實戰(zhàn)演練 - “減庫存”設(shè)計

下單減庫存

即當(dāng)買家下單后,在商品的總庫存中減去買家購買數(shù)量。下單減庫存是最簡單的減庫存方式,也是控制最精確的一種,下單時直接通過數(shù)據(jù)庫的事務(wù)機(jī)制控制商品庫存,這樣一定不會出現(xiàn)超賣的情況。但是要知道,有些人下完單可能并不會付款。

  • 優(yōu)點:控制精確不會超賣
  • 缺點:惡意下單,會影響賣家的商品銷售

付款減庫存

即買家下單后,并不立即減庫存,而是等到有用戶付款后才真正減庫存,否則庫存一直保留給其他買家。

  • 優(yōu)點:可以不免惡意下單
  • 缺點:庫存會超賣,或者買家下單后付不了款的情況

預(yù)扣庫存

這種方式相對復(fù)雜一些,買家下單后,庫存為其保留一定的時間(如 10 分鐘),超過這個時間,庫存將會自動釋放,釋放后其他買家就可以繼續(xù)購買。在買家付款前,系統(tǒng)會校驗該訂單的庫存是否還有保留:如果沒有保留,則再次嘗試預(yù)扣;如果庫存不足(也就是預(yù)扣失?。﹦t不允許繼續(xù)付款;如果預(yù)扣成功,則完成付款并實際地減去庫存。

  • 優(yōu)點:一定程度上避免惡意下單
  • 缺點:要結(jié)合安全和反作弊的措施來制止,系統(tǒng)設(shè)計比較復(fù)雜

減庫存設(shè)計選擇

針對疫情搶購口罩這個功能,我的理解是可以允許適當(dāng)?shù)某u(當(dāng)然這個還得由運營的伙伴決定),原因是口罩的數(shù)量和單價比較低,超賣對企業(yè)損失不大,搶購失敗對客戶體驗的影響度也有限,惠民活動保證庫存能夠全部賣出就行,所以付款減庫存預(yù)扣庫存是兩個可以考慮的的減庫存方案,庫存方案的選擇主要是影響庫存、訂單、支付模塊的交互方式。

并發(fā)減庫存的問題

上面的是功能設(shè)計方案選擇,那么如何保證并發(fā)減庫這個最后的操作正確性,我理解主要的方式是加鎖。加鎖有兩個層面:一個是程序?qū)用?,另一個是數(shù)據(jù)庫層面。


加鎖設(shè)計
分布式鎖

這里加分布式鎖就是將多線程請求轉(zhuǎn)成單線程請求,因為每次只有一個線程獲得鎖并執(zhí)行,其余都被阻塞了。

在使用分布式鎖的時候需要注意當(dāng)鎖遇到數(shù)據(jù)庫事務(wù)的情況,mysql默認(rèn)的事務(wù)隔離級別是REPEATABLE-READ,在這種隔離級別下,同一個事務(wù)中多次讀取,返回的數(shù)據(jù)是一樣的。

同時,Spring聲明式事務(wù)默認(rèn)的傳播特性REQUIRED 如下圖所示:


REQUIRED傳播方式

Spring聲明式事務(wù)是Spring AOP最好的例子,Spring是通過AOP代理的方式來實現(xiàn)事務(wù)的,也就是說在調(diào)用reduceStock()方法的之前就已經(jīng)開啟了事務(wù)。

public enum Propagation {
    /**
     * 需要事務(wù),默認(rèn)傳播性行為。
     * 如果當(dāng)前存在事務(wù),就沿用當(dāng)前事務(wù),否則新建一個事務(wù)運行子方法
     */
    REQUIRED(0),
    /**
     * 支持事務(wù),如果當(dāng)前存在事務(wù),就沿用當(dāng)前事務(wù),
     * 如果不存在,則繼續(xù)采用無事務(wù)的方式運行子方法
     */
    SUPPORTS(1),
    /**
     * 必須使用事務(wù),如果當(dāng)前沒有事務(wù),拋出異常
     * 如果存在當(dāng)前事務(wù),就沿用當(dāng)前事務(wù)
     */
    MANDATORY(2),
    /**
     * 無論當(dāng)前事務(wù)是否存在,都會創(chuàng)建新事務(wù)允許方法
     * 這樣新事務(wù)就可以擁有新的鎖和隔離級別等特性,與當(dāng)前事務(wù)相互獨立
     */
    REQUIRES_NEW(3),
    /**
     * 不支持事務(wù),當(dāng)前存在事務(wù)時,將掛起事務(wù),運行方法
     */
    NOT_SUPPORTED(4),
    /**
     * 不支持事務(wù),如果當(dāng)前方法存在事務(wù),將拋出異常,否則繼續(xù)使用無事務(wù)機(jī)制運行
     */
    NEVER(5),
    /**
     * 在當(dāng)前方法調(diào)用子方法時,如果子方法發(fā)生異常
     * 只回滾子方法執(zhí)行過的SQL,而不回滾當(dāng)前方法的事務(wù)
     */
    NESTED(6);
    ......
}

那么,在并發(fā)情況下可能會存在這樣的情況,假設(shè)線程T1和T2都執(zhí)行到這里,于是它們都開啟了事務(wù)S1和S2,T1先執(zhí)行,T2后執(zhí)行,由于T2執(zhí)行的時候事務(wù)已經(jīng)創(chuàng)建了,根據(jù)隔離級別,這個時候事務(wù)S2讀取不到S1已提交的數(shù)據(jù),于是就會出現(xiàn)T1和T2讀取到的值是一樣的,即T2讀取的是T1更新前的庫存數(shù)據(jù)。

數(shù)據(jù)庫樂觀鎖

數(shù)據(jù)增加一個版本標(biāo)識,一般是通過為數(shù)據(jù)庫表增加一個數(shù)字類型的 “version” 字段來實現(xiàn)。當(dāng)讀取數(shù)據(jù)時,將version字段的值一同讀出,數(shù)據(jù)每更新一次,對此version值加一。當(dāng)我們提交更新的時候,判斷數(shù)據(jù)庫表對應(yīng)記錄的當(dāng)前版本信息與第一次取出來的version值進(jìn)行比對,如果數(shù)據(jù)庫表當(dāng)前版本號與第一次取出來的version值相等,則予以更新,否則認(rèn)為是過期數(shù)據(jù)。更新的時候帶上版本號,只有當(dāng)前版本號與更新之前查詢時的版本一致,才會更新。

避免超賣

對于普通商品,秒殺只是一種大促手段,即使庫存超賣,商家也可以通過補貨來解決。而對于一些商品,秒殺作為一種營銷手段,完全不允許庫存為負(fù),也就是在數(shù)據(jù)一致性上,需要保證大并發(fā)請求時數(shù)據(jù)庫中的庫存字段值不能為負(fù)。這里有下面幾種方法:

  • 通過事務(wù)來判斷,即保證減后庫存不能為負(fù),否則就回滾。
  • 直接設(shè)置數(shù)據(jù)庫字段類型為無符號整數(shù),這樣一旦庫存為負(fù)就會在執(zhí)行 SQL 時報錯。
  • 使用 CASE WHEN 判斷語句:UPDATE item SET inventory CASE WHEN inventory xxx THEN inventory xxx ELSE inventory

實戰(zhàn)演練 - 動靜分離

那到底什么才是動靜分離呢?所謂“動靜分離”,其實就是把用戶請求的數(shù)據(jù)(如 HTML 頁面)劃分為“動態(tài)數(shù)據(jù)”和“靜態(tài)數(shù)據(jù)”。我這里就簡單的把css,js,圖片等信息歸納為“靜態(tài)數(shù)據(jù)”,由后臺接口產(chǎn)生的數(shù)據(jù)歸納為“動態(tài)數(shù)據(jù)”。

那么,怎樣對靜態(tài)數(shù)據(jù)做緩存呢?這里總結(jié)了幾個重點。

第一,你應(yīng)該把靜態(tài)數(shù)據(jù)緩存到離用戶最近的地方。 靜態(tài)數(shù)據(jù)就是那些相對不會變化的數(shù)據(jù),因此我們可以把它們緩存起來。緩存到哪里呢?常見的就三種,用戶瀏覽器里、CDN 上或者在服務(wù)端的 Cache 中。你應(yīng)該根據(jù)情況,把它們盡量緩存到離用戶最近的地方。

第二,靜態(tài)化改造就是要直接緩存 HTTP 連接。 相較于普通的數(shù)據(jù)緩存而言,你肯定還聽過系統(tǒng)的靜態(tài)化改造。靜態(tài)化改造是直接緩存 HTTP 連接而不是僅僅緩存數(shù)據(jù),如下圖所示,Web 代理服務(wù)器根據(jù)請求 URL,直接取出對應(yīng)的 HTTP 響應(yīng)頭和響應(yīng)體然后直接返回,這個響應(yīng)過程簡單得連 HTTP協(xié)議都不用重新組裝,甚至連 HTTP 請求頭也不需要解析。

靜態(tài)化改造

第三,讓誰來緩存靜態(tài)數(shù)據(jù)也很重要。不同語言寫的 Cache 軟件處理緩存數(shù)據(jù)的效率也各不相同。以 Java 為例,因為 Java 系統(tǒng)本身也有其弱點(比如不擅長處理大量連接請求,每個連接消耗的內(nèi)存較多,Servlet 容器解析 HTTP 協(xié)議較慢),所以你可以不在 Java 層做緩存,而是直接在 Web 服務(wù)器層上做,這樣你就可以屏蔽 Java 語言層面的一些弱點;而相比起來,Web 服務(wù)器(如 Nginx、Apache、Varnish)也更擅長處理大并發(fā)的靜態(tài)文件請求。

動靜分離的幾種架構(gòu)方案

1)實體機(jī)單機(jī)部署

Nginx+Cache+Java 結(jié)構(gòu)實體機(jī)單機(jī)部署

優(yōu)點:

  • 沒有網(wǎng)絡(luò)瓶頸,而且能使用大內(nèi)存;
  • 既能提升命中率,又能減少 Gzip 壓縮;
  • 減少 Cache 失效壓力,因為采用定時失效方式,例如只緩存 3 秒鐘,過期即自動失效。

缺點:

  • 但是一定程度上也造成了 CPU 的浪費,因為單個的 Java 進(jìn)程很難用完整個實體機(jī)的 CPU
  • 一個實體機(jī)上部署了 Java 應(yīng)用又作為 Cache 來使用,這造成了運維上的高復(fù)雜度,所以這是一個折中的方案。

2)統(tǒng)一 Cache 層
所謂統(tǒng)一 Cache 層,就是將單機(jī)的 Cache 統(tǒng)一分離出來,形成一個單獨的 Cache 集群。統(tǒng)一 Cache 層是個更理想的可推廣方案,該方案的結(jié)構(gòu)圖如下:

統(tǒng)一 Cache

優(yōu)點:

  • 單獨一個 Cache 層,可以減少多個應(yīng)用接入時使用 Cache 的成本。這樣接入的應(yīng)用只要維護(hù)自己的 Java 系統(tǒng)就好,不需要單獨維護(hù) Cache,而只關(guān)心如何使用即可。
  • 統(tǒng)一 Cache 的方案更易于維護(hù),如后面加強監(jiān)控、配置的自動化,只需要一套解決方案就行,統(tǒng)一起來維護(hù)升級也比較方便。
  • 可以共享內(nèi)存,最大化利用內(nèi)存,不同系統(tǒng)之間的內(nèi)存可以動態(tài)切換,從而能夠有效應(yīng)對各種攻擊

缺點:

  • Cache 層內(nèi)部交換網(wǎng)絡(luò)成為瓶頸。
  • 緩存服務(wù)器的網(wǎng)卡也會是瓶頸。
  • 機(jī)器少風(fēng)險較大,掛掉一臺就會影響很大一部分緩存數(shù)據(jù)。

3)上 CDN

當(dāng)前比較理想的一種 CDN 化方案

在將整個系統(tǒng)做動靜分離后,我們自然會想到更進(jìn)一步的方案,就是將 Cache 進(jìn)一步前移到 CDN 上,因為 CDN 離用戶最近,效果會更好。

優(yōu)點:

  • 離用戶最近,效果會更好。
  • 強制刷新整個頁面,也會請求 CDN,這樣一來,系統(tǒng)只是向服務(wù)端請求很少的有效數(shù)據(jù),而不需要重復(fù)請求大量的靜態(tài)數(shù)據(jù)。

缺點:

  • 需要處理失效問題命中率問題、發(fā)布更新問題

總結(jié):使用CDN最好滿足下面幾個條件:

  • 靠近訪問量比較集中的地區(qū)
  • 離主站相對較遠(yuǎn)
  • 節(jié)點到主站間的網(wǎng)絡(luò)比較好,而且穩(wěn)定
  • 節(jié)點容量比較大,不會占用其他 CDN 太多的資源
  • 還有一點也很重要,那就是:節(jié)點不要太多

實戰(zhàn)演練 - 流量削峰

流量削峰 - 消息隊列

要對流量進(jìn)行削峰,最容易想到的解決方案就是用消息隊列來緩沖瞬時流量,把同步的直接調(diào)用轉(zhuǎn)換成異步的間接推送,中間通過一個隊列在一端承接瞬時的流量洪峰,在另一端平滑地將消息推送出去。在這里,消息隊列就像“水庫”一樣, 攔蓄上游的洪水,削減進(jìn)入下游河道的洪峰流量,從而達(dá)到減免洪水災(zāi)害的目的。


用消息隊列來緩沖瞬時流量

流量削峰 - 其它方案

1)利用線程池加鎖等待也是一種常用的排隊方式。
2)先進(jìn)先出、先進(jìn)后出等常用的內(nèi)存排隊算法的實現(xiàn)方式。
3)把請求序列化到文件中,然后再順序地讀文件(例如基于 MySQL binlog 的同步機(jī)制)來恢復(fù)請求等方式。

流量削峰總結(jié)

流量削峰共同特征,就是把“一步的操作”變成“兩步的操作”,其中增加的一步操作用來起到緩沖的作用。

實戰(zhàn)演練 - 一致性

這里先介紹下分布式系統(tǒng)中的一致性相關(guān)概念和常用的幾個解決方案,最后結(jié)合之前的秒殺場景選擇相關(guān)的解決方案。

緩存一致性 - 緩存與數(shù)據(jù)庫一致性

TODO

業(yè)務(wù)數(shù)據(jù)一致性 - 分布式事務(wù)和最終一致

分布式事務(wù)
一、兩階段提交(2PC)

兩階段提交就是使用XA協(xié)議的原理,我們可以從下面這個圖的流程來很容易的看出中間的一些比如commit和abort的細(xì)節(jié)。

2PC

兩階段提交這種解決方案屬于犧牲了一部分可用性來換取的一致性。

優(yōu)點: 盡量保證了數(shù)據(jù)的強一致,適合對數(shù)據(jù)強一致要求很高的關(guān)鍵領(lǐng)域。(其實也不能100%保證強一致)
缺點: 實現(xiàn)復(fù)雜,犧牲了可用性,對性能影響較大,不適合高并發(fā)高性能場景,如果分布式系統(tǒng)跨接口調(diào)用。

二、補償事務(wù)(TCC)

TCC模型完全交由業(yè)務(wù)實現(xiàn),每個子業(yè)務(wù)都需要實現(xiàn)Try-Confirm-Cancel三個接口,對業(yè)務(wù)侵入大,資源鎖定交由業(yè)務(wù)方。

  • 1、Try:嘗試執(zhí)行業(yè)務(wù),完成所有業(yè)務(wù)檢查(一致性),預(yù)留必要的業(yè)務(wù)資源(準(zhǔn)隔離性)。
  • 2、Confirm:確認(rèn)執(zhí)行業(yè)務(wù),不再做業(yè)務(wù)檢查。只使用Try階段預(yù)留的業(yè)務(wù)資源,Confirm操作滿足冪等性。
  • 3、Cancel:取消執(zhí)行業(yè)務(wù)釋放Try階段預(yù)留業(yè)務(wù)資源。
補償事務(wù)(TCC

舉個例子,假入 Bob 要向 Smith 轉(zhuǎn)賬,我們有一個本地方法,里面依次調(diào)用,思路大概是:
1、首先在 Try 階段,要先調(diào)用遠(yuǎn)程接口把 Smith 和 Bob 的錢給凍結(jié)起來。
2、在 Confirm 階段,執(zhí)行遠(yuǎn)程調(diào)用的轉(zhuǎn)賬的操作,轉(zhuǎn)賬成功進(jìn)行解凍。
3、如果第2步執(zhí)行成功,那么轉(zhuǎn)賬成功,如果第二步執(zhí)行失敗,則調(diào)用遠(yuǎn)程凍結(jié)接口對應(yīng)的解凍方法 (Cancel)。

優(yōu)點: 跟2PC比起來,實現(xiàn)以及流程相對簡單了一些,但數(shù)據(jù)的一致性比2PC也要差一些
缺點: 缺點還是比較明顯的,在2,3步中都有可能失敗。TCC屬于應(yīng)用層的一種補償方式,所以需要程序員在實現(xiàn)的時候多寫很多補償?shù)拇a,在一些場景中,一些業(yè)務(wù)流程可能用TCC不太好定義及處理。

相對于 2PC,TCC 適用的范圍更大,但是開發(fā)量也更大,畢竟都在業(yè)務(wù)上實現(xiàn),而且有時候你會發(fā)現(xiàn)這三個方法還真不好寫。不過也因為是在業(yè)務(wù)上實現(xiàn)的,所以TCC可以跨數(shù)據(jù)庫、跨不同的業(yè)務(wù)系統(tǒng)來實現(xiàn)事務(wù)。

三、本地消息表(異步確保)

本地消息表這種實現(xiàn)方式應(yīng)該是業(yè)界使用最多的,其核心思想是將分布式事務(wù)拆分成本地事務(wù)進(jìn)行處理,如下圖所示:

本地消息表

消息生產(chǎn)方,需要額外建一個消息表,并記錄消息發(fā)送狀態(tài)。消息表和業(yè)務(wù)數(shù)據(jù)要在一個事務(wù)里提交,也就是說他們要在一個數(shù)據(jù)庫里面。然后消息會經(jīng)過MQ發(fā)送到消息的消費方。如果消息發(fā)送失敗,會進(jìn)行重試發(fā)送。

消息消費方,需要處理這個消息,并完成自己的業(yè)務(wù)邏輯。此時如果本地事務(wù)處理成功,表明已經(jīng)處理成功了,如果處理失敗,那么就會重試執(zhí)行。如果是業(yè)務(wù)上面的失敗,可以給生產(chǎn)方發(fā)送一個業(yè)務(wù)補償消息,通知生產(chǎn)方進(jìn)行回滾等操作。

生產(chǎn)方和消費方定時掃描本地消息表,把還沒處理完成的消息或者失敗的消息再發(fā)送一遍。如果有靠譜的自動對賬補賬邏輯,這種方案還是非常實用的。

優(yōu)點: 一種非常經(jīng)典的實現(xiàn),避免了分布式事務(wù),實現(xiàn)了最終一致性。
缺點: 消息表會耦合到業(yè)務(wù)系統(tǒng)中,如果沒有封裝好的解決方案,會有很多雜活需要處理。

四、MQ 事務(wù)消息

以阿里的 RocketMQ 中間件為例,其思路大致為:

第一階段Prepared消息,會拿到消息的地址。
第二階段執(zhí)行本地事務(wù),第三階段通過第一階段拿到的地址去訪問消息,并修改狀態(tài)。

也就是說在業(yè)務(wù)方法內(nèi)要想消息隊列提交兩次請求,一次發(fā)送消息和一次確認(rèn)消息。如果確認(rèn)消息發(fā)送失敗了RocketMQ會定期掃描消息集群中的事務(wù)消息,這時候發(fā)現(xiàn)了Prepared消息,它會向消息發(fā)送者確認(rèn),所以生產(chǎn)方需要實現(xiàn)一個check接口,RocketMQ會根據(jù)發(fā)送端設(shè)置的策略來決定是回滾還是繼續(xù)發(fā)送確認(rèn)消息。這樣就保證了消息發(fā)送與本地事務(wù)同時成功或同時失敗。

RocketMQ事務(wù)消息

優(yōu)點: 實現(xiàn)了最終一致性,不需要依賴本地數(shù)據(jù)庫事務(wù)。
缺點: 需要引入新的中間件,主流MQ不支持。

五、Sagas 事務(wù)模型

Saga事務(wù)模型又叫做長時間運行的事務(wù)(Long-running-transaction), 它描述的是另外一種在沒有兩階段提交的的情況下解決分布式系統(tǒng)中復(fù)雜的業(yè)務(wù)事務(wù)問題。

Saga事務(wù)是一個長事務(wù),整個事務(wù)可以由多個本地事務(wù)組成,每個本地事務(wù)有相應(yīng)的執(zhí)行模塊和補償模塊,當(dāng)Saga事務(wù)中任意一個事務(wù)出錯了,可以調(diào)用相關(guān)事務(wù)進(jìn)行對應(yīng)的補償恢復(fù),達(dá)到事務(wù)的最終一致性。

它與2PC不同,2PC是同步的,而Saga模式是異步和反應(yīng)性的。在Saga模式中,分布式事務(wù)由所有相關(guān)微服務(wù)上的異步本地事務(wù)完成。微服務(wù)通過事件總線相互通信。

Saga

Saga也是一種補償協(xié)議,在 Saga 模式下,分布式事務(wù)內(nèi)有多個參與者,每一個參與者都是一個沖正補償服務(wù),需要用戶根據(jù)業(yè)務(wù)場景實現(xiàn)其正向操作和逆向回滾操作。

優(yōu)點: Saga模式的一大優(yōu)勢是它支持長事務(wù)。因為每個微服務(wù)僅關(guān)注其自己的本地原子事務(wù),所以如果微服務(wù)運行很長時間,則不會阻止其他微服務(wù)。這也允許事務(wù)繼續(xù)等待用戶輸入。此外,由于所有本地事務(wù)都是并行發(fā)生的,因此任何對象都沒有鎖定。
缺點:Saga模式很難調(diào)試,特別是涉及許多微服務(wù)時。此外,如果系統(tǒng)變得復(fù)雜,事件消息可能變得難以維護(hù)。Saga模式的另一個缺點是它沒有讀取隔離。例如,客戶可以看到正在創(chuàng)建的訂單,但在下一秒,訂單將因補償交易而被刪除。

國外AxonFramework的框架很好的實現(xiàn)了Saga感興趣的可以研究下

六、分布式事務(wù)總結(jié)

我們綜合對比下幾種分布式事務(wù)解決方案: ps: 感覺不怎么對 - -!

  • 一致性保證:2PC > TCC = SAGA > 事務(wù)消息 > 本地消息表
  • 業(yè)務(wù)友好性:2PC > 事務(wù)消息 > SAGA > TCC > 本地消息表
  • 性 能 損 耗:2PC > TCC > SAGA = 事務(wù)消息 > 本地消息表

在柔性事務(wù)解決方案中,雖然SAGA和TCC看上去可以保證數(shù)據(jù)的最終一致性,但分布式系統(tǒng)的生產(chǎn)環(huán)境復(fù)雜多變,某些情況是可以導(dǎo)致柔性事務(wù)機(jī)制失效的,所以無論使用那種方案,都需要最終的兜底策略,人工校驗,修復(fù)數(shù)據(jù)。

事務(wù)最終一致性的幾種做法
最終一致性的幾種做法
一、單數(shù)據(jù)庫情況下的事務(wù) - 對應(yīng)上圖的A流程

如果應(yīng)用系統(tǒng)是單一的數(shù)據(jù)庫,那么這個很好保證,利用數(shù)據(jù)庫的事務(wù)特性來滿足事務(wù)的一致性,這時候的一致性是強一致性的。對于java應(yīng)用系統(tǒng)來講,很少直接通過事務(wù)的start和commit以及rollback來硬編碼,大多通過spring的事務(wù)模板或者聲明式事務(wù)來保證。

二、基于事務(wù)型消息隊列的最終一致性 - 對應(yīng)上圖中的C流程

借助消息隊列,在處理業(yè)務(wù)邏輯的地方,發(fā)送消息,業(yè)務(wù)邏輯處理成功后,提交消息,確保消息是發(fā)送成功的,之后消息隊列投遞來進(jìn)行處理,如果成功,則結(jié)束,如果沒有成功,則重試,直到成功,不過僅僅適用業(yè)務(wù)邏輯中,第一階段成功,第二階段必須成功的場景。對應(yīng)上圖中的C流程。

三、基于消息隊列+定時補償機(jī)制的最終一致性 - 對應(yīng)上圖的E流程

前面部分和上面基于事務(wù)型消息的隊列,不同的是,第二階段重試的地方,不再是消息中間件自身的重試邏輯了,而是單獨的補償任務(wù)機(jī)制。其實在大多數(shù)的邏輯中,第二階段失敗的概率比較小,所以單獨獨立補償任務(wù)表出來,可以更加清晰,能夠比較明確的直到當(dāng)前多少任務(wù)是失敗的。對應(yīng)上圖的E流程。

四、異步回調(diào)機(jī)制的引入 - 上圖中的B流程

A應(yīng)用調(diào)用B,在同步調(diào)用的返回結(jié)果中,B返回成功給到A,一般情況下,這時候就結(jié)束了,其實在99.99%的情況是沒問題的,但是有時候為了確保100%,記住最起碼在系統(tǒng)設(shè)計中100%,這時候B系統(tǒng)再回調(diào)A一下,告訴A,你調(diào)用我的邏輯,確實成功了。其實這個邏輯,非常類似TCP協(xié)議中的三次握手。

五、類似double check機(jī)制的確認(rèn)機(jī)制 - 上圖中的D流程

還是上圖中異步回調(diào)的過程,A在同步調(diào)用B,B返回成功了。這次調(diào)用結(jié)束了,但是A為了確保,在過一段時間,這個時間可以是幾秒,也可以是每天定時處理,再調(diào)用B一次,查詢一下之前的那次調(diào)用是否成功。例如A調(diào)用B更新訂單狀態(tài),這時候成功了,延遲幾秒后,A查詢B,確認(rèn)一下狀態(tài)是否是自己剛剛期望的。

六、最大努力通知 - 冪等性控制

重試會面臨問題,重試之后不能給業(yè)務(wù)邏輯帶來影響,例如創(chuàng)建訂單,第一次調(diào)用超時了,但是調(diào)用的系統(tǒng)不知道超時了是成功了還是失敗了,然后他就重試,但是實際上第一次調(diào)用訂單創(chuàng)建是成功了的,這時候重試了,顯然不能再創(chuàng)建訂單了。

  1. 查詢: 查詢的API,可以說是天然的冪等性,因為你查詢一次和查詢兩次,對于系統(tǒng)來講,沒有任何數(shù)據(jù)的變更,所以,查詢一次和查詢多次一樣的。

  2. MVCC方案: 多版本并發(fā)控制,update with condition,更新帶條件,這也是在系統(tǒng)設(shè)計的時候,合理的選擇樂觀鎖,通過version或者其他條件,來做樂觀鎖,這樣保證更新及時在并發(fā)的情況下,也不會有太大的問題。

例如 :

update tablexxx set name=#name#,version=version+1 where version=#version# 

或者是

update tablexxx set quality=quality-#subQuality# where quality-#subQuality# >= 0 

3. 單獨的去重表:如果涉及到的去重的地方特別多,例如ERP系統(tǒng)中有各種各樣的業(yè)務(wù)單據(jù),每一種業(yè)務(wù)單據(jù)都需要去重,這時候,可以單獨搞一張去重表,在插入數(shù)據(jù)的時候,插入去重表,利用數(shù)據(jù)庫的唯一索引特性,保證唯一的邏輯。

4. 分布式鎖:還是拿插入數(shù)據(jù)的例子,如果是分布是系統(tǒng),構(gòu)建唯一索引比較困難,例如唯一性的字段沒法確定,這時候可以引入分布式鎖,通過第三方的系統(tǒng),在業(yè)務(wù)系統(tǒng)插入數(shù)據(jù)或者更新數(shù)據(jù),獲取分布式鎖,然后做操作,之后釋放鎖,這樣其實是把多線程并發(fā)的鎖的思路,引入多多個系統(tǒng),也就是分布式系統(tǒng)中得解決思路。

5. 刪除數(shù)據(jù):刪除數(shù)據(jù),僅僅第一次刪除是真正的操作數(shù)據(jù),第二次甚至第三次刪除,直接返回成功,這樣保證了冪等。

6. 插入數(shù)據(jù)的唯一索引:插入數(shù)據(jù)的唯一性,可以通過業(yè)務(wù)主鍵來進(jìn)行約束,例如一個特定的業(yè)務(wù)場景,三個字段肯定確定唯一性,那么,可以在數(shù)據(jù)庫表添加唯一索引來進(jìn)行標(biāo)示。

7. API層面的冪等:這里有一個場景,API層面的冪等,例如提交數(shù)據(jù),如何控制重復(fù)提交,這里可以在提交數(shù)據(jù)的form表單或者客戶端軟件,增加一個唯一標(biāo)示,然后服務(wù)端,根據(jù)這個UUID來進(jìn)行去重,這樣就能比較好的做到API層面的唯一標(biāo)示。

8. 狀態(tài)機(jī)冪等:在設(shè)計單據(jù)相關(guān)的業(yè)務(wù),或者是任務(wù)相關(guān)的業(yè)務(wù),肯定會涉及到狀態(tài)機(jī),就是業(yè)務(wù)單據(jù)上面有個狀態(tài),狀態(tài)在不同的情況下會發(fā)生變更,一般情況下存在有限狀態(tài)機(jī),這時候,如果狀態(tài)機(jī)已經(jīng)處于下一個狀態(tài),這時候來了一個上一個狀態(tài)的變更,理論上是不能夠變更的,這樣的話,保證了有限狀態(tài)機(jī)的冪等。

秒殺系統(tǒng)的一致性設(shè)計

針對秒殺的場景,由于性能要求比較高、一致性要求比較低,所以我并不推薦使用XA、TCC、Saga的方式,推薦最終一致性方案。秒殺系統(tǒng)可能有的模塊有“訂單”、“庫存”、“支付”、“物流”、“短信”,最常用的下單減庫存的庫存設(shè)計場景下,在業(yè)務(wù)領(lǐng)域劃分上“訂單”、“庫存”其實都屬于下單服務(wù),我覺得可以在一個下單服務(wù)中完成減庫存和訂單操作這兩步動作,“支付”、“物流”、“短信” 屬于其它領(lǐng)域。然后通過消息隊列通知訂單處理服務(wù)解耦物流、支付、短信能服務(wù)。如下圖所示:

訂單流程示意圖

在這個設(shè)計下我們就可以采取基于事務(wù)型消息隊列的最終一致性(對應(yīng)上圖的C流程)基于消息隊列+定時補償機(jī)制的最終一致性(對應(yīng)上圖的E流程) 的最終一致性方案。 TODO 后面有機(jī)會再補充付款減庫存預(yù)扣庫存的設(shè)計思路。

但是下單服務(wù)的事務(wù)的處理上我們需要有下面這些優(yōu)化思路:

1. 結(jié)合業(yè)務(wù)場景,使用低級別事務(wù)隔離
在高并發(fā)業(yè)務(wù)中,為了保證業(yè)務(wù)數(shù)據(jù)的一致性,操作數(shù)據(jù)庫時往往會使用不同級別的事務(wù)隔離,隔離等級越高,并發(fā)性能就越低。

那在實際的業(yè)務(wù)中,我們要如何選擇呢,下邊舉兩個例子:

在修改用戶的最后登錄時間,或者用戶的個人資料等數(shù)據(jù)時,這些數(shù)據(jù)都只有用戶自己登錄和登陸后才會修改,不存在一個事務(wù)提交的信息被覆蓋的可能,所以這樣的業(yè)務(wù)我們就最低的隔離級別。

如果賬戶的余額或者積分的消費,就可能存在多個客戶端同事消費一個賬戶的情況,此時我們應(yīng)該選擇可重復(fù)讀隔離級別,來保證當(dāng)一個客戶端在操作的時候,其他客戶端不能對該數(shù)據(jù)進(jìn)行操作。

2. 避免行鎖升級表鎖
我們知道,InnoDB中行鎖是通過索引實現(xiàn)的,當(dāng)不通過索引條件檢索數(shù)據(jù)時,行鎖就會升級成表鎖,我們知道表鎖會嚴(yán)重影響我們對整張表的操作,應(yīng)該避免這種情況。

3. 控制事務(wù)的大小,減少鎖定的資源和鎖定的時間
下邊這個SQL異常相比很多并發(fā)比較高的系統(tǒng)里都會遇見,比如搶購系統(tǒng)的日志中:

MySQLQueryInterruptedException: Query execution was interrupted

由于搶購系統(tǒng)中,提交訂單業(yè)務(wù)開啟了事務(wù),在并發(fā)環(huán)境中對一條記錄進(jìn)行更新操作的情況下,由于更新記錄所在的事務(wù)還可能存在其他操作,導(dǎo)致一個事務(wù)比較長,當(dāng)大量請求進(jìn)入時,就可能導(dǎo)致一些請求同時進(jìn)入事務(wù)中,由于鎖的競爭是不公平的,當(dāng)多個事務(wù)同時對一條記錄進(jìn)行更新時,極端情況下,一個更新操作進(jìn)去排隊系統(tǒng)后,可能會一直拿不到鎖,最后因超市被系統(tǒng)中斷,就會拋出上邊這個異常。

在上面的下單服務(wù)中,提交訂單需要創(chuàng)建訂單和扣減庫存,兩種不同順序的執(zhí)行方式,結(jié)果都一樣,但是性能確實不一樣的:

執(zhí)行順序1 執(zhí)行順序2
1.開啟事務(wù)
2.查詢庫存,判斷庫存是否滿足
3.創(chuàng)建訂單
4.扣除庫存
5.提交或回滾
1.開啟事務(wù)
2.查詢庫存,判斷庫存是否滿足
3.扣除庫存
4.創(chuàng)建訂單
5.提交或回滾

這兩種不同的執(zhí)行方式,雖然這些操作都在一個事務(wù)中,但是鎖的申請不在同一時間,鎖只有當(dāng)其他操作都執(zhí)行完成才會釋放鎖??蹨p庫存是更新操作,屬于行鎖,如果先扣減庫存會影響到其他操作該數(shù)據(jù)的事務(wù),所以我們應(yīng)該盡可能的避免長時間持有該鎖,盡快的釋放鎖。

因為創(chuàng)建訂單和扣除庫存不管先執(zhí)行哪一步都不影響業(yè)務(wù),所以我們可以先執(zhí)行新增操作,把扣除庫存放到最后,也就是使用執(zhí)行順序1 ,來減少鎖的持有時間。

實戰(zhàn)演練 - DB設(shè)計

數(shù)據(jù)結(jié)構(gòu)

TODO

SQL語句注意事項

1)一定要避免全表掃描,如果掃一張大表的數(shù)據(jù)就會造成慢查詢,導(dǎo)致數(shù)據(jù)的連接池直接塞滿,導(dǎo)致事故。
首先考慮在where和order by設(shè)計的列上建立索引例如:

  • where 子句中對字段進(jìn)行 null 值判斷
  • 應(yīng)盡量避免在 where 子句中使用!=或<>操作符
  • 應(yīng)盡量避免在 where 子句中使用 or 來連接條件
  • in 和 not in 也要慎用
  • 在優(yōu)化大表連接查詢的時候,有一個方法就是join操作拆分為in查詢
  • 而select id from t where name like 'abc%' 才用到索引,慢查詢一般在測試環(huán)境不容易復(fù)現(xiàn),若要提高效率,可以考慮全文檢索。
  • 應(yīng)盡量避免在 where 子句中對字段進(jìn)行表達(dá)式操作例如:where num/2 , num=100*2

2)合理的使用索引,索引并不是越多越好,使用不當(dāng)會造成性能開銷。
3)盡量避免大事務(wù)操作,提高系統(tǒng)并發(fā)能力。
4)盡量避免象客戶端返回大量數(shù)據(jù),如果返回則要考慮是否需求合理。

實戰(zhàn)演練 - 高可用方案

說到系統(tǒng)的高可用建設(shè),它其實是一個系統(tǒng)工程,需要考慮到系統(tǒng)建設(shè)的各個階段,也就是說它其實貫穿了系統(tǒng)建設(shè)的整個生命周期,如下圖所示:

高可用生命周期

高可用是一個比較大的議題,我們這里就幾個常用的限流熔斷,服務(wù)拒絕這幾個方面簡單做下介紹。

1)限流
限流就是當(dāng)系統(tǒng)容量達(dá)到瓶頸時,我們需要通過限制一部分流量來保護(hù)系統(tǒng),并做到既可以人工執(zhí)行開關(guān),也支持自動化保護(hù)的措施。這里推薦一個比較好的限流開源框架Guava-RateLimiter

2)熔斷
熔斷就是切斷項目對指定服務(wù)的調(diào)用。舉個例子在分布式環(huán)境下有A,B,C,D四個個服務(wù),A依賴B,C,D。在調(diào)用的過程中發(fā)現(xiàn)D服務(wù)異常了,為了不拖垮整個集群,我們會選擇不調(diào)用D服務(wù),進(jìn)行服務(wù)降級。這里推薦一個比較好的限流開源框架Hystrix基于dubbo的samples

3)服務(wù)拒絕
當(dāng)系統(tǒng)負(fù)載達(dá)到一定閾值時,例如 CPU 使用率達(dá)到 90% 或者系統(tǒng) load 值達(dá)到 2*CPU 核數(shù)時,系統(tǒng)直接拒絕所有請求,這種方式是最暴力但也最有效的系統(tǒng)保護(hù)方式。例如秒殺系統(tǒng),我們在如下幾個環(huán)節(jié)設(shè)計過載保護(hù):

在最前端的 Nginx 上設(shè)置過載保護(hù),當(dāng)機(jī)器負(fù)載達(dá)到某個值時直接拒絕 HTTP 請求并返回 503 錯誤碼,在 Java 層同樣也可以設(shè)計過載保護(hù)。

4)降級非核心功能
就是當(dāng)系統(tǒng)的容量達(dá)到一定程度時,限制或者關(guān)閉系統(tǒng)的某些非核心功能,從而把有限的資源保留給更核心的業(yè)務(wù)。但是這種功能最好有一個配置開關(guān)。例如:當(dāng)秒殺流量達(dá)到 5w/s 時,把成交記錄的獲取從展示 20 條降級到只展示 5 條?!皬?20 改到 5”這個操作由一個開關(guān)來實現(xiàn),也就是設(shè)置一個能夠從開關(guān)系統(tǒng)動態(tài)獲取的系統(tǒng)參數(shù)。又例如:商品詳情的優(yōu)惠信息展示,把有限的系統(tǒng)資源用在保障交易系統(tǒng)正確展示優(yōu)惠信息上,即保障用戶真正下單時的價格是正確的。

實戰(zhàn)演練 - 其它議題和知識儲備

在實施落地秒殺一個落地系統(tǒng)時,我們可能還需也要很多知識儲備。例如:分布式緩存Redis知識,Nginx優(yōu)化,HTTP/PRC知識,DB設(shè)計、秒殺安全、分庫分表、多數(shù)據(jù)源,分布式鎖知識,分布式事務(wù)和補償知識等等。足夠的知識儲備,適當(dāng)?shù)倪x擇使用才能幫助我們更好的落地系統(tǒng)。

參考:
極客時間專欄-如何設(shè)計秒殺系統(tǒng)
如何設(shè)計一個秒殺系統(tǒng)
如何設(shè)計一個秒殺系統(tǒng) CSDN
秒殺常見問題
需求收集和總體架構(gòu)設(shè)計
Guava-RateLimiter詳解
吊打面試官系列-秒殺系統(tǒng)設(shè)計
并發(fā)減庫存,怎么保證不超賣?
緩存與數(shù)據(jù)庫一致性系列
分布式系統(tǒng)的一致性問題
這一次,徹底弄懂“秒殺系統(tǒng)”
分布式柔性事務(wù)的TCC方案
聊聊分布式事務(wù),再說說解決方案
我用分布式事務(wù)干掉了一摞簡歷
高并發(fā)場景下的數(shù)據(jù)庫事務(wù)調(diào)優(yōu)
聊一下分布式事務(wù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容