'如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)'

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


客戶端:esxi 5.0 虛擬機,配置為4核 5G內存。一共17臺,每臺和服務器建立6萬個連接。完成100萬客戶端模擬

5. 技術分析和實現

5.1) 單機實現100萬用戶連接

這一點來說相對簡單,筆者在幾年前就早完成了單機百萬用戶的開發以及操作。現代的服務器都可以支持百萬用戶。相關內容可以查看:

github代碼以及相關文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系統配置以及優化文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3萬QPS

這個問題需要分2個部分來看客戶端方面和服務器方面。

  • 客戶端QPS


因為有100萬連接連在服務器上,QPS為3萬。這就意味著每個連接每33秒,就需要向服務器發一個搖紅包的請求。

因為單IP可以建立的連接數為6萬左右, 有17臺服務器同時模擬客戶端行為。我們要做的就保證在每一秒都有這麼多的請求發往服務器即可。

其中技術要點就是客戶端協同。但是各個客戶端的啟動時間,建立連接的時間都不一致,還存在網絡斷開重連這樣的情況,各個客戶端如何判斷何時自己需要發送請求,各自該發送多少請求呢?

我是這樣解決的:利用NTP服務,同步所有的服務器時間,客戶端利用時間戳來判斷自己的此時需要發送多少請求。

算法很容易實現:假設有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數為100萬,它計算 100萬/5萬=20,所有的用戶應該分為20組

如果 time() % 20 == 用戶id % 20,那麼這個id的用戶就該在這一秒發出請求,如此實現了多客戶端協同工作。每個客戶端只需要知道 總用戶數和QPS 就能自行準確發出請求了。

(擴展思考:如果QPS是3萬 這樣不能被整除的數目,該如何辦?如何保證每臺客戶端發出的請求數目儘量的均衡呢?)

  • 服務器QPS

服務器端的QPS相對簡單,它只需要處理客戶端的請求即可。但是為了客觀瞭解處理情況,我們還需要做2件事情。

  • 第一:需要記錄每秒處理的請求數目,這需要在代碼裡埋入計數器。
  • 第二:我們需要監控網絡,因為網絡的吞吐情況,可以客觀的反映出QPS的真實數據。
  • 為此,我利用python腳本 結合ethtool 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網絡的數據包通過情況如何。它可以客觀的顯示出我們的網絡有如此多的數據傳輸在發生。


工具截圖:

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


客戶端:esxi 5.0 虛擬機,配置為4核 5G內存。一共17臺,每臺和服務器建立6萬個連接。完成100萬客戶端模擬

5. 技術分析和實現

5.1) 單機實現100萬用戶連接

這一點來說相對簡單,筆者在幾年前就早完成了單機百萬用戶的開發以及操作。現代的服務器都可以支持百萬用戶。相關內容可以查看:

github代碼以及相關文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系統配置以及優化文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3萬QPS

這個問題需要分2個部分來看客戶端方面和服務器方面。

  • 客戶端QPS


因為有100萬連接連在服務器上,QPS為3萬。這就意味著每個連接每33秒,就需要向服務器發一個搖紅包的請求。

因為單IP可以建立的連接數為6萬左右, 有17臺服務器同時模擬客戶端行為。我們要做的就保證在每一秒都有這麼多的請求發往服務器即可。

其中技術要點就是客戶端協同。但是各個客戶端的啟動時間,建立連接的時間都不一致,還存在網絡斷開重連這樣的情況,各個客戶端如何判斷何時自己需要發送請求,各自該發送多少請求呢?

我是這樣解決的:利用NTP服務,同步所有的服務器時間,客戶端利用時間戳來判斷自己的此時需要發送多少請求。

算法很容易實現:假設有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數為100萬,它計算 100萬/5萬=20,所有的用戶應該分為20組

如果 time() % 20 == 用戶id % 20,那麼這個id的用戶就該在這一秒發出請求,如此實現了多客戶端協同工作。每個客戶端只需要知道 總用戶數和QPS 就能自行準確發出請求了。

(擴展思考:如果QPS是3萬 這樣不能被整除的數目,該如何辦?如何保證每臺客戶端發出的請求數目儘量的均衡呢?)

  • 服務器QPS

服務器端的QPS相對簡單,它只需要處理客戶端的請求即可。但是為了客觀瞭解處理情況,我們還需要做2件事情。

  • 第一:需要記錄每秒處理的請求數目,這需要在代碼裡埋入計數器。
  • 第二:我們需要監控網絡,因為網絡的吞吐情況,可以客觀的反映出QPS的真實數據。
  • 為此,我利用python腳本 結合ethtool 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網絡的數據包通過情況如何。它可以客觀的顯示出我們的網絡有如此多的數據傳輸在發生。


工具截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


5.3) 搖紅包業務

搖紅包的業務非常簡單,首先服務器按照一定的速度生產紅包。紅包沒有被取走的話,就堆積在裡面。

服務器接收一個客戶端的請求,如果服務器裡現在有紅包就會告訴客戶端有,否則就提示沒有紅包。

因為單機每秒有3萬的請求,所以大部分的請求會失敗。只需要處理好鎖的問題即可。

我為了減少競爭,將所有的用戶分在了不同的桶裡。這樣可以減少對鎖的競爭。如果以後還有更高的性能要求,還可以使用 高性能隊列——Disruptor來進一步提高性能。

注意,在我的測試環境裡是缺少支付這個核心服務的,所以實現的難度是大大的減輕了。

另外提供一組數字:2016年淘寶的雙11的交易峰值僅僅為12萬/秒,微信紅包分發速度是5萬/秒,要做到這點是非常困難的。(http://mt.sohu.com/20161111/n472951708.shtml)

5.4) 發紅包業務

發紅包的業務很簡單,系統隨機產生一些紅包,並且隨機選擇一些用戶,系統向這些用戶提示有紅包。

這些用戶只需要發出拆紅包的請求,系統就可以隨機從紅包中拆分出部分金額,分給用戶,完成這個業務。同樣這裡也沒有支付這個核心服務。

5.5)監控

最後 我們需要一套監控系統來了解系統的狀況,我借用了我另一個項目(https://github.com/xiaojiaqi/fakewechat)

裡的部分代碼完成了這個監控模塊,利用這個監控,服務器和客戶端會把當前的計數器內容發往監控,監控需要把各個客戶端的數據做一個整合和展示。

同時還會把日誌記錄下來,給以後的分析提供原始數據。線上系統更多使用opentsdb這樣的時序數據庫,這裡資源有限,所以用了一個原始的方案。

監控顯示日誌大概這樣:

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


客戶端:esxi 5.0 虛擬機,配置為4核 5G內存。一共17臺,每臺和服務器建立6萬個連接。完成100萬客戶端模擬

5. 技術分析和實現

5.1) 單機實現100萬用戶連接

這一點來說相對簡單,筆者在幾年前就早完成了單機百萬用戶的開發以及操作。現代的服務器都可以支持百萬用戶。相關內容可以查看:

github代碼以及相關文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系統配置以及優化文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3萬QPS

這個問題需要分2個部分來看客戶端方面和服務器方面。

  • 客戶端QPS


因為有100萬連接連在服務器上,QPS為3萬。這就意味著每個連接每33秒,就需要向服務器發一個搖紅包的請求。

因為單IP可以建立的連接數為6萬左右, 有17臺服務器同時模擬客戶端行為。我們要做的就保證在每一秒都有這麼多的請求發往服務器即可。

其中技術要點就是客戶端協同。但是各個客戶端的啟動時間,建立連接的時間都不一致,還存在網絡斷開重連這樣的情況,各個客戶端如何判斷何時自己需要發送請求,各自該發送多少請求呢?

我是這樣解決的:利用NTP服務,同步所有的服務器時間,客戶端利用時間戳來判斷自己的此時需要發送多少請求。

算法很容易實現:假設有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數為100萬,它計算 100萬/5萬=20,所有的用戶應該分為20組

如果 time() % 20 == 用戶id % 20,那麼這個id的用戶就該在這一秒發出請求,如此實現了多客戶端協同工作。每個客戶端只需要知道 總用戶數和QPS 就能自行準確發出請求了。

(擴展思考:如果QPS是3萬 這樣不能被整除的數目,該如何辦?如何保證每臺客戶端發出的請求數目儘量的均衡呢?)

  • 服務器QPS

服務器端的QPS相對簡單,它只需要處理客戶端的請求即可。但是為了客觀瞭解處理情況,我們還需要做2件事情。

  • 第一:需要記錄每秒處理的請求數目,這需要在代碼裡埋入計數器。
  • 第二:我們需要監控網絡,因為網絡的吞吐情況,可以客觀的反映出QPS的真實數據。
  • 為此,我利用python腳本 結合ethtool 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網絡的數據包通過情況如何。它可以客觀的顯示出我們的網絡有如此多的數據傳輸在發生。


工具截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


5.3) 搖紅包業務

搖紅包的業務非常簡單,首先服務器按照一定的速度生產紅包。紅包沒有被取走的話,就堆積在裡面。

服務器接收一個客戶端的請求,如果服務器裡現在有紅包就會告訴客戶端有,否則就提示沒有紅包。

因為單機每秒有3萬的請求,所以大部分的請求會失敗。只需要處理好鎖的問題即可。

我為了減少競爭,將所有的用戶分在了不同的桶裡。這樣可以減少對鎖的競爭。如果以後還有更高的性能要求,還可以使用 高性能隊列——Disruptor來進一步提高性能。

注意,在我的測試環境裡是缺少支付這個核心服務的,所以實現的難度是大大的減輕了。

另外提供一組數字:2016年淘寶的雙11的交易峰值僅僅為12萬/秒,微信紅包分發速度是5萬/秒,要做到這點是非常困難的。(http://mt.sohu.com/20161111/n472951708.shtml)

5.4) 發紅包業務

發紅包的業務很簡單,系統隨機產生一些紅包,並且隨機選擇一些用戶,系統向這些用戶提示有紅包。

這些用戶只需要發出拆紅包的請求,系統就可以隨機從紅包中拆分出部分金額,分給用戶,完成這個業務。同樣這裡也沒有支付這個核心服務。

5.5)監控

最後 我們需要一套監控系統來了解系統的狀況,我借用了我另一個項目(https://github.com/xiaojiaqi/fakewechat)

裡的部分代碼完成了這個監控模塊,利用這個監控,服務器和客戶端會把當前的計數器內容發往監控,監控需要把各個客戶端的數據做一個整合和展示。

同時還會把日誌記錄下來,給以後的分析提供原始數據。線上系統更多使用opentsdb這樣的時序數據庫,這裡資源有限,所以用了一個原始的方案。

監控顯示日誌大概這樣:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


6. 代碼實現及分析

在代碼方面,使用到的技巧實在不多,主要是設計思想和golang本身的一些問題需要考慮。

首先golang的goroutine 的數目控制,因為至少有100萬以上的連接,所以按照普通的設計方案,至少需要200萬或者300萬的goroutine在工作。這會造成系統本身的負擔很重。

其次就是100萬個連接的管理,無論是連接還是業務都會造成一些心智的負擔。

我的設計是這樣的:

首先將100萬連接分成多個不同的SET,每個SET是一個獨立,平行的對象。每個SET 只管理幾千個連接,如果單個SET 工作正常,我只需要添加SET就能提高系統處理能力。

其次謹慎的設計了每個SET裡數據結構的大小,保證每個SET的壓力不會太大,不會出現消息的堆積。

再次減少了gcroutine的數目,每個連接只使用一個goroutine,發送消息在一個SET裡只有一個gcroutine負責,這樣節省了100萬個goroutine。

這樣整個系統只需要保留 100萬零幾百個gcroutine就能完成業務。大量的節省了cpu 和內存

系統的工作流程大概是:每個客戶端連接成功後,系統會分配一個goroutine讀取客戶端的消息當消息讀取完成,將它轉化為消息對象放至在SET的接收消息隊列,然後返回獲取下一個消息。

在SET內部,有一個工作goroutine,它只做非常簡單而高效的事情,它做的事情如下,檢查SET的接受消息,它會收到3類消息

  1. 客戶端的搖紅包請求消息
  2. 客戶端的其他消息 比如聊天 好友這一類
  3. 服務器端對客戶端消息的迴應

對於第1種消息客戶端的搖紅包請求消息 是這樣處理的,從客戶端拿到搖紅包請求消息,試圖從SET的紅包隊列裡 獲取一個紅包

如果拿到了就把紅包信息 返回給客戶端,否則構造一個沒有搖到的消息,返回給對應的客戶端。

對於第2種消息客戶端的其他消息 比如聊天 好友這一類,只需簡單地從隊列裡拿走消息,轉發給後端的聊天服務隊列即可,其他服務會把消息轉發出去。

對於第3種消息服務器端對客戶端消息的迴應。SET 只需要根據消息裡的用戶id,找到SET裡保留的用戶連接對象,發回去就可以了。

對於紅包產生服務,它的工作很簡單,只需要按照順序在輪流在每個SET的紅包產生對列裡放至紅包對象就可以了。這樣可以保證每個SET裡都是公平的,其次它的工作強度很低,可以保證業務穩定。

見代碼:

https://github.com/xiaojiaqi/10billionhongbaos

7. 實踐

實踐的過程分為3個階段

階段1

分別啟動服務器端和監控端,然後逐一啟動17臺客戶端,讓它們建立起100萬的鏈接。

在服務器端,利用ss 命令 統計出每個客戶端和服務器建立了多少連接。

命令如下:

Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print $8}” | sort | uniq –c’

結果如下:

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


客戶端:esxi 5.0 虛擬機,配置為4核 5G內存。一共17臺,每臺和服務器建立6萬個連接。完成100萬客戶端模擬

5. 技術分析和實現

5.1) 單機實現100萬用戶連接

這一點來說相對簡單,筆者在幾年前就早完成了單機百萬用戶的開發以及操作。現代的服務器都可以支持百萬用戶。相關內容可以查看:

github代碼以及相關文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系統配置以及優化文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3萬QPS

這個問題需要分2個部分來看客戶端方面和服務器方面。

  • 客戶端QPS


因為有100萬連接連在服務器上,QPS為3萬。這就意味著每個連接每33秒,就需要向服務器發一個搖紅包的請求。

因為單IP可以建立的連接數為6萬左右, 有17臺服務器同時模擬客戶端行為。我們要做的就保證在每一秒都有這麼多的請求發往服務器即可。

其中技術要點就是客戶端協同。但是各個客戶端的啟動時間,建立連接的時間都不一致,還存在網絡斷開重連這樣的情況,各個客戶端如何判斷何時自己需要發送請求,各自該發送多少請求呢?

我是這樣解決的:利用NTP服務,同步所有的服務器時間,客戶端利用時間戳來判斷自己的此時需要發送多少請求。

算法很容易實現:假設有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數為100萬,它計算 100萬/5萬=20,所有的用戶應該分為20組

如果 time() % 20 == 用戶id % 20,那麼這個id的用戶就該在這一秒發出請求,如此實現了多客戶端協同工作。每個客戶端只需要知道 總用戶數和QPS 就能自行準確發出請求了。

(擴展思考:如果QPS是3萬 這樣不能被整除的數目,該如何辦?如何保證每臺客戶端發出的請求數目儘量的均衡呢?)

  • 服務器QPS

服務器端的QPS相對簡單,它只需要處理客戶端的請求即可。但是為了客觀瞭解處理情況,我們還需要做2件事情。

  • 第一:需要記錄每秒處理的請求數目,這需要在代碼裡埋入計數器。
  • 第二:我們需要監控網絡,因為網絡的吞吐情況,可以客觀的反映出QPS的真實數據。
  • 為此,我利用python腳本 結合ethtool 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網絡的數據包通過情況如何。它可以客觀的顯示出我們的網絡有如此多的數據傳輸在發生。


工具截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


5.3) 搖紅包業務

搖紅包的業務非常簡單,首先服務器按照一定的速度生產紅包。紅包沒有被取走的話,就堆積在裡面。

服務器接收一個客戶端的請求,如果服務器裡現在有紅包就會告訴客戶端有,否則就提示沒有紅包。

因為單機每秒有3萬的請求,所以大部分的請求會失敗。只需要處理好鎖的問題即可。

我為了減少競爭,將所有的用戶分在了不同的桶裡。這樣可以減少對鎖的競爭。如果以後還有更高的性能要求,還可以使用 高性能隊列——Disruptor來進一步提高性能。

注意,在我的測試環境裡是缺少支付這個核心服務的,所以實現的難度是大大的減輕了。

另外提供一組數字:2016年淘寶的雙11的交易峰值僅僅為12萬/秒,微信紅包分發速度是5萬/秒,要做到這點是非常困難的。(http://mt.sohu.com/20161111/n472951708.shtml)

5.4) 發紅包業務

發紅包的業務很簡單,系統隨機產生一些紅包,並且隨機選擇一些用戶,系統向這些用戶提示有紅包。

這些用戶只需要發出拆紅包的請求,系統就可以隨機從紅包中拆分出部分金額,分給用戶,完成這個業務。同樣這裡也沒有支付這個核心服務。

5.5)監控

最後 我們需要一套監控系統來了解系統的狀況,我借用了我另一個項目(https://github.com/xiaojiaqi/fakewechat)

裡的部分代碼完成了這個監控模塊,利用這個監控,服務器和客戶端會把當前的計數器內容發往監控,監控需要把各個客戶端的數據做一個整合和展示。

同時還會把日誌記錄下來,給以後的分析提供原始數據。線上系統更多使用opentsdb這樣的時序數據庫,這裡資源有限,所以用了一個原始的方案。

監控顯示日誌大概這樣:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


6. 代碼實現及分析

在代碼方面,使用到的技巧實在不多,主要是設計思想和golang本身的一些問題需要考慮。

首先golang的goroutine 的數目控制,因為至少有100萬以上的連接,所以按照普通的設計方案,至少需要200萬或者300萬的goroutine在工作。這會造成系統本身的負擔很重。

其次就是100萬個連接的管理,無論是連接還是業務都會造成一些心智的負擔。

我的設計是這樣的:

首先將100萬連接分成多個不同的SET,每個SET是一個獨立,平行的對象。每個SET 只管理幾千個連接,如果單個SET 工作正常,我只需要添加SET就能提高系統處理能力。

其次謹慎的設計了每個SET裡數據結構的大小,保證每個SET的壓力不會太大,不會出現消息的堆積。

再次減少了gcroutine的數目,每個連接只使用一個goroutine,發送消息在一個SET裡只有一個gcroutine負責,這樣節省了100萬個goroutine。

這樣整個系統只需要保留 100萬零幾百個gcroutine就能完成業務。大量的節省了cpu 和內存

系統的工作流程大概是:每個客戶端連接成功後,系統會分配一個goroutine讀取客戶端的消息當消息讀取完成,將它轉化為消息對象放至在SET的接收消息隊列,然後返回獲取下一個消息。

在SET內部,有一個工作goroutine,它只做非常簡單而高效的事情,它做的事情如下,檢查SET的接受消息,它會收到3類消息

  1. 客戶端的搖紅包請求消息
  2. 客戶端的其他消息 比如聊天 好友這一類
  3. 服務器端對客戶端消息的迴應

對於第1種消息客戶端的搖紅包請求消息 是這樣處理的,從客戶端拿到搖紅包請求消息,試圖從SET的紅包隊列裡 獲取一個紅包

如果拿到了就把紅包信息 返回給客戶端,否則構造一個沒有搖到的消息,返回給對應的客戶端。

對於第2種消息客戶端的其他消息 比如聊天 好友這一類,只需簡單地從隊列裡拿走消息,轉發給後端的聊天服務隊列即可,其他服務會把消息轉發出去。

對於第3種消息服務器端對客戶端消息的迴應。SET 只需要根據消息裡的用戶id,找到SET裡保留的用戶連接對象,發回去就可以了。

對於紅包產生服務,它的工作很簡單,只需要按照順序在輪流在每個SET的紅包產生對列裡放至紅包對象就可以了。這樣可以保證每個SET裡都是公平的,其次它的工作強度很低,可以保證業務穩定。

見代碼:

https://github.com/xiaojiaqi/10billionhongbaos

7. 實踐

實踐的過程分為3個階段

階段1

分別啟動服務器端和監控端,然後逐一啟動17臺客戶端,讓它們建立起100萬的鏈接。

在服務器端,利用ss 命令 統計出每個客戶端和服務器建立了多少連接。

命令如下:

Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print $8}” | sort | uniq –c’

結果如下:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段2

利用客戶端的http接口,將所有的客戶端QPS 調整到3萬,讓客戶端發出3W QPS強度的請求。

運行如下命令:

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


客戶端:esxi 5.0 虛擬機,配置為4核 5G內存。一共17臺,每臺和服務器建立6萬個連接。完成100萬客戶端模擬

5. 技術分析和實現

5.1) 單機實現100萬用戶連接

這一點來說相對簡單,筆者在幾年前就早完成了單機百萬用戶的開發以及操作。現代的服務器都可以支持百萬用戶。相關內容可以查看:

github代碼以及相關文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系統配置以及優化文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3萬QPS

這個問題需要分2個部分來看客戶端方面和服務器方面。

  • 客戶端QPS


因為有100萬連接連在服務器上,QPS為3萬。這就意味著每個連接每33秒,就需要向服務器發一個搖紅包的請求。

因為單IP可以建立的連接數為6萬左右, 有17臺服務器同時模擬客戶端行為。我們要做的就保證在每一秒都有這麼多的請求發往服務器即可。

其中技術要點就是客戶端協同。但是各個客戶端的啟動時間,建立連接的時間都不一致,還存在網絡斷開重連這樣的情況,各個客戶端如何判斷何時自己需要發送請求,各自該發送多少請求呢?

我是這樣解決的:利用NTP服務,同步所有的服務器時間,客戶端利用時間戳來判斷自己的此時需要發送多少請求。

算法很容易實現:假設有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數為100萬,它計算 100萬/5萬=20,所有的用戶應該分為20組

如果 time() % 20 == 用戶id % 20,那麼這個id的用戶就該在這一秒發出請求,如此實現了多客戶端協同工作。每個客戶端只需要知道 總用戶數和QPS 就能自行準確發出請求了。

(擴展思考:如果QPS是3萬 這樣不能被整除的數目,該如何辦?如何保證每臺客戶端發出的請求數目儘量的均衡呢?)

  • 服務器QPS

服務器端的QPS相對簡單,它只需要處理客戶端的請求即可。但是為了客觀瞭解處理情況,我們還需要做2件事情。

  • 第一:需要記錄每秒處理的請求數目,這需要在代碼裡埋入計數器。
  • 第二:我們需要監控網絡,因為網絡的吞吐情況,可以客觀的反映出QPS的真實數據。
  • 為此,我利用python腳本 結合ethtool 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網絡的數據包通過情況如何。它可以客觀的顯示出我們的網絡有如此多的數據傳輸在發生。


工具截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


5.3) 搖紅包業務

搖紅包的業務非常簡單,首先服務器按照一定的速度生產紅包。紅包沒有被取走的話,就堆積在裡面。

服務器接收一個客戶端的請求,如果服務器裡現在有紅包就會告訴客戶端有,否則就提示沒有紅包。

因為單機每秒有3萬的請求,所以大部分的請求會失敗。只需要處理好鎖的問題即可。

我為了減少競爭,將所有的用戶分在了不同的桶裡。這樣可以減少對鎖的競爭。如果以後還有更高的性能要求,還可以使用 高性能隊列——Disruptor來進一步提高性能。

注意,在我的測試環境裡是缺少支付這個核心服務的,所以實現的難度是大大的減輕了。

另外提供一組數字:2016年淘寶的雙11的交易峰值僅僅為12萬/秒,微信紅包分發速度是5萬/秒,要做到這點是非常困難的。(http://mt.sohu.com/20161111/n472951708.shtml)

5.4) 發紅包業務

發紅包的業務很簡單,系統隨機產生一些紅包,並且隨機選擇一些用戶,系統向這些用戶提示有紅包。

這些用戶只需要發出拆紅包的請求,系統就可以隨機從紅包中拆分出部分金額,分給用戶,完成這個業務。同樣這裡也沒有支付這個核心服務。

5.5)監控

最後 我們需要一套監控系統來了解系統的狀況,我借用了我另一個項目(https://github.com/xiaojiaqi/fakewechat)

裡的部分代碼完成了這個監控模塊,利用這個監控,服務器和客戶端會把當前的計數器內容發往監控,監控需要把各個客戶端的數據做一個整合和展示。

同時還會把日誌記錄下來,給以後的分析提供原始數據。線上系統更多使用opentsdb這樣的時序數據庫,這裡資源有限,所以用了一個原始的方案。

監控顯示日誌大概這樣:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


6. 代碼實現及分析

在代碼方面,使用到的技巧實在不多,主要是設計思想和golang本身的一些問題需要考慮。

首先golang的goroutine 的數目控制,因為至少有100萬以上的連接,所以按照普通的設計方案,至少需要200萬或者300萬的goroutine在工作。這會造成系統本身的負擔很重。

其次就是100萬個連接的管理,無論是連接還是業務都會造成一些心智的負擔。

我的設計是這樣的:

首先將100萬連接分成多個不同的SET,每個SET是一個獨立,平行的對象。每個SET 只管理幾千個連接,如果單個SET 工作正常,我只需要添加SET就能提高系統處理能力。

其次謹慎的設計了每個SET裡數據結構的大小,保證每個SET的壓力不會太大,不會出現消息的堆積。

再次減少了gcroutine的數目,每個連接只使用一個goroutine,發送消息在一個SET裡只有一個gcroutine負責,這樣節省了100萬個goroutine。

這樣整個系統只需要保留 100萬零幾百個gcroutine就能完成業務。大量的節省了cpu 和內存

系統的工作流程大概是:每個客戶端連接成功後,系統會分配一個goroutine讀取客戶端的消息當消息讀取完成,將它轉化為消息對象放至在SET的接收消息隊列,然後返回獲取下一個消息。

在SET內部,有一個工作goroutine,它只做非常簡單而高效的事情,它做的事情如下,檢查SET的接受消息,它會收到3類消息

  1. 客戶端的搖紅包請求消息
  2. 客戶端的其他消息 比如聊天 好友這一類
  3. 服務器端對客戶端消息的迴應

對於第1種消息客戶端的搖紅包請求消息 是這樣處理的,從客戶端拿到搖紅包請求消息,試圖從SET的紅包隊列裡 獲取一個紅包

如果拿到了就把紅包信息 返回給客戶端,否則構造一個沒有搖到的消息,返回給對應的客戶端。

對於第2種消息客戶端的其他消息 比如聊天 好友這一類,只需簡單地從隊列裡拿走消息,轉發給後端的聊天服務隊列即可,其他服務會把消息轉發出去。

對於第3種消息服務器端對客戶端消息的迴應。SET 只需要根據消息裡的用戶id,找到SET裡保留的用戶連接對象,發回去就可以了。

對於紅包產生服務,它的工作很簡單,只需要按照順序在輪流在每個SET的紅包產生對列裡放至紅包對象就可以了。這樣可以保證每個SET裡都是公平的,其次它的工作強度很低,可以保證業務穩定。

見代碼:

https://github.com/xiaojiaqi/10billionhongbaos

7. 實踐

實踐的過程分為3個階段

階段1

分別啟動服務器端和監控端,然後逐一啟動17臺客戶端,讓它們建立起100萬的鏈接。

在服務器端,利用ss 命令 統計出每個客戶端和服務器建立了多少連接。

命令如下:

Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print $8}” | sort | uniq –c’

結果如下:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段2

利用客戶端的http接口,將所有的客戶端QPS 調整到3萬,讓客戶端發出3W QPS強度的請求。

運行如下命令:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


觀察網絡監控和監控端反饋,發現QPS 達到預期數據,網絡監控截圖:

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


客戶端:esxi 5.0 虛擬機,配置為4核 5G內存。一共17臺,每臺和服務器建立6萬個連接。完成100萬客戶端模擬

5. 技術分析和實現

5.1) 單機實現100萬用戶連接

這一點來說相對簡單,筆者在幾年前就早完成了單機百萬用戶的開發以及操作。現代的服務器都可以支持百萬用戶。相關內容可以查看:

github代碼以及相關文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系統配置以及優化文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3萬QPS

這個問題需要分2個部分來看客戶端方面和服務器方面。

  • 客戶端QPS


因為有100萬連接連在服務器上,QPS為3萬。這就意味著每個連接每33秒,就需要向服務器發一個搖紅包的請求。

因為單IP可以建立的連接數為6萬左右, 有17臺服務器同時模擬客戶端行為。我們要做的就保證在每一秒都有這麼多的請求發往服務器即可。

其中技術要點就是客戶端協同。但是各個客戶端的啟動時間,建立連接的時間都不一致,還存在網絡斷開重連這樣的情況,各個客戶端如何判斷何時自己需要發送請求,各自該發送多少請求呢?

我是這樣解決的:利用NTP服務,同步所有的服務器時間,客戶端利用時間戳來判斷自己的此時需要發送多少請求。

算法很容易實現:假設有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數為100萬,它計算 100萬/5萬=20,所有的用戶應該分為20組

如果 time() % 20 == 用戶id % 20,那麼這個id的用戶就該在這一秒發出請求,如此實現了多客戶端協同工作。每個客戶端只需要知道 總用戶數和QPS 就能自行準確發出請求了。

(擴展思考:如果QPS是3萬 這樣不能被整除的數目,該如何辦?如何保證每臺客戶端發出的請求數目儘量的均衡呢?)

  • 服務器QPS

服務器端的QPS相對簡單,它只需要處理客戶端的請求即可。但是為了客觀瞭解處理情況,我們還需要做2件事情。

  • 第一:需要記錄每秒處理的請求數目,這需要在代碼裡埋入計數器。
  • 第二:我們需要監控網絡,因為網絡的吞吐情況,可以客觀的反映出QPS的真實數據。
  • 為此,我利用python腳本 結合ethtool 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網絡的數據包通過情況如何。它可以客觀的顯示出我們的網絡有如此多的數據傳輸在發生。


工具截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


5.3) 搖紅包業務

搖紅包的業務非常簡單,首先服務器按照一定的速度生產紅包。紅包沒有被取走的話,就堆積在裡面。

服務器接收一個客戶端的請求,如果服務器裡現在有紅包就會告訴客戶端有,否則就提示沒有紅包。

因為單機每秒有3萬的請求,所以大部分的請求會失敗。只需要處理好鎖的問題即可。

我為了減少競爭,將所有的用戶分在了不同的桶裡。這樣可以減少對鎖的競爭。如果以後還有更高的性能要求,還可以使用 高性能隊列——Disruptor來進一步提高性能。

注意,在我的測試環境裡是缺少支付這個核心服務的,所以實現的難度是大大的減輕了。

另外提供一組數字:2016年淘寶的雙11的交易峰值僅僅為12萬/秒,微信紅包分發速度是5萬/秒,要做到這點是非常困難的。(http://mt.sohu.com/20161111/n472951708.shtml)

5.4) 發紅包業務

發紅包的業務很簡單,系統隨機產生一些紅包,並且隨機選擇一些用戶,系統向這些用戶提示有紅包。

這些用戶只需要發出拆紅包的請求,系統就可以隨機從紅包中拆分出部分金額,分給用戶,完成這個業務。同樣這裡也沒有支付這個核心服務。

5.5)監控

最後 我們需要一套監控系統來了解系統的狀況,我借用了我另一個項目(https://github.com/xiaojiaqi/fakewechat)

裡的部分代碼完成了這個監控模塊,利用這個監控,服務器和客戶端會把當前的計數器內容發往監控,監控需要把各個客戶端的數據做一個整合和展示。

同時還會把日誌記錄下來,給以後的分析提供原始數據。線上系統更多使用opentsdb這樣的時序數據庫,這裡資源有限,所以用了一個原始的方案。

監控顯示日誌大概這樣:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


6. 代碼實現及分析

在代碼方面,使用到的技巧實在不多,主要是設計思想和golang本身的一些問題需要考慮。

首先golang的goroutine 的數目控制,因為至少有100萬以上的連接,所以按照普通的設計方案,至少需要200萬或者300萬的goroutine在工作。這會造成系統本身的負擔很重。

其次就是100萬個連接的管理,無論是連接還是業務都會造成一些心智的負擔。

我的設計是這樣的:

首先將100萬連接分成多個不同的SET,每個SET是一個獨立,平行的對象。每個SET 只管理幾千個連接,如果單個SET 工作正常,我只需要添加SET就能提高系統處理能力。

其次謹慎的設計了每個SET裡數據結構的大小,保證每個SET的壓力不會太大,不會出現消息的堆積。

再次減少了gcroutine的數目,每個連接只使用一個goroutine,發送消息在一個SET裡只有一個gcroutine負責,這樣節省了100萬個goroutine。

這樣整個系統只需要保留 100萬零幾百個gcroutine就能完成業務。大量的節省了cpu 和內存

系統的工作流程大概是:每個客戶端連接成功後,系統會分配一個goroutine讀取客戶端的消息當消息讀取完成,將它轉化為消息對象放至在SET的接收消息隊列,然後返回獲取下一個消息。

在SET內部,有一個工作goroutine,它只做非常簡單而高效的事情,它做的事情如下,檢查SET的接受消息,它會收到3類消息

  1. 客戶端的搖紅包請求消息
  2. 客戶端的其他消息 比如聊天 好友這一類
  3. 服務器端對客戶端消息的迴應

對於第1種消息客戶端的搖紅包請求消息 是這樣處理的,從客戶端拿到搖紅包請求消息,試圖從SET的紅包隊列裡 獲取一個紅包

如果拿到了就把紅包信息 返回給客戶端,否則構造一個沒有搖到的消息,返回給對應的客戶端。

對於第2種消息客戶端的其他消息 比如聊天 好友這一類,只需簡單地從隊列裡拿走消息,轉發給後端的聊天服務隊列即可,其他服務會把消息轉發出去。

對於第3種消息服務器端對客戶端消息的迴應。SET 只需要根據消息裡的用戶id,找到SET裡保留的用戶連接對象,發回去就可以了。

對於紅包產生服務,它的工作很簡單,只需要按照順序在輪流在每個SET的紅包產生對列裡放至紅包對象就可以了。這樣可以保證每個SET裡都是公平的,其次它的工作強度很低,可以保證業務穩定。

見代碼:

https://github.com/xiaojiaqi/10billionhongbaos

7. 實踐

實踐的過程分為3個階段

階段1

分別啟動服務器端和監控端,然後逐一啟動17臺客戶端,讓它們建立起100萬的鏈接。

在服務器端,利用ss 命令 統計出每個客戶端和服務器建立了多少連接。

命令如下:

Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print $8}” | sort | uniq –c’

結果如下:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段2

利用客戶端的http接口,將所有的客戶端QPS 調整到3萬,讓客戶端發出3W QPS強度的請求。

運行如下命令:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


觀察網絡監控和監控端反饋,發現QPS 達到預期數據,網絡監控截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


在服務器端啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包,總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


客戶端:esxi 5.0 虛擬機,配置為4核 5G內存。一共17臺,每臺和服務器建立6萬個連接。完成100萬客戶端模擬

5. 技術分析和實現

5.1) 單機實現100萬用戶連接

這一點來說相對簡單,筆者在幾年前就早完成了單機百萬用戶的開發以及操作。現代的服務器都可以支持百萬用戶。相關內容可以查看:

github代碼以及相關文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系統配置以及優化文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3萬QPS

這個問題需要分2個部分來看客戶端方面和服務器方面。

  • 客戶端QPS


因為有100萬連接連在服務器上,QPS為3萬。這就意味著每個連接每33秒,就需要向服務器發一個搖紅包的請求。

因為單IP可以建立的連接數為6萬左右, 有17臺服務器同時模擬客戶端行為。我們要做的就保證在每一秒都有這麼多的請求發往服務器即可。

其中技術要點就是客戶端協同。但是各個客戶端的啟動時間,建立連接的時間都不一致,還存在網絡斷開重連這樣的情況,各個客戶端如何判斷何時自己需要發送請求,各自該發送多少請求呢?

我是這樣解決的:利用NTP服務,同步所有的服務器時間,客戶端利用時間戳來判斷自己的此時需要發送多少請求。

算法很容易實現:假設有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數為100萬,它計算 100萬/5萬=20,所有的用戶應該分為20組

如果 time() % 20 == 用戶id % 20,那麼這個id的用戶就該在這一秒發出請求,如此實現了多客戶端協同工作。每個客戶端只需要知道 總用戶數和QPS 就能自行準確發出請求了。

(擴展思考:如果QPS是3萬 這樣不能被整除的數目,該如何辦?如何保證每臺客戶端發出的請求數目儘量的均衡呢?)

  • 服務器QPS

服務器端的QPS相對簡單,它只需要處理客戶端的請求即可。但是為了客觀瞭解處理情況,我們還需要做2件事情。

  • 第一:需要記錄每秒處理的請求數目,這需要在代碼裡埋入計數器。
  • 第二:我們需要監控網絡,因為網絡的吞吐情況,可以客觀的反映出QPS的真實數據。
  • 為此,我利用python腳本 結合ethtool 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網絡的數據包通過情況如何。它可以客觀的顯示出我們的網絡有如此多的數據傳輸在發生。


工具截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


5.3) 搖紅包業務

搖紅包的業務非常簡單,首先服務器按照一定的速度生產紅包。紅包沒有被取走的話,就堆積在裡面。

服務器接收一個客戶端的請求,如果服務器裡現在有紅包就會告訴客戶端有,否則就提示沒有紅包。

因為單機每秒有3萬的請求,所以大部分的請求會失敗。只需要處理好鎖的問題即可。

我為了減少競爭,將所有的用戶分在了不同的桶裡。這樣可以減少對鎖的競爭。如果以後還有更高的性能要求,還可以使用 高性能隊列——Disruptor來進一步提高性能。

注意,在我的測試環境裡是缺少支付這個核心服務的,所以實現的難度是大大的減輕了。

另外提供一組數字:2016年淘寶的雙11的交易峰值僅僅為12萬/秒,微信紅包分發速度是5萬/秒,要做到這點是非常困難的。(http://mt.sohu.com/20161111/n472951708.shtml)

5.4) 發紅包業務

發紅包的業務很簡單,系統隨機產生一些紅包,並且隨機選擇一些用戶,系統向這些用戶提示有紅包。

這些用戶只需要發出拆紅包的請求,系統就可以隨機從紅包中拆分出部分金額,分給用戶,完成這個業務。同樣這裡也沒有支付這個核心服務。

5.5)監控

最後 我們需要一套監控系統來了解系統的狀況,我借用了我另一個項目(https://github.com/xiaojiaqi/fakewechat)

裡的部分代碼完成了這個監控模塊,利用這個監控,服務器和客戶端會把當前的計數器內容發往監控,監控需要把各個客戶端的數據做一個整合和展示。

同時還會把日誌記錄下來,給以後的分析提供原始數據。線上系統更多使用opentsdb這樣的時序數據庫,這裡資源有限,所以用了一個原始的方案。

監控顯示日誌大概這樣:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


6. 代碼實現及分析

在代碼方面,使用到的技巧實在不多,主要是設計思想和golang本身的一些問題需要考慮。

首先golang的goroutine 的數目控制,因為至少有100萬以上的連接,所以按照普通的設計方案,至少需要200萬或者300萬的goroutine在工作。這會造成系統本身的負擔很重。

其次就是100萬個連接的管理,無論是連接還是業務都會造成一些心智的負擔。

我的設計是這樣的:

首先將100萬連接分成多個不同的SET,每個SET是一個獨立,平行的對象。每個SET 只管理幾千個連接,如果單個SET 工作正常,我只需要添加SET就能提高系統處理能力。

其次謹慎的設計了每個SET裡數據結構的大小,保證每個SET的壓力不會太大,不會出現消息的堆積。

再次減少了gcroutine的數目,每個連接只使用一個goroutine,發送消息在一個SET裡只有一個gcroutine負責,這樣節省了100萬個goroutine。

這樣整個系統只需要保留 100萬零幾百個gcroutine就能完成業務。大量的節省了cpu 和內存

系統的工作流程大概是:每個客戶端連接成功後,系統會分配一個goroutine讀取客戶端的消息當消息讀取完成,將它轉化為消息對象放至在SET的接收消息隊列,然後返回獲取下一個消息。

在SET內部,有一個工作goroutine,它只做非常簡單而高效的事情,它做的事情如下,檢查SET的接受消息,它會收到3類消息

  1. 客戶端的搖紅包請求消息
  2. 客戶端的其他消息 比如聊天 好友這一類
  3. 服務器端對客戶端消息的迴應

對於第1種消息客戶端的搖紅包請求消息 是這樣處理的,從客戶端拿到搖紅包請求消息,試圖從SET的紅包隊列裡 獲取一個紅包

如果拿到了就把紅包信息 返回給客戶端,否則構造一個沒有搖到的消息,返回給對應的客戶端。

對於第2種消息客戶端的其他消息 比如聊天 好友這一類,只需簡單地從隊列裡拿走消息,轉發給後端的聊天服務隊列即可,其他服務會把消息轉發出去。

對於第3種消息服務器端對客戶端消息的迴應。SET 只需要根據消息裡的用戶id,找到SET裡保留的用戶連接對象,發回去就可以了。

對於紅包產生服務,它的工作很簡單,只需要按照順序在輪流在每個SET的紅包產生對列裡放至紅包對象就可以了。這樣可以保證每個SET裡都是公平的,其次它的工作強度很低,可以保證業務穩定。

見代碼:

https://github.com/xiaojiaqi/10billionhongbaos

7. 實踐

實踐的過程分為3個階段

階段1

分別啟動服務器端和監控端,然後逐一啟動17臺客戶端,讓它們建立起100萬的鏈接。

在服務器端,利用ss 命令 統計出每個客戶端和服務器建立了多少連接。

命令如下:

Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print $8}” | sort | uniq –c’

結果如下:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段2

利用客戶端的http接口,將所有的客戶端QPS 調整到3萬,讓客戶端發出3W QPS強度的請求。

運行如下命令:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


觀察網絡監控和監控端反饋,發現QPS 達到預期數據,網絡監控截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


在服務器端啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包,總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


客戶端:esxi 5.0 虛擬機,配置為4核 5G內存。一共17臺,每臺和服務器建立6萬個連接。完成100萬客戶端模擬

5. 技術分析和實現

5.1) 單機實現100萬用戶連接

這一點來說相對簡單,筆者在幾年前就早完成了單機百萬用戶的開發以及操作。現代的服務器都可以支持百萬用戶。相關內容可以查看:

github代碼以及相關文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系統配置以及優化文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3萬QPS

這個問題需要分2個部分來看客戶端方面和服務器方面。

  • 客戶端QPS


因為有100萬連接連在服務器上,QPS為3萬。這就意味著每個連接每33秒,就需要向服務器發一個搖紅包的請求。

因為單IP可以建立的連接數為6萬左右, 有17臺服務器同時模擬客戶端行為。我們要做的就保證在每一秒都有這麼多的請求發往服務器即可。

其中技術要點就是客戶端協同。但是各個客戶端的啟動時間,建立連接的時間都不一致,還存在網絡斷開重連這樣的情況,各個客戶端如何判斷何時自己需要發送請求,各自該發送多少請求呢?

我是這樣解決的:利用NTP服務,同步所有的服務器時間,客戶端利用時間戳來判斷自己的此時需要發送多少請求。

算法很容易實現:假設有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數為100萬,它計算 100萬/5萬=20,所有的用戶應該分為20組

如果 time() % 20 == 用戶id % 20,那麼這個id的用戶就該在這一秒發出請求,如此實現了多客戶端協同工作。每個客戶端只需要知道 總用戶數和QPS 就能自行準確發出請求了。

(擴展思考:如果QPS是3萬 這樣不能被整除的數目,該如何辦?如何保證每臺客戶端發出的請求數目儘量的均衡呢?)

  • 服務器QPS

服務器端的QPS相對簡單,它只需要處理客戶端的請求即可。但是為了客觀瞭解處理情況,我們還需要做2件事情。

  • 第一:需要記錄每秒處理的請求數目,這需要在代碼裡埋入計數器。
  • 第二:我們需要監控網絡,因為網絡的吞吐情況,可以客觀的反映出QPS的真實數據。
  • 為此,我利用python腳本 結合ethtool 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網絡的數據包通過情況如何。它可以客觀的顯示出我們的網絡有如此多的數據傳輸在發生。


工具截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


5.3) 搖紅包業務

搖紅包的業務非常簡單,首先服務器按照一定的速度生產紅包。紅包沒有被取走的話,就堆積在裡面。

服務器接收一個客戶端的請求,如果服務器裡現在有紅包就會告訴客戶端有,否則就提示沒有紅包。

因為單機每秒有3萬的請求,所以大部分的請求會失敗。只需要處理好鎖的問題即可。

我為了減少競爭,將所有的用戶分在了不同的桶裡。這樣可以減少對鎖的競爭。如果以後還有更高的性能要求,還可以使用 高性能隊列——Disruptor來進一步提高性能。

注意,在我的測試環境裡是缺少支付這個核心服務的,所以實現的難度是大大的減輕了。

另外提供一組數字:2016年淘寶的雙11的交易峰值僅僅為12萬/秒,微信紅包分發速度是5萬/秒,要做到這點是非常困難的。(http://mt.sohu.com/20161111/n472951708.shtml)

5.4) 發紅包業務

發紅包的業務很簡單,系統隨機產生一些紅包,並且隨機選擇一些用戶,系統向這些用戶提示有紅包。

這些用戶只需要發出拆紅包的請求,系統就可以隨機從紅包中拆分出部分金額,分給用戶,完成這個業務。同樣這裡也沒有支付這個核心服務。

5.5)監控

最後 我們需要一套監控系統來了解系統的狀況,我借用了我另一個項目(https://github.com/xiaojiaqi/fakewechat)

裡的部分代碼完成了這個監控模塊,利用這個監控,服務器和客戶端會把當前的計數器內容發往監控,監控需要把各個客戶端的數據做一個整合和展示。

同時還會把日誌記錄下來,給以後的分析提供原始數據。線上系統更多使用opentsdb這樣的時序數據庫,這裡資源有限,所以用了一個原始的方案。

監控顯示日誌大概這樣:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


6. 代碼實現及分析

在代碼方面,使用到的技巧實在不多,主要是設計思想和golang本身的一些問題需要考慮。

首先golang的goroutine 的數目控制,因為至少有100萬以上的連接,所以按照普通的設計方案,至少需要200萬或者300萬的goroutine在工作。這會造成系統本身的負擔很重。

其次就是100萬個連接的管理,無論是連接還是業務都會造成一些心智的負擔。

我的設計是這樣的:

首先將100萬連接分成多個不同的SET,每個SET是一個獨立,平行的對象。每個SET 只管理幾千個連接,如果單個SET 工作正常,我只需要添加SET就能提高系統處理能力。

其次謹慎的設計了每個SET裡數據結構的大小,保證每個SET的壓力不會太大,不會出現消息的堆積。

再次減少了gcroutine的數目,每個連接只使用一個goroutine,發送消息在一個SET裡只有一個gcroutine負責,這樣節省了100萬個goroutine。

這樣整個系統只需要保留 100萬零幾百個gcroutine就能完成業務。大量的節省了cpu 和內存

系統的工作流程大概是:每個客戶端連接成功後,系統會分配一個goroutine讀取客戶端的消息當消息讀取完成,將它轉化為消息對象放至在SET的接收消息隊列,然後返回獲取下一個消息。

在SET內部,有一個工作goroutine,它只做非常簡單而高效的事情,它做的事情如下,檢查SET的接受消息,它會收到3類消息

  1. 客戶端的搖紅包請求消息
  2. 客戶端的其他消息 比如聊天 好友這一類
  3. 服務器端對客戶端消息的迴應

對於第1種消息客戶端的搖紅包請求消息 是這樣處理的,從客戶端拿到搖紅包請求消息,試圖從SET的紅包隊列裡 獲取一個紅包

如果拿到了就把紅包信息 返回給客戶端,否則構造一個沒有搖到的消息,返回給對應的客戶端。

對於第2種消息客戶端的其他消息 比如聊天 好友這一類,只需簡單地從隊列裡拿走消息,轉發給後端的聊天服務隊列即可,其他服務會把消息轉發出去。

對於第3種消息服務器端對客戶端消息的迴應。SET 只需要根據消息裡的用戶id,找到SET裡保留的用戶連接對象,發回去就可以了。

對於紅包產生服務,它的工作很簡單,只需要按照順序在輪流在每個SET的紅包產生對列裡放至紅包對象就可以了。這樣可以保證每個SET裡都是公平的,其次它的工作強度很低,可以保證業務穩定。

見代碼:

https://github.com/xiaojiaqi/10billionhongbaos

7. 實踐

實踐的過程分為3個階段

階段1

分別啟動服務器端和監控端,然後逐一啟動17臺客戶端,讓它們建立起100萬的鏈接。

在服務器端,利用ss 命令 統計出每個客戶端和服務器建立了多少連接。

命令如下:

Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print $8}” | sort | uniq –c’

結果如下:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段2

利用客戶端的http接口,將所有的客戶端QPS 調整到3萬,讓客戶端發出3W QPS強度的請求。

運行如下命令:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


觀察網絡監控和監控端反饋,發現QPS 達到預期數據,網絡監控截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


在服務器端啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包,總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段3

利用客戶端的http接口,將所有的客戶端QPS 調整到6萬,讓客戶端發出6W QPS強度的請求。

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


客戶端:esxi 5.0 虛擬機,配置為4核 5G內存。一共17臺,每臺和服務器建立6萬個連接。完成100萬客戶端模擬

5. 技術分析和實現

5.1) 單機實現100萬用戶連接

這一點來說相對簡單,筆者在幾年前就早完成了單機百萬用戶的開發以及操作。現代的服務器都可以支持百萬用戶。相關內容可以查看:

github代碼以及相關文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系統配置以及優化文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3萬QPS

這個問題需要分2個部分來看客戶端方面和服務器方面。

  • 客戶端QPS


因為有100萬連接連在服務器上,QPS為3萬。這就意味著每個連接每33秒,就需要向服務器發一個搖紅包的請求。

因為單IP可以建立的連接數為6萬左右, 有17臺服務器同時模擬客戶端行為。我們要做的就保證在每一秒都有這麼多的請求發往服務器即可。

其中技術要點就是客戶端協同。但是各個客戶端的啟動時間,建立連接的時間都不一致,還存在網絡斷開重連這樣的情況,各個客戶端如何判斷何時自己需要發送請求,各自該發送多少請求呢?

我是這樣解決的:利用NTP服務,同步所有的服務器時間,客戶端利用時間戳來判斷自己的此時需要發送多少請求。

算法很容易實現:假設有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數為100萬,它計算 100萬/5萬=20,所有的用戶應該分為20組

如果 time() % 20 == 用戶id % 20,那麼這個id的用戶就該在這一秒發出請求,如此實現了多客戶端協同工作。每個客戶端只需要知道 總用戶數和QPS 就能自行準確發出請求了。

(擴展思考:如果QPS是3萬 這樣不能被整除的數目,該如何辦?如何保證每臺客戶端發出的請求數目儘量的均衡呢?)

  • 服務器QPS

服務器端的QPS相對簡單,它只需要處理客戶端的請求即可。但是為了客觀瞭解處理情況,我們還需要做2件事情。

  • 第一:需要記錄每秒處理的請求數目,這需要在代碼裡埋入計數器。
  • 第二:我們需要監控網絡,因為網絡的吞吐情況,可以客觀的反映出QPS的真實數據。
  • 為此,我利用python腳本 結合ethtool 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網絡的數據包通過情況如何。它可以客觀的顯示出我們的網絡有如此多的數據傳輸在發生。


工具截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


5.3) 搖紅包業務

搖紅包的業務非常簡單,首先服務器按照一定的速度生產紅包。紅包沒有被取走的話,就堆積在裡面。

服務器接收一個客戶端的請求,如果服務器裡現在有紅包就會告訴客戶端有,否則就提示沒有紅包。

因為單機每秒有3萬的請求,所以大部分的請求會失敗。只需要處理好鎖的問題即可。

我為了減少競爭,將所有的用戶分在了不同的桶裡。這樣可以減少對鎖的競爭。如果以後還有更高的性能要求,還可以使用 高性能隊列——Disruptor來進一步提高性能。

注意,在我的測試環境裡是缺少支付這個核心服務的,所以實現的難度是大大的減輕了。

另外提供一組數字:2016年淘寶的雙11的交易峰值僅僅為12萬/秒,微信紅包分發速度是5萬/秒,要做到這點是非常困難的。(http://mt.sohu.com/20161111/n472951708.shtml)

5.4) 發紅包業務

發紅包的業務很簡單,系統隨機產生一些紅包,並且隨機選擇一些用戶,系統向這些用戶提示有紅包。

這些用戶只需要發出拆紅包的請求,系統就可以隨機從紅包中拆分出部分金額,分給用戶,完成這個業務。同樣這裡也沒有支付這個核心服務。

5.5)監控

最後 我們需要一套監控系統來了解系統的狀況,我借用了我另一個項目(https://github.com/xiaojiaqi/fakewechat)

裡的部分代碼完成了這個監控模塊,利用這個監控,服務器和客戶端會把當前的計數器內容發往監控,監控需要把各個客戶端的數據做一個整合和展示。

同時還會把日誌記錄下來,給以後的分析提供原始數據。線上系統更多使用opentsdb這樣的時序數據庫,這裡資源有限,所以用了一個原始的方案。

監控顯示日誌大概這樣:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


6. 代碼實現及分析

在代碼方面,使用到的技巧實在不多,主要是設計思想和golang本身的一些問題需要考慮。

首先golang的goroutine 的數目控制,因為至少有100萬以上的連接,所以按照普通的設計方案,至少需要200萬或者300萬的goroutine在工作。這會造成系統本身的負擔很重。

其次就是100萬個連接的管理,無論是連接還是業務都會造成一些心智的負擔。

我的設計是這樣的:

首先將100萬連接分成多個不同的SET,每個SET是一個獨立,平行的對象。每個SET 只管理幾千個連接,如果單個SET 工作正常,我只需要添加SET就能提高系統處理能力。

其次謹慎的設計了每個SET裡數據結構的大小,保證每個SET的壓力不會太大,不會出現消息的堆積。

再次減少了gcroutine的數目,每個連接只使用一個goroutine,發送消息在一個SET裡只有一個gcroutine負責,這樣節省了100萬個goroutine。

這樣整個系統只需要保留 100萬零幾百個gcroutine就能完成業務。大量的節省了cpu 和內存

系統的工作流程大概是:每個客戶端連接成功後,系統會分配一個goroutine讀取客戶端的消息當消息讀取完成,將它轉化為消息對象放至在SET的接收消息隊列,然後返回獲取下一個消息。

在SET內部,有一個工作goroutine,它只做非常簡單而高效的事情,它做的事情如下,檢查SET的接受消息,它會收到3類消息

  1. 客戶端的搖紅包請求消息
  2. 客戶端的其他消息 比如聊天 好友這一類
  3. 服務器端對客戶端消息的迴應

對於第1種消息客戶端的搖紅包請求消息 是這樣處理的,從客戶端拿到搖紅包請求消息,試圖從SET的紅包隊列裡 獲取一個紅包

如果拿到了就把紅包信息 返回給客戶端,否則構造一個沒有搖到的消息,返回給對應的客戶端。

對於第2種消息客戶端的其他消息 比如聊天 好友這一類,只需簡單地從隊列裡拿走消息,轉發給後端的聊天服務隊列即可,其他服務會把消息轉發出去。

對於第3種消息服務器端對客戶端消息的迴應。SET 只需要根據消息裡的用戶id,找到SET裡保留的用戶連接對象,發回去就可以了。

對於紅包產生服務,它的工作很簡單,只需要按照順序在輪流在每個SET的紅包產生對列裡放至紅包對象就可以了。這樣可以保證每個SET裡都是公平的,其次它的工作強度很低,可以保證業務穩定。

見代碼:

https://github.com/xiaojiaqi/10billionhongbaos

7. 實踐

實踐的過程分為3個階段

階段1

分別啟動服務器端和監控端,然後逐一啟動17臺客戶端,讓它們建立起100萬的鏈接。

在服務器端,利用ss 命令 統計出每個客戶端和服務器建立了多少連接。

命令如下:

Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print $8}” | sort | uniq –c’

結果如下:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段2

利用客戶端的http接口,將所有的客戶端QPS 調整到3萬,讓客戶端發出3W QPS強度的請求。

運行如下命令:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


觀察網絡監控和監控端反饋,發現QPS 達到預期數據,網絡監控截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


在服務器端啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包,總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段3

利用客戶端的http接口,將所有的客戶端QPS 調整到6萬,讓客戶端發出6W QPS強度的請求。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


如法炮製,在服務器端,啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包。總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

最後,實踐完成。

8. 分析數據

在實踐過程中,服務器和客戶端都將自己內部的計數器記錄發往監控端,成為了日誌。

我們利用簡單python 腳本和gnuplt 繪圖工具,將實踐的過程可視化,由此來驗證運行過程。

第一張是客戶端的QPS發送數據:

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


客戶端:esxi 5.0 虛擬機,配置為4核 5G內存。一共17臺,每臺和服務器建立6萬個連接。完成100萬客戶端模擬

5. 技術分析和實現

5.1) 單機實現100萬用戶連接

這一點來說相對簡單,筆者在幾年前就早完成了單機百萬用戶的開發以及操作。現代的服務器都可以支持百萬用戶。相關內容可以查看:

github代碼以及相關文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系統配置以及優化文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3萬QPS

這個問題需要分2個部分來看客戶端方面和服務器方面。

  • 客戶端QPS


因為有100萬連接連在服務器上,QPS為3萬。這就意味著每個連接每33秒,就需要向服務器發一個搖紅包的請求。

因為單IP可以建立的連接數為6萬左右, 有17臺服務器同時模擬客戶端行為。我們要做的就保證在每一秒都有這麼多的請求發往服務器即可。

其中技術要點就是客戶端協同。但是各個客戶端的啟動時間,建立連接的時間都不一致,還存在網絡斷開重連這樣的情況,各個客戶端如何判斷何時自己需要發送請求,各自該發送多少請求呢?

我是這樣解決的:利用NTP服務,同步所有的服務器時間,客戶端利用時間戳來判斷自己的此時需要發送多少請求。

算法很容易實現:假設有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數為100萬,它計算 100萬/5萬=20,所有的用戶應該分為20組

如果 time() % 20 == 用戶id % 20,那麼這個id的用戶就該在這一秒發出請求,如此實現了多客戶端協同工作。每個客戶端只需要知道 總用戶數和QPS 就能自行準確發出請求了。

(擴展思考:如果QPS是3萬 這樣不能被整除的數目,該如何辦?如何保證每臺客戶端發出的請求數目儘量的均衡呢?)

  • 服務器QPS

服務器端的QPS相對簡單,它只需要處理客戶端的請求即可。但是為了客觀瞭解處理情況,我們還需要做2件事情。

  • 第一:需要記錄每秒處理的請求數目,這需要在代碼裡埋入計數器。
  • 第二:我們需要監控網絡,因為網絡的吞吐情況,可以客觀的反映出QPS的真實數據。
  • 為此,我利用python腳本 結合ethtool 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網絡的數據包通過情況如何。它可以客觀的顯示出我們的網絡有如此多的數據傳輸在發生。


工具截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


5.3) 搖紅包業務

搖紅包的業務非常簡單,首先服務器按照一定的速度生產紅包。紅包沒有被取走的話,就堆積在裡面。

服務器接收一個客戶端的請求,如果服務器裡現在有紅包就會告訴客戶端有,否則就提示沒有紅包。

因為單機每秒有3萬的請求,所以大部分的請求會失敗。只需要處理好鎖的問題即可。

我為了減少競爭,將所有的用戶分在了不同的桶裡。這樣可以減少對鎖的競爭。如果以後還有更高的性能要求,還可以使用 高性能隊列——Disruptor來進一步提高性能。

注意,在我的測試環境裡是缺少支付這個核心服務的,所以實現的難度是大大的減輕了。

另外提供一組數字:2016年淘寶的雙11的交易峰值僅僅為12萬/秒,微信紅包分發速度是5萬/秒,要做到這點是非常困難的。(http://mt.sohu.com/20161111/n472951708.shtml)

5.4) 發紅包業務

發紅包的業務很簡單,系統隨機產生一些紅包,並且隨機選擇一些用戶,系統向這些用戶提示有紅包。

這些用戶只需要發出拆紅包的請求,系統就可以隨機從紅包中拆分出部分金額,分給用戶,完成這個業務。同樣這裡也沒有支付這個核心服務。

5.5)監控

最後 我們需要一套監控系統來了解系統的狀況,我借用了我另一個項目(https://github.com/xiaojiaqi/fakewechat)

裡的部分代碼完成了這個監控模塊,利用這個監控,服務器和客戶端會把當前的計數器內容發往監控,監控需要把各個客戶端的數據做一個整合和展示。

同時還會把日誌記錄下來,給以後的分析提供原始數據。線上系統更多使用opentsdb這樣的時序數據庫,這裡資源有限,所以用了一個原始的方案。

監控顯示日誌大概這樣:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


6. 代碼實現及分析

在代碼方面,使用到的技巧實在不多,主要是設計思想和golang本身的一些問題需要考慮。

首先golang的goroutine 的數目控制,因為至少有100萬以上的連接,所以按照普通的設計方案,至少需要200萬或者300萬的goroutine在工作。這會造成系統本身的負擔很重。

其次就是100萬個連接的管理,無論是連接還是業務都會造成一些心智的負擔。

我的設計是這樣的:

首先將100萬連接分成多個不同的SET,每個SET是一個獨立,平行的對象。每個SET 只管理幾千個連接,如果單個SET 工作正常,我只需要添加SET就能提高系統處理能力。

其次謹慎的設計了每個SET裡數據結構的大小,保證每個SET的壓力不會太大,不會出現消息的堆積。

再次減少了gcroutine的數目,每個連接只使用一個goroutine,發送消息在一個SET裡只有一個gcroutine負責,這樣節省了100萬個goroutine。

這樣整個系統只需要保留 100萬零幾百個gcroutine就能完成業務。大量的節省了cpu 和內存

系統的工作流程大概是:每個客戶端連接成功後,系統會分配一個goroutine讀取客戶端的消息當消息讀取完成,將它轉化為消息對象放至在SET的接收消息隊列,然後返回獲取下一個消息。

在SET內部,有一個工作goroutine,它只做非常簡單而高效的事情,它做的事情如下,檢查SET的接受消息,它會收到3類消息

  1. 客戶端的搖紅包請求消息
  2. 客戶端的其他消息 比如聊天 好友這一類
  3. 服務器端對客戶端消息的迴應

對於第1種消息客戶端的搖紅包請求消息 是這樣處理的,從客戶端拿到搖紅包請求消息,試圖從SET的紅包隊列裡 獲取一個紅包

如果拿到了就把紅包信息 返回給客戶端,否則構造一個沒有搖到的消息,返回給對應的客戶端。

對於第2種消息客戶端的其他消息 比如聊天 好友這一類,只需簡單地從隊列裡拿走消息,轉發給後端的聊天服務隊列即可,其他服務會把消息轉發出去。

對於第3種消息服務器端對客戶端消息的迴應。SET 只需要根據消息裡的用戶id,找到SET裡保留的用戶連接對象,發回去就可以了。

對於紅包產生服務,它的工作很簡單,只需要按照順序在輪流在每個SET的紅包產生對列裡放至紅包對象就可以了。這樣可以保證每個SET裡都是公平的,其次它的工作強度很低,可以保證業務穩定。

見代碼:

https://github.com/xiaojiaqi/10billionhongbaos

7. 實踐

實踐的過程分為3個階段

階段1

分別啟動服務器端和監控端,然後逐一啟動17臺客戶端,讓它們建立起100萬的鏈接。

在服務器端,利用ss 命令 統計出每個客戶端和服務器建立了多少連接。

命令如下:

Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print $8}” | sort | uniq –c’

結果如下:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段2

利用客戶端的http接口,將所有的客戶端QPS 調整到3萬,讓客戶端發出3W QPS強度的請求。

運行如下命令:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


觀察網絡監控和監控端反饋,發現QPS 達到預期數據,網絡監控截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


在服務器端啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包,總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段3

利用客戶端的http接口,將所有的客戶端QPS 調整到6萬,讓客戶端發出6W QPS強度的請求。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


如法炮製,在服務器端,啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包。總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

最後,實踐完成。

8. 分析數據

在實踐過程中,服務器和客戶端都將自己內部的計數器記錄發往監控端,成為了日誌。

我們利用簡單python 腳本和gnuplt 繪圖工具,將實踐的過程可視化,由此來驗證運行過程。

第一張是客戶端的QPS發送數據:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


這張圖的橫座標是時間,單位是秒,縱座標是QPS,表示這時刻所有客戶端發送的請求的QPS。

圖的第一區間,幾個小的峰值,是100萬客戶端建立連接的, 圖的第二區間是3萬QPS 區間,我們可以看到數據 比較穩定的保持在3萬這個區間。

最後是6萬QPS區間。但是從整張圖可以看到QPS不是完美地保持在我們希望的直線上。這主要是以下幾個原因造成的

  1. 當非常多goroutine 同時運行的時候,依靠sleep 定時並不準確,發生了偏移。我覺得這是golang本身調度導致的。當然如果cpu比較強勁,這個現象會消失。
  2. 因為網絡的影響,客戶端在發起連接時,可能發生延遲,導致在前1秒沒有完成連接。
  3. 服務器負載較大時,1000M網絡已經出現了丟包現象,可以通過ifconfig 命令觀察到這個現象,所以會有QPS的波動。

第二張是 服務器處理的QPS圖:

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


客戶端:esxi 5.0 虛擬機,配置為4核 5G內存。一共17臺,每臺和服務器建立6萬個連接。完成100萬客戶端模擬

5. 技術分析和實現

5.1) 單機實現100萬用戶連接

這一點來說相對簡單,筆者在幾年前就早完成了單機百萬用戶的開發以及操作。現代的服務器都可以支持百萬用戶。相關內容可以查看:

github代碼以及相關文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系統配置以及優化文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3萬QPS

這個問題需要分2個部分來看客戶端方面和服務器方面。

  • 客戶端QPS


因為有100萬連接連在服務器上,QPS為3萬。這就意味著每個連接每33秒,就需要向服務器發一個搖紅包的請求。

因為單IP可以建立的連接數為6萬左右, 有17臺服務器同時模擬客戶端行為。我們要做的就保證在每一秒都有這麼多的請求發往服務器即可。

其中技術要點就是客戶端協同。但是各個客戶端的啟動時間,建立連接的時間都不一致,還存在網絡斷開重連這樣的情況,各個客戶端如何判斷何時自己需要發送請求,各自該發送多少請求呢?

我是這樣解決的:利用NTP服務,同步所有的服務器時間,客戶端利用時間戳來判斷自己的此時需要發送多少請求。

算法很容易實現:假設有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數為100萬,它計算 100萬/5萬=20,所有的用戶應該分為20組

如果 time() % 20 == 用戶id % 20,那麼這個id的用戶就該在這一秒發出請求,如此實現了多客戶端協同工作。每個客戶端只需要知道 總用戶數和QPS 就能自行準確發出請求了。

(擴展思考:如果QPS是3萬 這樣不能被整除的數目,該如何辦?如何保證每臺客戶端發出的請求數目儘量的均衡呢?)

  • 服務器QPS

服務器端的QPS相對簡單,它只需要處理客戶端的請求即可。但是為了客觀瞭解處理情況,我們還需要做2件事情。

  • 第一:需要記錄每秒處理的請求數目,這需要在代碼裡埋入計數器。
  • 第二:我們需要監控網絡,因為網絡的吞吐情況,可以客觀的反映出QPS的真實數據。
  • 為此,我利用python腳本 結合ethtool 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網絡的數據包通過情況如何。它可以客觀的顯示出我們的網絡有如此多的數據傳輸在發生。


工具截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


5.3) 搖紅包業務

搖紅包的業務非常簡單,首先服務器按照一定的速度生產紅包。紅包沒有被取走的話,就堆積在裡面。

服務器接收一個客戶端的請求,如果服務器裡現在有紅包就會告訴客戶端有,否則就提示沒有紅包。

因為單機每秒有3萬的請求,所以大部分的請求會失敗。只需要處理好鎖的問題即可。

我為了減少競爭,將所有的用戶分在了不同的桶裡。這樣可以減少對鎖的競爭。如果以後還有更高的性能要求,還可以使用 高性能隊列——Disruptor來進一步提高性能。

注意,在我的測試環境裡是缺少支付這個核心服務的,所以實現的難度是大大的減輕了。

另外提供一組數字:2016年淘寶的雙11的交易峰值僅僅為12萬/秒,微信紅包分發速度是5萬/秒,要做到這點是非常困難的。(http://mt.sohu.com/20161111/n472951708.shtml)

5.4) 發紅包業務

發紅包的業務很簡單,系統隨機產生一些紅包,並且隨機選擇一些用戶,系統向這些用戶提示有紅包。

這些用戶只需要發出拆紅包的請求,系統就可以隨機從紅包中拆分出部分金額,分給用戶,完成這個業務。同樣這裡也沒有支付這個核心服務。

5.5)監控

最後 我們需要一套監控系統來了解系統的狀況,我借用了我另一個項目(https://github.com/xiaojiaqi/fakewechat)

裡的部分代碼完成了這個監控模塊,利用這個監控,服務器和客戶端會把當前的計數器內容發往監控,監控需要把各個客戶端的數據做一個整合和展示。

同時還會把日誌記錄下來,給以後的分析提供原始數據。線上系統更多使用opentsdb這樣的時序數據庫,這裡資源有限,所以用了一個原始的方案。

監控顯示日誌大概這樣:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


6. 代碼實現及分析

在代碼方面,使用到的技巧實在不多,主要是設計思想和golang本身的一些問題需要考慮。

首先golang的goroutine 的數目控制,因為至少有100萬以上的連接,所以按照普通的設計方案,至少需要200萬或者300萬的goroutine在工作。這會造成系統本身的負擔很重。

其次就是100萬個連接的管理,無論是連接還是業務都會造成一些心智的負擔。

我的設計是這樣的:

首先將100萬連接分成多個不同的SET,每個SET是一個獨立,平行的對象。每個SET 只管理幾千個連接,如果單個SET 工作正常,我只需要添加SET就能提高系統處理能力。

其次謹慎的設計了每個SET裡數據結構的大小,保證每個SET的壓力不會太大,不會出現消息的堆積。

再次減少了gcroutine的數目,每個連接只使用一個goroutine,發送消息在一個SET裡只有一個gcroutine負責,這樣節省了100萬個goroutine。

這樣整個系統只需要保留 100萬零幾百個gcroutine就能完成業務。大量的節省了cpu 和內存

系統的工作流程大概是:每個客戶端連接成功後,系統會分配一個goroutine讀取客戶端的消息當消息讀取完成,將它轉化為消息對象放至在SET的接收消息隊列,然後返回獲取下一個消息。

在SET內部,有一個工作goroutine,它只做非常簡單而高效的事情,它做的事情如下,檢查SET的接受消息,它會收到3類消息

  1. 客戶端的搖紅包請求消息
  2. 客戶端的其他消息 比如聊天 好友這一類
  3. 服務器端對客戶端消息的迴應

對於第1種消息客戶端的搖紅包請求消息 是這樣處理的,從客戶端拿到搖紅包請求消息,試圖從SET的紅包隊列裡 獲取一個紅包

如果拿到了就把紅包信息 返回給客戶端,否則構造一個沒有搖到的消息,返回給對應的客戶端。

對於第2種消息客戶端的其他消息 比如聊天 好友這一類,只需簡單地從隊列裡拿走消息,轉發給後端的聊天服務隊列即可,其他服務會把消息轉發出去。

對於第3種消息服務器端對客戶端消息的迴應。SET 只需要根據消息裡的用戶id,找到SET裡保留的用戶連接對象,發回去就可以了。

對於紅包產生服務,它的工作很簡單,只需要按照順序在輪流在每個SET的紅包產生對列裡放至紅包對象就可以了。這樣可以保證每個SET裡都是公平的,其次它的工作強度很低,可以保證業務穩定。

見代碼:

https://github.com/xiaojiaqi/10billionhongbaos

7. 實踐

實踐的過程分為3個階段

階段1

分別啟動服務器端和監控端,然後逐一啟動17臺客戶端,讓它們建立起100萬的鏈接。

在服務器端,利用ss 命令 統計出每個客戶端和服務器建立了多少連接。

命令如下:

Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print $8}” | sort | uniq –c’

結果如下:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段2

利用客戶端的http接口,將所有的客戶端QPS 調整到3萬,讓客戶端發出3W QPS強度的請求。

運行如下命令:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


觀察網絡監控和監控端反饋,發現QPS 達到預期數據,網絡監控截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


在服務器端啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包,總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段3

利用客戶端的http接口,將所有的客戶端QPS 調整到6萬,讓客戶端發出6W QPS強度的請求。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


如法炮製,在服務器端,啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包。總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

最後,實踐完成。

8. 分析數據

在實踐過程中,服務器和客戶端都將自己內部的計數器記錄發往監控端,成為了日誌。

我們利用簡單python 腳本和gnuplt 繪圖工具,將實踐的過程可視化,由此來驗證運行過程。

第一張是客戶端的QPS發送數據:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


這張圖的橫座標是時間,單位是秒,縱座標是QPS,表示這時刻所有客戶端發送的請求的QPS。

圖的第一區間,幾個小的峰值,是100萬客戶端建立連接的, 圖的第二區間是3萬QPS 區間,我們可以看到數據 比較穩定的保持在3萬這個區間。

最後是6萬QPS區間。但是從整張圖可以看到QPS不是完美地保持在我們希望的直線上。這主要是以下幾個原因造成的

  1. 當非常多goroutine 同時運行的時候,依靠sleep 定時並不準確,發生了偏移。我覺得這是golang本身調度導致的。當然如果cpu比較強勁,這個現象會消失。
  2. 因為網絡的影響,客戶端在發起連接時,可能發生延遲,導致在前1秒沒有完成連接。
  3. 服務器負載較大時,1000M網絡已經出現了丟包現象,可以通過ifconfig 命令觀察到這個現象,所以會有QPS的波動。

第二張是 服務器處理的QPS圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


和客戶端的向對應的,服務器也存在3個區間,和客戶端的情況很接近。

但是我們看到了在大概22:57分,系統的處理能力就有一個明顯的下降,隨後又提高的尖狀。這說明代碼還需要優化。

整體觀察在3萬QPS區間,服務器的QPS比較穩定,在6萬QSP時候,服務器的處理就不穩定了。我相信這和我的代碼有關,如果繼續優化的話,還應該能有更好的效果。

將2張圖合併起來 :

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


客戶端:esxi 5.0 虛擬機,配置為4核 5G內存。一共17臺,每臺和服務器建立6萬個連接。完成100萬客戶端模擬

5. 技術分析和實現

5.1) 單機實現100萬用戶連接

這一點來說相對簡單,筆者在幾年前就早完成了單機百萬用戶的開發以及操作。現代的服務器都可以支持百萬用戶。相關內容可以查看:

github代碼以及相關文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系統配置以及優化文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3萬QPS

這個問題需要分2個部分來看客戶端方面和服務器方面。

  • 客戶端QPS


因為有100萬連接連在服務器上,QPS為3萬。這就意味著每個連接每33秒,就需要向服務器發一個搖紅包的請求。

因為單IP可以建立的連接數為6萬左右, 有17臺服務器同時模擬客戶端行為。我們要做的就保證在每一秒都有這麼多的請求發往服務器即可。

其中技術要點就是客戶端協同。但是各個客戶端的啟動時間,建立連接的時間都不一致,還存在網絡斷開重連這樣的情況,各個客戶端如何判斷何時自己需要發送請求,各自該發送多少請求呢?

我是這樣解決的:利用NTP服務,同步所有的服務器時間,客戶端利用時間戳來判斷自己的此時需要發送多少請求。

算法很容易實現:假設有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數為100萬,它計算 100萬/5萬=20,所有的用戶應該分為20組

如果 time() % 20 == 用戶id % 20,那麼這個id的用戶就該在這一秒發出請求,如此實現了多客戶端協同工作。每個客戶端只需要知道 總用戶數和QPS 就能自行準確發出請求了。

(擴展思考:如果QPS是3萬 這樣不能被整除的數目,該如何辦?如何保證每臺客戶端發出的請求數目儘量的均衡呢?)

  • 服務器QPS

服務器端的QPS相對簡單,它只需要處理客戶端的請求即可。但是為了客觀瞭解處理情況,我們還需要做2件事情。

  • 第一:需要記錄每秒處理的請求數目,這需要在代碼裡埋入計數器。
  • 第二:我們需要監控網絡,因為網絡的吞吐情況,可以客觀的反映出QPS的真實數據。
  • 為此,我利用python腳本 結合ethtool 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網絡的數據包通過情況如何。它可以客觀的顯示出我們的網絡有如此多的數據傳輸在發生。


工具截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


5.3) 搖紅包業務

搖紅包的業務非常簡單,首先服務器按照一定的速度生產紅包。紅包沒有被取走的話,就堆積在裡面。

服務器接收一個客戶端的請求,如果服務器裡現在有紅包就會告訴客戶端有,否則就提示沒有紅包。

因為單機每秒有3萬的請求,所以大部分的請求會失敗。只需要處理好鎖的問題即可。

我為了減少競爭,將所有的用戶分在了不同的桶裡。這樣可以減少對鎖的競爭。如果以後還有更高的性能要求,還可以使用 高性能隊列——Disruptor來進一步提高性能。

注意,在我的測試環境裡是缺少支付這個核心服務的,所以實現的難度是大大的減輕了。

另外提供一組數字:2016年淘寶的雙11的交易峰值僅僅為12萬/秒,微信紅包分發速度是5萬/秒,要做到這點是非常困難的。(http://mt.sohu.com/20161111/n472951708.shtml)

5.4) 發紅包業務

發紅包的業務很簡單,系統隨機產生一些紅包,並且隨機選擇一些用戶,系統向這些用戶提示有紅包。

這些用戶只需要發出拆紅包的請求,系統就可以隨機從紅包中拆分出部分金額,分給用戶,完成這個業務。同樣這裡也沒有支付這個核心服務。

5.5)監控

最後 我們需要一套監控系統來了解系統的狀況,我借用了我另一個項目(https://github.com/xiaojiaqi/fakewechat)

裡的部分代碼完成了這個監控模塊,利用這個監控,服務器和客戶端會把當前的計數器內容發往監控,監控需要把各個客戶端的數據做一個整合和展示。

同時還會把日誌記錄下來,給以後的分析提供原始數據。線上系統更多使用opentsdb這樣的時序數據庫,這裡資源有限,所以用了一個原始的方案。

監控顯示日誌大概這樣:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


6. 代碼實現及分析

在代碼方面,使用到的技巧實在不多,主要是設計思想和golang本身的一些問題需要考慮。

首先golang的goroutine 的數目控制,因為至少有100萬以上的連接,所以按照普通的設計方案,至少需要200萬或者300萬的goroutine在工作。這會造成系統本身的負擔很重。

其次就是100萬個連接的管理,無論是連接還是業務都會造成一些心智的負擔。

我的設計是這樣的:

首先將100萬連接分成多個不同的SET,每個SET是一個獨立,平行的對象。每個SET 只管理幾千個連接,如果單個SET 工作正常,我只需要添加SET就能提高系統處理能力。

其次謹慎的設計了每個SET裡數據結構的大小,保證每個SET的壓力不會太大,不會出現消息的堆積。

再次減少了gcroutine的數目,每個連接只使用一個goroutine,發送消息在一個SET裡只有一個gcroutine負責,這樣節省了100萬個goroutine。

這樣整個系統只需要保留 100萬零幾百個gcroutine就能完成業務。大量的節省了cpu 和內存

系統的工作流程大概是:每個客戶端連接成功後,系統會分配一個goroutine讀取客戶端的消息當消息讀取完成,將它轉化為消息對象放至在SET的接收消息隊列,然後返回獲取下一個消息。

在SET內部,有一個工作goroutine,它只做非常簡單而高效的事情,它做的事情如下,檢查SET的接受消息,它會收到3類消息

  1. 客戶端的搖紅包請求消息
  2. 客戶端的其他消息 比如聊天 好友這一類
  3. 服務器端對客戶端消息的迴應

對於第1種消息客戶端的搖紅包請求消息 是這樣處理的,從客戶端拿到搖紅包請求消息,試圖從SET的紅包隊列裡 獲取一個紅包

如果拿到了就把紅包信息 返回給客戶端,否則構造一個沒有搖到的消息,返回給對應的客戶端。

對於第2種消息客戶端的其他消息 比如聊天 好友這一類,只需簡單地從隊列裡拿走消息,轉發給後端的聊天服務隊列即可,其他服務會把消息轉發出去。

對於第3種消息服務器端對客戶端消息的迴應。SET 只需要根據消息裡的用戶id,找到SET裡保留的用戶連接對象,發回去就可以了。

對於紅包產生服務,它的工作很簡單,只需要按照順序在輪流在每個SET的紅包產生對列裡放至紅包對象就可以了。這樣可以保證每個SET裡都是公平的,其次它的工作強度很低,可以保證業務穩定。

見代碼:

https://github.com/xiaojiaqi/10billionhongbaos

7. 實踐

實踐的過程分為3個階段

階段1

分別啟動服務器端和監控端,然後逐一啟動17臺客戶端,讓它們建立起100萬的鏈接。

在服務器端,利用ss 命令 統計出每個客戶端和服務器建立了多少連接。

命令如下:

Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print $8}” | sort | uniq –c’

結果如下:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段2

利用客戶端的http接口,將所有的客戶端QPS 調整到3萬,讓客戶端發出3W QPS強度的請求。

運行如下命令:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


觀察網絡監控和監控端反饋,發現QPS 達到預期數據,網絡監控截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


在服務器端啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包,總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段3

利用客戶端的http接口,將所有的客戶端QPS 調整到6萬,讓客戶端發出6W QPS強度的請求。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


如法炮製,在服務器端,啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包。總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

最後,實踐完成。

8. 分析數據

在實踐過程中,服務器和客戶端都將自己內部的計數器記錄發往監控端,成為了日誌。

我們利用簡單python 腳本和gnuplt 繪圖工具,將實踐的過程可視化,由此來驗證運行過程。

第一張是客戶端的QPS發送數據:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


這張圖的橫座標是時間,單位是秒,縱座標是QPS,表示這時刻所有客戶端發送的請求的QPS。

圖的第一區間,幾個小的峰值,是100萬客戶端建立連接的, 圖的第二區間是3萬QPS 區間,我們可以看到數據 比較穩定的保持在3萬這個區間。

最後是6萬QPS區間。但是從整張圖可以看到QPS不是完美地保持在我們希望的直線上。這主要是以下幾個原因造成的

  1. 當非常多goroutine 同時運行的時候,依靠sleep 定時並不準確,發生了偏移。我覺得這是golang本身調度導致的。當然如果cpu比較強勁,這個現象會消失。
  2. 因為網絡的影響,客戶端在發起連接時,可能發生延遲,導致在前1秒沒有完成連接。
  3. 服務器負載較大時,1000M網絡已經出現了丟包現象,可以通過ifconfig 命令觀察到這個現象,所以會有QPS的波動。

第二張是 服務器處理的QPS圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


和客戶端的向對應的,服務器也存在3個區間,和客戶端的情況很接近。

但是我們看到了在大概22:57分,系統的處理能力就有一個明顯的下降,隨後又提高的尖狀。這說明代碼還需要優化。

整體觀察在3萬QPS區間,服務器的QPS比較穩定,在6萬QSP時候,服務器的處理就不穩定了。我相信這和我的代碼有關,如果繼續優化的話,還應該能有更好的效果。

將2張圖合併起來 :

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


基本是吻合的,這也證明系統是符合預期設計的。

這是紅包生成數量的狀態變化圖:

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


客戶端:esxi 5.0 虛擬機,配置為4核 5G內存。一共17臺,每臺和服務器建立6萬個連接。完成100萬客戶端模擬

5. 技術分析和實現

5.1) 單機實現100萬用戶連接

這一點來說相對簡單,筆者在幾年前就早完成了單機百萬用戶的開發以及操作。現代的服務器都可以支持百萬用戶。相關內容可以查看:

github代碼以及相關文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系統配置以及優化文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3萬QPS

這個問題需要分2個部分來看客戶端方面和服務器方面。

  • 客戶端QPS


因為有100萬連接連在服務器上,QPS為3萬。這就意味著每個連接每33秒,就需要向服務器發一個搖紅包的請求。

因為單IP可以建立的連接數為6萬左右, 有17臺服務器同時模擬客戶端行為。我們要做的就保證在每一秒都有這麼多的請求發往服務器即可。

其中技術要點就是客戶端協同。但是各個客戶端的啟動時間,建立連接的時間都不一致,還存在網絡斷開重連這樣的情況,各個客戶端如何判斷何時自己需要發送請求,各自該發送多少請求呢?

我是這樣解決的:利用NTP服務,同步所有的服務器時間,客戶端利用時間戳來判斷自己的此時需要發送多少請求。

算法很容易實現:假設有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數為100萬,它計算 100萬/5萬=20,所有的用戶應該分為20組

如果 time() % 20 == 用戶id % 20,那麼這個id的用戶就該在這一秒發出請求,如此實現了多客戶端協同工作。每個客戶端只需要知道 總用戶數和QPS 就能自行準確發出請求了。

(擴展思考:如果QPS是3萬 這樣不能被整除的數目,該如何辦?如何保證每臺客戶端發出的請求數目儘量的均衡呢?)

  • 服務器QPS

服務器端的QPS相對簡單,它只需要處理客戶端的請求即可。但是為了客觀瞭解處理情況,我們還需要做2件事情。

  • 第一:需要記錄每秒處理的請求數目,這需要在代碼裡埋入計數器。
  • 第二:我們需要監控網絡,因為網絡的吞吐情況,可以客觀的反映出QPS的真實數據。
  • 為此,我利用python腳本 結合ethtool 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網絡的數據包通過情況如何。它可以客觀的顯示出我們的網絡有如此多的數據傳輸在發生。


工具截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


5.3) 搖紅包業務

搖紅包的業務非常簡單,首先服務器按照一定的速度生產紅包。紅包沒有被取走的話,就堆積在裡面。

服務器接收一個客戶端的請求,如果服務器裡現在有紅包就會告訴客戶端有,否則就提示沒有紅包。

因為單機每秒有3萬的請求,所以大部分的請求會失敗。只需要處理好鎖的問題即可。

我為了減少競爭,將所有的用戶分在了不同的桶裡。這樣可以減少對鎖的競爭。如果以後還有更高的性能要求,還可以使用 高性能隊列——Disruptor來進一步提高性能。

注意,在我的測試環境裡是缺少支付這個核心服務的,所以實現的難度是大大的減輕了。

另外提供一組數字:2016年淘寶的雙11的交易峰值僅僅為12萬/秒,微信紅包分發速度是5萬/秒,要做到這點是非常困難的。(http://mt.sohu.com/20161111/n472951708.shtml)

5.4) 發紅包業務

發紅包的業務很簡單,系統隨機產生一些紅包,並且隨機選擇一些用戶,系統向這些用戶提示有紅包。

這些用戶只需要發出拆紅包的請求,系統就可以隨機從紅包中拆分出部分金額,分給用戶,完成這個業務。同樣這裡也沒有支付這個核心服務。

5.5)監控

最後 我們需要一套監控系統來了解系統的狀況,我借用了我另一個項目(https://github.com/xiaojiaqi/fakewechat)

裡的部分代碼完成了這個監控模塊,利用這個監控,服務器和客戶端會把當前的計數器內容發往監控,監控需要把各個客戶端的數據做一個整合和展示。

同時還會把日誌記錄下來,給以後的分析提供原始數據。線上系統更多使用opentsdb這樣的時序數據庫,這裡資源有限,所以用了一個原始的方案。

監控顯示日誌大概這樣:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


6. 代碼實現及分析

在代碼方面,使用到的技巧實在不多,主要是設計思想和golang本身的一些問題需要考慮。

首先golang的goroutine 的數目控制,因為至少有100萬以上的連接,所以按照普通的設計方案,至少需要200萬或者300萬的goroutine在工作。這會造成系統本身的負擔很重。

其次就是100萬個連接的管理,無論是連接還是業務都會造成一些心智的負擔。

我的設計是這樣的:

首先將100萬連接分成多個不同的SET,每個SET是一個獨立,平行的對象。每個SET 只管理幾千個連接,如果單個SET 工作正常,我只需要添加SET就能提高系統處理能力。

其次謹慎的設計了每個SET裡數據結構的大小,保證每個SET的壓力不會太大,不會出現消息的堆積。

再次減少了gcroutine的數目,每個連接只使用一個goroutine,發送消息在一個SET裡只有一個gcroutine負責,這樣節省了100萬個goroutine。

這樣整個系統只需要保留 100萬零幾百個gcroutine就能完成業務。大量的節省了cpu 和內存

系統的工作流程大概是:每個客戶端連接成功後,系統會分配一個goroutine讀取客戶端的消息當消息讀取完成,將它轉化為消息對象放至在SET的接收消息隊列,然後返回獲取下一個消息。

在SET內部,有一個工作goroutine,它只做非常簡單而高效的事情,它做的事情如下,檢查SET的接受消息,它會收到3類消息

  1. 客戶端的搖紅包請求消息
  2. 客戶端的其他消息 比如聊天 好友這一類
  3. 服務器端對客戶端消息的迴應

對於第1種消息客戶端的搖紅包請求消息 是這樣處理的,從客戶端拿到搖紅包請求消息,試圖從SET的紅包隊列裡 獲取一個紅包

如果拿到了就把紅包信息 返回給客戶端,否則構造一個沒有搖到的消息,返回給對應的客戶端。

對於第2種消息客戶端的其他消息 比如聊天 好友這一類,只需簡單地從隊列裡拿走消息,轉發給後端的聊天服務隊列即可,其他服務會把消息轉發出去。

對於第3種消息服務器端對客戶端消息的迴應。SET 只需要根據消息裡的用戶id,找到SET裡保留的用戶連接對象,發回去就可以了。

對於紅包產生服務,它的工作很簡單,只需要按照順序在輪流在每個SET的紅包產生對列裡放至紅包對象就可以了。這樣可以保證每個SET裡都是公平的,其次它的工作強度很低,可以保證業務穩定。

見代碼:

https://github.com/xiaojiaqi/10billionhongbaos

7. 實踐

實踐的過程分為3個階段

階段1

分別啟動服務器端和監控端,然後逐一啟動17臺客戶端,讓它們建立起100萬的鏈接。

在服務器端,利用ss 命令 統計出每個客戶端和服務器建立了多少連接。

命令如下:

Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print $8}” | sort | uniq –c’

結果如下:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段2

利用客戶端的http接口,將所有的客戶端QPS 調整到3萬,讓客戶端發出3W QPS強度的請求。

運行如下命令:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


觀察網絡監控和監控端反饋,發現QPS 達到預期數據,網絡監控截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


在服務器端啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包,總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段3

利用客戶端的http接口,將所有的客戶端QPS 調整到6萬,讓客戶端發出6W QPS強度的請求。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


如法炮製,在服務器端,啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包。總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

最後,實踐完成。

8. 分析數據

在實踐過程中,服務器和客戶端都將自己內部的計數器記錄發往監控端,成為了日誌。

我們利用簡單python 腳本和gnuplt 繪圖工具,將實踐的過程可視化,由此來驗證運行過程。

第一張是客戶端的QPS發送數據:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


這張圖的橫座標是時間,單位是秒,縱座標是QPS,表示這時刻所有客戶端發送的請求的QPS。

圖的第一區間,幾個小的峰值,是100萬客戶端建立連接的, 圖的第二區間是3萬QPS 區間,我們可以看到數據 比較穩定的保持在3萬這個區間。

最後是6萬QPS區間。但是從整張圖可以看到QPS不是完美地保持在我們希望的直線上。這主要是以下幾個原因造成的

  1. 當非常多goroutine 同時運行的時候,依靠sleep 定時並不準確,發生了偏移。我覺得這是golang本身調度導致的。當然如果cpu比較強勁,這個現象會消失。
  2. 因為網絡的影響,客戶端在發起連接時,可能發生延遲,導致在前1秒沒有完成連接。
  3. 服務器負載較大時,1000M網絡已經出現了丟包現象,可以通過ifconfig 命令觀察到這個現象,所以會有QPS的波動。

第二張是 服務器處理的QPS圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


和客戶端的向對應的,服務器也存在3個區間,和客戶端的情況很接近。

但是我們看到了在大概22:57分,系統的處理能力就有一個明顯的下降,隨後又提高的尖狀。這說明代碼還需要優化。

整體觀察在3萬QPS區間,服務器的QPS比較穩定,在6萬QSP時候,服務器的處理就不穩定了。我相信這和我的代碼有關,如果繼續優化的話,還應該能有更好的效果。

將2張圖合併起來 :

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


基本是吻合的,這也證明系統是符合預期設計的。

這是紅包生成數量的狀態變化圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


非常的穩定。

這是客戶端每秒獲取的搖紅包狀態:

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


客戶端:esxi 5.0 虛擬機,配置為4核 5G內存。一共17臺,每臺和服務器建立6萬個連接。完成100萬客戶端模擬

5. 技術分析和實現

5.1) 單機實現100萬用戶連接

這一點來說相對簡單,筆者在幾年前就早完成了單機百萬用戶的開發以及操作。現代的服務器都可以支持百萬用戶。相關內容可以查看:

github代碼以及相關文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系統配置以及優化文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3萬QPS

這個問題需要分2個部分來看客戶端方面和服務器方面。

  • 客戶端QPS


因為有100萬連接連在服務器上,QPS為3萬。這就意味著每個連接每33秒,就需要向服務器發一個搖紅包的請求。

因為單IP可以建立的連接數為6萬左右, 有17臺服務器同時模擬客戶端行為。我們要做的就保證在每一秒都有這麼多的請求發往服務器即可。

其中技術要點就是客戶端協同。但是各個客戶端的啟動時間,建立連接的時間都不一致,還存在網絡斷開重連這樣的情況,各個客戶端如何判斷何時自己需要發送請求,各自該發送多少請求呢?

我是這樣解決的:利用NTP服務,同步所有的服務器時間,客戶端利用時間戳來判斷自己的此時需要發送多少請求。

算法很容易實現:假設有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數為100萬,它計算 100萬/5萬=20,所有的用戶應該分為20組

如果 time() % 20 == 用戶id % 20,那麼這個id的用戶就該在這一秒發出請求,如此實現了多客戶端協同工作。每個客戶端只需要知道 總用戶數和QPS 就能自行準確發出請求了。

(擴展思考:如果QPS是3萬 這樣不能被整除的數目,該如何辦?如何保證每臺客戶端發出的請求數目儘量的均衡呢?)

  • 服務器QPS

服務器端的QPS相對簡單,它只需要處理客戶端的請求即可。但是為了客觀瞭解處理情況,我們還需要做2件事情。

  • 第一:需要記錄每秒處理的請求數目,這需要在代碼裡埋入計數器。
  • 第二:我們需要監控網絡,因為網絡的吞吐情況,可以客觀的反映出QPS的真實數據。
  • 為此,我利用python腳本 結合ethtool 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網絡的數據包通過情況如何。它可以客觀的顯示出我們的網絡有如此多的數據傳輸在發生。


工具截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


5.3) 搖紅包業務

搖紅包的業務非常簡單,首先服務器按照一定的速度生產紅包。紅包沒有被取走的話,就堆積在裡面。

服務器接收一個客戶端的請求,如果服務器裡現在有紅包就會告訴客戶端有,否則就提示沒有紅包。

因為單機每秒有3萬的請求,所以大部分的請求會失敗。只需要處理好鎖的問題即可。

我為了減少競爭,將所有的用戶分在了不同的桶裡。這樣可以減少對鎖的競爭。如果以後還有更高的性能要求,還可以使用 高性能隊列——Disruptor來進一步提高性能。

注意,在我的測試環境裡是缺少支付這個核心服務的,所以實現的難度是大大的減輕了。

另外提供一組數字:2016年淘寶的雙11的交易峰值僅僅為12萬/秒,微信紅包分發速度是5萬/秒,要做到這點是非常困難的。(http://mt.sohu.com/20161111/n472951708.shtml)

5.4) 發紅包業務

發紅包的業務很簡單,系統隨機產生一些紅包,並且隨機選擇一些用戶,系統向這些用戶提示有紅包。

這些用戶只需要發出拆紅包的請求,系統就可以隨機從紅包中拆分出部分金額,分給用戶,完成這個業務。同樣這裡也沒有支付這個核心服務。

5.5)監控

最後 我們需要一套監控系統來了解系統的狀況,我借用了我另一個項目(https://github.com/xiaojiaqi/fakewechat)

裡的部分代碼完成了這個監控模塊,利用這個監控,服務器和客戶端會把當前的計數器內容發往監控,監控需要把各個客戶端的數據做一個整合和展示。

同時還會把日誌記錄下來,給以後的分析提供原始數據。線上系統更多使用opentsdb這樣的時序數據庫,這裡資源有限,所以用了一個原始的方案。

監控顯示日誌大概這樣:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


6. 代碼實現及分析

在代碼方面,使用到的技巧實在不多,主要是設計思想和golang本身的一些問題需要考慮。

首先golang的goroutine 的數目控制,因為至少有100萬以上的連接,所以按照普通的設計方案,至少需要200萬或者300萬的goroutine在工作。這會造成系統本身的負擔很重。

其次就是100萬個連接的管理,無論是連接還是業務都會造成一些心智的負擔。

我的設計是這樣的:

首先將100萬連接分成多個不同的SET,每個SET是一個獨立,平行的對象。每個SET 只管理幾千個連接,如果單個SET 工作正常,我只需要添加SET就能提高系統處理能力。

其次謹慎的設計了每個SET裡數據結構的大小,保證每個SET的壓力不會太大,不會出現消息的堆積。

再次減少了gcroutine的數目,每個連接只使用一個goroutine,發送消息在一個SET裡只有一個gcroutine負責,這樣節省了100萬個goroutine。

這樣整個系統只需要保留 100萬零幾百個gcroutine就能完成業務。大量的節省了cpu 和內存

系統的工作流程大概是:每個客戶端連接成功後,系統會分配一個goroutine讀取客戶端的消息當消息讀取完成,將它轉化為消息對象放至在SET的接收消息隊列,然後返回獲取下一個消息。

在SET內部,有一個工作goroutine,它只做非常簡單而高效的事情,它做的事情如下,檢查SET的接受消息,它會收到3類消息

  1. 客戶端的搖紅包請求消息
  2. 客戶端的其他消息 比如聊天 好友這一類
  3. 服務器端對客戶端消息的迴應

對於第1種消息客戶端的搖紅包請求消息 是這樣處理的,從客戶端拿到搖紅包請求消息,試圖從SET的紅包隊列裡 獲取一個紅包

如果拿到了就把紅包信息 返回給客戶端,否則構造一個沒有搖到的消息,返回給對應的客戶端。

對於第2種消息客戶端的其他消息 比如聊天 好友這一類,只需簡單地從隊列裡拿走消息,轉發給後端的聊天服務隊列即可,其他服務會把消息轉發出去。

對於第3種消息服務器端對客戶端消息的迴應。SET 只需要根據消息裡的用戶id,找到SET裡保留的用戶連接對象,發回去就可以了。

對於紅包產生服務,它的工作很簡單,只需要按照順序在輪流在每個SET的紅包產生對列裡放至紅包對象就可以了。這樣可以保證每個SET裡都是公平的,其次它的工作強度很低,可以保證業務穩定。

見代碼:

https://github.com/xiaojiaqi/10billionhongbaos

7. 實踐

實踐的過程分為3個階段

階段1

分別啟動服務器端和監控端,然後逐一啟動17臺客戶端,讓它們建立起100萬的鏈接。

在服務器端,利用ss 命令 統計出每個客戶端和服務器建立了多少連接。

命令如下:

Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print $8}” | sort | uniq –c’

結果如下:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段2

利用客戶端的http接口,將所有的客戶端QPS 調整到3萬,讓客戶端發出3W QPS強度的請求。

運行如下命令:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


觀察網絡監控和監控端反饋,發現QPS 達到預期數據,網絡監控截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


在服務器端啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包,總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段3

利用客戶端的http接口,將所有的客戶端QPS 調整到6萬,讓客戶端發出6W QPS強度的請求。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


如法炮製,在服務器端,啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包。總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

最後,實踐完成。

8. 分析數據

在實踐過程中,服務器和客戶端都將自己內部的計數器記錄發往監控端,成為了日誌。

我們利用簡單python 腳本和gnuplt 繪圖工具,將實踐的過程可視化,由此來驗證運行過程。

第一張是客戶端的QPS發送數據:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


這張圖的橫座標是時間,單位是秒,縱座標是QPS,表示這時刻所有客戶端發送的請求的QPS。

圖的第一區間,幾個小的峰值,是100萬客戶端建立連接的, 圖的第二區間是3萬QPS 區間,我們可以看到數據 比較穩定的保持在3萬這個區間。

最後是6萬QPS區間。但是從整張圖可以看到QPS不是完美地保持在我們希望的直線上。這主要是以下幾個原因造成的

  1. 當非常多goroutine 同時運行的時候,依靠sleep 定時並不準確,發生了偏移。我覺得這是golang本身調度導致的。當然如果cpu比較強勁,這個現象會消失。
  2. 因為網絡的影響,客戶端在發起連接時,可能發生延遲,導致在前1秒沒有完成連接。
  3. 服務器負載較大時,1000M網絡已經出現了丟包現象,可以通過ifconfig 命令觀察到這個現象,所以會有QPS的波動。

第二張是 服務器處理的QPS圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


和客戶端的向對應的,服務器也存在3個區間,和客戶端的情況很接近。

但是我們看到了在大概22:57分,系統的處理能力就有一個明顯的下降,隨後又提高的尖狀。這說明代碼還需要優化。

整體觀察在3萬QPS區間,服務器的QPS比較穩定,在6萬QSP時候,服務器的處理就不穩定了。我相信這和我的代碼有關,如果繼續優化的話,還應該能有更好的效果。

將2張圖合併起來 :

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


基本是吻合的,這也證明系統是符合預期設計的。

這是紅包生成數量的狀態變化圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


非常的穩定。

這是客戶端每秒獲取的搖紅包狀態:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


可以發現3萬QPS區間,客戶端每秒獲取的紅包數基本在200左右,在6萬QPS的時候,以及出現劇烈的抖動,不能保證在200這個數值了。

我覺得主要是6萬QPS時候,網絡的抖動加劇了,造成了紅包數目也在抖動。

最後是golang 自帶的pprof 信息,其中有gc 時間超過了10ms, 考慮到這是一個7年前的硬件,而且非獨佔模式,所以還是可以接受。

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


客戶端:esxi 5.0 虛擬機,配置為4核 5G內存。一共17臺,每臺和服務器建立6萬個連接。完成100萬客戶端模擬

5. 技術分析和實現

5.1) 單機實現100萬用戶連接

這一點來說相對簡單,筆者在幾年前就早完成了單機百萬用戶的開發以及操作。現代的服務器都可以支持百萬用戶。相關內容可以查看:

github代碼以及相關文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系統配置以及優化文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3萬QPS

這個問題需要分2個部分來看客戶端方面和服務器方面。

  • 客戶端QPS


因為有100萬連接連在服務器上,QPS為3萬。這就意味著每個連接每33秒,就需要向服務器發一個搖紅包的請求。

因為單IP可以建立的連接數為6萬左右, 有17臺服務器同時模擬客戶端行為。我們要做的就保證在每一秒都有這麼多的請求發往服務器即可。

其中技術要點就是客戶端協同。但是各個客戶端的啟動時間,建立連接的時間都不一致,還存在網絡斷開重連這樣的情況,各個客戶端如何判斷何時自己需要發送請求,各自該發送多少請求呢?

我是這樣解決的:利用NTP服務,同步所有的服務器時間,客戶端利用時間戳來判斷自己的此時需要發送多少請求。

算法很容易實現:假設有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數為100萬,它計算 100萬/5萬=20,所有的用戶應該分為20組

如果 time() % 20 == 用戶id % 20,那麼這個id的用戶就該在這一秒發出請求,如此實現了多客戶端協同工作。每個客戶端只需要知道 總用戶數和QPS 就能自行準確發出請求了。

(擴展思考:如果QPS是3萬 這樣不能被整除的數目,該如何辦?如何保證每臺客戶端發出的請求數目儘量的均衡呢?)

  • 服務器QPS

服務器端的QPS相對簡單,它只需要處理客戶端的請求即可。但是為了客觀瞭解處理情況,我們還需要做2件事情。

  • 第一:需要記錄每秒處理的請求數目,這需要在代碼裡埋入計數器。
  • 第二:我們需要監控網絡,因為網絡的吞吐情況,可以客觀的反映出QPS的真實數據。
  • 為此,我利用python腳本 結合ethtool 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網絡的數據包通過情況如何。它可以客觀的顯示出我們的網絡有如此多的數據傳輸在發生。


工具截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


5.3) 搖紅包業務

搖紅包的業務非常簡單,首先服務器按照一定的速度生產紅包。紅包沒有被取走的話,就堆積在裡面。

服務器接收一個客戶端的請求,如果服務器裡現在有紅包就會告訴客戶端有,否則就提示沒有紅包。

因為單機每秒有3萬的請求,所以大部分的請求會失敗。只需要處理好鎖的問題即可。

我為了減少競爭,將所有的用戶分在了不同的桶裡。這樣可以減少對鎖的競爭。如果以後還有更高的性能要求,還可以使用 高性能隊列——Disruptor來進一步提高性能。

注意,在我的測試環境裡是缺少支付這個核心服務的,所以實現的難度是大大的減輕了。

另外提供一組數字:2016年淘寶的雙11的交易峰值僅僅為12萬/秒,微信紅包分發速度是5萬/秒,要做到這點是非常困難的。(http://mt.sohu.com/20161111/n472951708.shtml)

5.4) 發紅包業務

發紅包的業務很簡單,系統隨機產生一些紅包,並且隨機選擇一些用戶,系統向這些用戶提示有紅包。

這些用戶只需要發出拆紅包的請求,系統就可以隨機從紅包中拆分出部分金額,分給用戶,完成這個業務。同樣這裡也沒有支付這個核心服務。

5.5)監控

最後 我們需要一套監控系統來了解系統的狀況,我借用了我另一個項目(https://github.com/xiaojiaqi/fakewechat)

裡的部分代碼完成了這個監控模塊,利用這個監控,服務器和客戶端會把當前的計數器內容發往監控,監控需要把各個客戶端的數據做一個整合和展示。

同時還會把日誌記錄下來,給以後的分析提供原始數據。線上系統更多使用opentsdb這樣的時序數據庫,這裡資源有限,所以用了一個原始的方案。

監控顯示日誌大概這樣:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


6. 代碼實現及分析

在代碼方面,使用到的技巧實在不多,主要是設計思想和golang本身的一些問題需要考慮。

首先golang的goroutine 的數目控制,因為至少有100萬以上的連接,所以按照普通的設計方案,至少需要200萬或者300萬的goroutine在工作。這會造成系統本身的負擔很重。

其次就是100萬個連接的管理,無論是連接還是業務都會造成一些心智的負擔。

我的設計是這樣的:

首先將100萬連接分成多個不同的SET,每個SET是一個獨立,平行的對象。每個SET 只管理幾千個連接,如果單個SET 工作正常,我只需要添加SET就能提高系統處理能力。

其次謹慎的設計了每個SET裡數據結構的大小,保證每個SET的壓力不會太大,不會出現消息的堆積。

再次減少了gcroutine的數目,每個連接只使用一個goroutine,發送消息在一個SET裡只有一個gcroutine負責,這樣節省了100萬個goroutine。

這樣整個系統只需要保留 100萬零幾百個gcroutine就能完成業務。大量的節省了cpu 和內存

系統的工作流程大概是:每個客戶端連接成功後,系統會分配一個goroutine讀取客戶端的消息當消息讀取完成,將它轉化為消息對象放至在SET的接收消息隊列,然後返回獲取下一個消息。

在SET內部,有一個工作goroutine,它只做非常簡單而高效的事情,它做的事情如下,檢查SET的接受消息,它會收到3類消息

  1. 客戶端的搖紅包請求消息
  2. 客戶端的其他消息 比如聊天 好友這一類
  3. 服務器端對客戶端消息的迴應

對於第1種消息客戶端的搖紅包請求消息 是這樣處理的,從客戶端拿到搖紅包請求消息,試圖從SET的紅包隊列裡 獲取一個紅包

如果拿到了就把紅包信息 返回給客戶端,否則構造一個沒有搖到的消息,返回給對應的客戶端。

對於第2種消息客戶端的其他消息 比如聊天 好友這一類,只需簡單地從隊列裡拿走消息,轉發給後端的聊天服務隊列即可,其他服務會把消息轉發出去。

對於第3種消息服務器端對客戶端消息的迴應。SET 只需要根據消息裡的用戶id,找到SET裡保留的用戶連接對象,發回去就可以了。

對於紅包產生服務,它的工作很簡單,只需要按照順序在輪流在每個SET的紅包產生對列裡放至紅包對象就可以了。這樣可以保證每個SET裡都是公平的,其次它的工作強度很低,可以保證業務穩定。

見代碼:

https://github.com/xiaojiaqi/10billionhongbaos

7. 實踐

實踐的過程分為3個階段

階段1

分別啟動服務器端和監控端,然後逐一啟動17臺客戶端,讓它們建立起100萬的鏈接。

在服務器端,利用ss 命令 統計出每個客戶端和服務器建立了多少連接。

命令如下:

Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print $8}” | sort | uniq –c’

結果如下:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段2

利用客戶端的http接口,將所有的客戶端QPS 調整到3萬,讓客戶端發出3W QPS強度的請求。

運行如下命令:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


觀察網絡監控和監控端反饋,發現QPS 達到預期數據,網絡監控截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


在服務器端啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包,總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段3

利用客戶端的http接口,將所有的客戶端QPS 調整到6萬,讓客戶端發出6W QPS強度的請求。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


如法炮製,在服務器端,啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包。總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

最後,實踐完成。

8. 分析數據

在實踐過程中,服務器和客戶端都將自己內部的計數器記錄發往監控端,成為了日誌。

我們利用簡單python 腳本和gnuplt 繪圖工具,將實踐的過程可視化,由此來驗證運行過程。

第一張是客戶端的QPS發送數據:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


這張圖的橫座標是時間,單位是秒,縱座標是QPS,表示這時刻所有客戶端發送的請求的QPS。

圖的第一區間,幾個小的峰值,是100萬客戶端建立連接的, 圖的第二區間是3萬QPS 區間,我們可以看到數據 比較穩定的保持在3萬這個區間。

最後是6萬QPS區間。但是從整張圖可以看到QPS不是完美地保持在我們希望的直線上。這主要是以下幾個原因造成的

  1. 當非常多goroutine 同時運行的時候,依靠sleep 定時並不準確,發生了偏移。我覺得這是golang本身調度導致的。當然如果cpu比較強勁,這個現象會消失。
  2. 因為網絡的影響,客戶端在發起連接時,可能發生延遲,導致在前1秒沒有完成連接。
  3. 服務器負載較大時,1000M網絡已經出現了丟包現象,可以通過ifconfig 命令觀察到這個現象,所以會有QPS的波動。

第二張是 服務器處理的QPS圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


和客戶端的向對應的,服務器也存在3個區間,和客戶端的情況很接近。

但是我們看到了在大概22:57分,系統的處理能力就有一個明顯的下降,隨後又提高的尖狀。這說明代碼還需要優化。

整體觀察在3萬QPS區間,服務器的QPS比較穩定,在6萬QSP時候,服務器的處理就不穩定了。我相信這和我的代碼有關,如果繼續優化的話,還應該能有更好的效果。

將2張圖合併起來 :

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


基本是吻合的,這也證明系統是符合預期設計的。

這是紅包生成數量的狀態變化圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


非常的穩定。

這是客戶端每秒獲取的搖紅包狀態:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


可以發現3萬QPS區間,客戶端每秒獲取的紅包數基本在200左右,在6萬QPS的時候,以及出現劇烈的抖動,不能保證在200這個數值了。

我覺得主要是6萬QPS時候,網絡的抖動加劇了,造成了紅包數目也在抖動。

最後是golang 自帶的pprof 信息,其中有gc 時間超過了10ms, 考慮到這是一個7年前的硬件,而且非獨佔模式,所以還是可以接受。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


總結

按照設計目標,我們模擬和設計了一個支持100萬用戶,並且每秒至少可以支持3萬QPS,最多6萬QPS的系統,簡單模擬了微信的搖紅包和發紅包的過程。可以說達到了預期的目的。

如果600臺主機每臺主機可以支持6萬QPS,只需要7分鐘就可以完成 100億次搖紅包請求。

雖然這個原型簡單地完成了預設的業務,但是它和真正的服務會有哪些差別呢?我羅列了一下

"

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》一文,看完以後,感慨良多,收益很多。

正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經過去良久,但是其中的思想仍然是可以為很多後端設計借鑑。

同時作為一微信後端工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?

所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?

否則讀完以後腦子裡能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

實現的目標:單機支持100萬連接,模擬了搖紅包和發紅包過程,單機峰值QPS 6萬,平穩支持了業務。

注:本文以及作者所有內容,僅代表個人理解和實踐,過程和微信團隊沒有任何關係,真正的線上系統也不同,只是從一些技術點進行了實踐,請讀者進行區分。

2. 背景知識

  • QPS:Queries per second 每秒的請求數目
  • PPS:Packets per second 每秒數據包數目
  • 搖紅包:客戶端發出一個搖紅包的請求,如果系統有紅包就會返回,用戶獲得紅包
  • 發紅包:產生一個紅包裡面含有一定金額,紅包指定數個用戶,每個用戶會收到紅包信息,用戶可以發送拆紅包的請求,獲取其中的部分金額。


3. 確定目標

在一切系統開始以前,我們應該搞清楚我們的系統在完成以後,應該有一個什麼樣的負載能力。

3.1 用戶總數

通過文章我們可以瞭解到接入服務器638臺,服務上限大概是14.3億用戶, 所以單機負載的用戶上限大概是14.3億/638臺=228萬用戶/臺。

但是目前中國肯定不會有14億用戶同時在線,參考http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。

所以在2015年春節期間,雖然使用的用戶會很多,但是同時在線肯定不到5.4億。

3.2. 服務器數量

一共有638臺服務器,按照正常運維設計,我相信所有服務器不會完全上線,會有一定的硬件冗餘,來防止突發硬件故障。假設一共有600臺接入服務器。

3.3 單機需要支持的負載數

每臺服務器支持的用戶數:5.4億/600 = 90萬。也就是平均單機支持90萬用戶。如果真實情況比90萬更多,則模擬的情況可能會有偏差,但是我認為QPS在這個實驗中更重要。

3.4. 單機峰值QPS

文章中明確表示為1400萬QPS.這個數值是非常高的,但是因為有600臺服務器存在,所以單機的QPS為 1400萬/600= 約為2.3萬QPS

文章曾經提及系統可以支持4000萬QPS,那麼系統的QPS 至少要到4000萬/600 = 約為 6.6萬, 這個數值大約是目前的3倍,短期來看並不會被觸及。但是我相信應該做過相應的壓力測試。

3.5. 發放紅包

文中提到系統以5萬個每秒的下發速度,那麼單機每秒下發速度50000/600 =83個/秒,也就是單機系統應該保證每秒以83個的速度下發即可。

最後考慮到系統的真實性,還至少有用戶登錄的動作,拿紅包這樣的業務。真實的系統還會包括聊天這樣的服務業務。

最後整體的看一下 100億次搖紅包這個需求,假設它是均勻地發生在春節聯歡晚會的4個小時裡,那麼服務器的QPS 應該是10000000000/600/3600/4.0=1157. 也就是單機每秒1000多次,這個數值其實並不高。

如果完全由峰值速度1400萬消化 10000000000/(1400*10000) = 714秒,也就是說只需要峰值堅持11分鐘,就可以完成所有的請求。可見互聯網產品的一個特點就是峰值非常高,持續時間並不會很長。

總結

從單臺服務器看,它需要滿足下面一些條件:

  1. 支持至少100萬連接用戶
  2. 每秒至少能處理2.3萬的QPS,這裡我們把目標定得更高一些 分別設定到了3萬和6萬。
  3. 搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其餘的2.29萬次請求會知道自己沒搖到。
  4. 當然客戶端在收到紅包以後,也需要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模塊,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  5. 支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。


想完整模擬整個系統實在太難了,首先需要海量的服務器,其次需要上億的模擬客戶端。這對我來說是辦不到

但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。

和現有系統區別:和大部分高QPS測試的不同,本系統的側重點有所不同。我對2者做了一些對比。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


4. 基礎軟件和硬件

4.1軟件

Golang 1.8r3 , shell, python (開發沒有使用c++ 而是使用了golang, 是因為使用golang 的最初原型達到了系統要求。雖然golang 還存在一定的問題,但是和開發效率比,這點損失可以接受)

服務器操作系統:Ubuntu 12.04

客戶端操作系統:debian 5.0

4.2硬件環境

服務端:dell R2950。8核物理機,非獨佔有其他業務在工作,16G內存。這臺硬件大概是7年前的產品,性能應該不是很高要求。

服務器硬件版本:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


服務器CPU信息:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


客戶端:esxi 5.0 虛擬機,配置為4核 5G內存。一共17臺,每臺和服務器建立6萬個連接。完成100萬客戶端模擬

5. 技術分析和實現

5.1) 單機實現100萬用戶連接

這一點來說相對簡單,筆者在幾年前就早完成了單機百萬用戶的開發以及操作。現代的服務器都可以支持百萬用戶。相關內容可以查看:

github代碼以及相關文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide

系統配置以及優化文檔:

https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3萬QPS

這個問題需要分2個部分來看客戶端方面和服務器方面。

  • 客戶端QPS


因為有100萬連接連在服務器上,QPS為3萬。這就意味著每個連接每33秒,就需要向服務器發一個搖紅包的請求。

因為單IP可以建立的連接數為6萬左右, 有17臺服務器同時模擬客戶端行為。我們要做的就保證在每一秒都有這麼多的請求發往服務器即可。

其中技術要點就是客戶端協同。但是各個客戶端的啟動時間,建立連接的時間都不一致,還存在網絡斷開重連這樣的情況,各個客戶端如何判斷何時自己需要發送請求,各自該發送多少請求呢?

我是這樣解決的:利用NTP服務,同步所有的服務器時間,客戶端利用時間戳來判斷自己的此時需要發送多少請求。

算法很容易實現:假設有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數為100萬,它計算 100萬/5萬=20,所有的用戶應該分為20組

如果 time() % 20 == 用戶id % 20,那麼這個id的用戶就該在這一秒發出請求,如此實現了多客戶端協同工作。每個客戶端只需要知道 總用戶數和QPS 就能自行準確發出請求了。

(擴展思考:如果QPS是3萬 這樣不能被整除的數目,該如何辦?如何保證每臺客戶端發出的請求數目儘量的均衡呢?)

  • 服務器QPS

服務器端的QPS相對簡單,它只需要處理客戶端的請求即可。但是為了客觀瞭解處理情況,我們還需要做2件事情。

  • 第一:需要記錄每秒處理的請求數目,這需要在代碼裡埋入計數器。
  • 第二:我們需要監控網絡,因為網絡的吞吐情況,可以客觀的反映出QPS的真實數據。
  • 為此,我利用python腳本 結合ethtool 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網絡的數據包通過情況如何。它可以客觀的顯示出我們的網絡有如此多的數據傳輸在發生。


工具截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


5.3) 搖紅包業務

搖紅包的業務非常簡單,首先服務器按照一定的速度生產紅包。紅包沒有被取走的話,就堆積在裡面。

服務器接收一個客戶端的請求,如果服務器裡現在有紅包就會告訴客戶端有,否則就提示沒有紅包。

因為單機每秒有3萬的請求,所以大部分的請求會失敗。只需要處理好鎖的問題即可。

我為了減少競爭,將所有的用戶分在了不同的桶裡。這樣可以減少對鎖的競爭。如果以後還有更高的性能要求,還可以使用 高性能隊列——Disruptor來進一步提高性能。

注意,在我的測試環境裡是缺少支付這個核心服務的,所以實現的難度是大大的減輕了。

另外提供一組數字:2016年淘寶的雙11的交易峰值僅僅為12萬/秒,微信紅包分發速度是5萬/秒,要做到這點是非常困難的。(http://mt.sohu.com/20161111/n472951708.shtml)

5.4) 發紅包業務

發紅包的業務很簡單,系統隨機產生一些紅包,並且隨機選擇一些用戶,系統向這些用戶提示有紅包。

這些用戶只需要發出拆紅包的請求,系統就可以隨機從紅包中拆分出部分金額,分給用戶,完成這個業務。同樣這裡也沒有支付這個核心服務。

5.5)監控

最後 我們需要一套監控系統來了解系統的狀況,我借用了我另一個項目(https://github.com/xiaojiaqi/fakewechat)

裡的部分代碼完成了這個監控模塊,利用這個監控,服務器和客戶端會把當前的計數器內容發往監控,監控需要把各個客戶端的數據做一個整合和展示。

同時還會把日誌記錄下來,給以後的分析提供原始數據。線上系統更多使用opentsdb這樣的時序數據庫,這裡資源有限,所以用了一個原始的方案。

監控顯示日誌大概這樣:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


6. 代碼實現及分析

在代碼方面,使用到的技巧實在不多,主要是設計思想和golang本身的一些問題需要考慮。

首先golang的goroutine 的數目控制,因為至少有100萬以上的連接,所以按照普通的設計方案,至少需要200萬或者300萬的goroutine在工作。這會造成系統本身的負擔很重。

其次就是100萬個連接的管理,無論是連接還是業務都會造成一些心智的負擔。

我的設計是這樣的:

首先將100萬連接分成多個不同的SET,每個SET是一個獨立,平行的對象。每個SET 只管理幾千個連接,如果單個SET 工作正常,我只需要添加SET就能提高系統處理能力。

其次謹慎的設計了每個SET裡數據結構的大小,保證每個SET的壓力不會太大,不會出現消息的堆積。

再次減少了gcroutine的數目,每個連接只使用一個goroutine,發送消息在一個SET裡只有一個gcroutine負責,這樣節省了100萬個goroutine。

這樣整個系統只需要保留 100萬零幾百個gcroutine就能完成業務。大量的節省了cpu 和內存

系統的工作流程大概是:每個客戶端連接成功後,系統會分配一個goroutine讀取客戶端的消息當消息讀取完成,將它轉化為消息對象放至在SET的接收消息隊列,然後返回獲取下一個消息。

在SET內部,有一個工作goroutine,它只做非常簡單而高效的事情,它做的事情如下,檢查SET的接受消息,它會收到3類消息

  1. 客戶端的搖紅包請求消息
  2. 客戶端的其他消息 比如聊天 好友這一類
  3. 服務器端對客戶端消息的迴應

對於第1種消息客戶端的搖紅包請求消息 是這樣處理的,從客戶端拿到搖紅包請求消息,試圖從SET的紅包隊列裡 獲取一個紅包

如果拿到了就把紅包信息 返回給客戶端,否則構造一個沒有搖到的消息,返回給對應的客戶端。

對於第2種消息客戶端的其他消息 比如聊天 好友這一類,只需簡單地從隊列裡拿走消息,轉發給後端的聊天服務隊列即可,其他服務會把消息轉發出去。

對於第3種消息服務器端對客戶端消息的迴應。SET 只需要根據消息裡的用戶id,找到SET裡保留的用戶連接對象,發回去就可以了。

對於紅包產生服務,它的工作很簡單,只需要按照順序在輪流在每個SET的紅包產生對列裡放至紅包對象就可以了。這樣可以保證每個SET裡都是公平的,其次它的工作強度很低,可以保證業務穩定。

見代碼:

https://github.com/xiaojiaqi/10billionhongbaos

7. 實踐

實踐的過程分為3個階段

階段1

分別啟動服務器端和監控端,然後逐一啟動17臺客戶端,讓它們建立起100萬的鏈接。

在服務器端,利用ss 命令 統計出每個客戶端和服務器建立了多少連接。

命令如下:

Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print $8}” | sort | uniq –c’

結果如下:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段2

利用客戶端的http接口,將所有的客戶端QPS 調整到3萬,讓客戶端發出3W QPS強度的請求。

運行如下命令:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


觀察網絡監控和監控端反饋,發現QPS 達到預期數據,網絡監控截圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


在服務器端啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包,總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


階段3

利用客戶端的http接口,將所有的客戶端QPS 調整到6萬,讓客戶端發出6W QPS強度的請求。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


如法炮製,在服務器端,啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包。總共4萬個。

此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

最後,實踐完成。

8. 分析數據

在實踐過程中,服務器和客戶端都將自己內部的計數器記錄發往監控端,成為了日誌。

我們利用簡單python 腳本和gnuplt 繪圖工具,將實踐的過程可視化,由此來驗證運行過程。

第一張是客戶端的QPS發送數據:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


這張圖的橫座標是時間,單位是秒,縱座標是QPS,表示這時刻所有客戶端發送的請求的QPS。

圖的第一區間,幾個小的峰值,是100萬客戶端建立連接的, 圖的第二區間是3萬QPS 區間,我們可以看到數據 比較穩定的保持在3萬這個區間。

最後是6萬QPS區間。但是從整張圖可以看到QPS不是完美地保持在我們希望的直線上。這主要是以下幾個原因造成的

  1. 當非常多goroutine 同時運行的時候,依靠sleep 定時並不準確,發生了偏移。我覺得這是golang本身調度導致的。當然如果cpu比較強勁,這個現象會消失。
  2. 因為網絡的影響,客戶端在發起連接時,可能發生延遲,導致在前1秒沒有完成連接。
  3. 服務器負載較大時,1000M網絡已經出現了丟包現象,可以通過ifconfig 命令觀察到這個現象,所以會有QPS的波動。

第二張是 服務器處理的QPS圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


和客戶端的向對應的,服務器也存在3個區間,和客戶端的情況很接近。

但是我們看到了在大概22:57分,系統的處理能力就有一個明顯的下降,隨後又提高的尖狀。這說明代碼還需要優化。

整體觀察在3萬QPS區間,服務器的QPS比較穩定,在6萬QSP時候,服務器的處理就不穩定了。我相信這和我的代碼有關,如果繼續優化的話,還應該能有更好的效果。

將2張圖合併起來 :

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


基本是吻合的,這也證明系統是符合預期設計的。

這是紅包生成數量的狀態變化圖:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


非常的穩定。

這是客戶端每秒獲取的搖紅包狀態:

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


可以發現3萬QPS區間,客戶端每秒獲取的紅包數基本在200左右,在6萬QPS的時候,以及出現劇烈的抖動,不能保證在200這個數值了。

我覺得主要是6萬QPS時候,網絡的抖動加劇了,造成了紅包數目也在抖動。

最後是golang 自帶的pprof 信息,其中有gc 時間超過了10ms, 考慮到這是一個7年前的硬件,而且非獨佔模式,所以還是可以接受。

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


總結

按照設計目標,我們模擬和設計了一個支持100萬用戶,並且每秒至少可以支持3萬QPS,最多6萬QPS的系統,簡單模擬了微信的搖紅包和發紅包的過程。可以說達到了預期的目的。

如果600臺主機每臺主機可以支持6萬QPS,只需要7分鐘就可以完成 100億次搖紅包請求。

雖然這個原型簡單地完成了預設的業務,但是它和真正的服務會有哪些差別呢?我羅列了一下

如何設計抗住100億次請求的搶紅包系統?(附GitHub代碼)


"

相關推薦

推薦中...