扛住 100 億次請求?我們來試一試

  • 2019 年 11 月 29 日
  • 筆記

來源:github.com/xiaojiaqi/10billionhongbaos/

  • 1. 前言
  • 2. 背景知識
  • 3. 確定目標
  • 4. 基礎軟體和硬體
  • 5. 技術分析和實現
  • 6. 程式碼實現及分析
  • 7. 實踐
  • 8. 分析數據
  • 總結:

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個「有把握」的春晚紅包系統」》(url)一文,看完以後,感慨良多,收益很多。正所謂他山之石,可以攻玉,雖然此文發表於2015年,我看到時已經是2016年末,但是其中的思想仍然是可以為很多後端設計借鑒,。同時作為一個工程師,看完以後又會思考,學習了這樣的文章以後,是否能給自己的工作帶來一些實際的經驗呢?所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下100億次紅包請求呢?否則讀完以後腦子裡能剩下的東西 不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標,在本地環境的模擬了此過程。

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

2. 背景知識

QPS: Queries 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萬次請求會知道自己沒搖到。當然客戶端在收到紅包以後,也需要確保客戶端和伺服器兩邊的紅包數目和紅包內的金額要一致。因為沒有支付模組,所以我們也把要求提高一倍,達到200個紅包每秒的分發速度
  4. 支援用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。同樣也設定200個紅包每秒的分發速度為我們的目標。

想完整模擬整個系統實在太難了,首先需要海量的伺服器,其次需要上億的模擬客戶端。這對我來說是辦不到,但是有一點可以確定,整個系統是可以水平擴展的,所以我們可以模擬100萬客戶端,在模擬一台伺服器 那麼就完成了1/600的模擬。

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

常見高QPS系統壓力測試

本系統壓力測試

連接數

一般<1000 (幾百以內)

1000000 (1百萬)

單連接吞吐量

非常大 每個連接幾十M位元組吞吐

非常小 每個連接每次幾十個位元組

需要的IO次數

不多

非常多

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年前的產品,性能應該不是很高要求。 伺服器硬體版本:

machine

伺服器CPU資訊:

cpu

客戶端: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 工具編寫了一個簡單的工具,通過它我們可以直觀的監視到網路的數據包通過情況如何。它可以客觀的顯示出我們的網路有如此多的數據傳輸在發生。 工具截圖:

工具截圖

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這樣的時序資料庫,這裡資源有限,所以用了一個原始的方案

監控顯示日誌大概這樣

監控日誌

6. 程式碼實現及分析

在程式碼方面,使用到的技巧實在不多,主要是設計思想和golang本身的一些問題需要考慮。 首先golang的goroutine 的數目控制,因為至少有100萬以上的連接,所以按照普通的設計方案,至少需要200萬或者300萬的goroutine在工作。這會造成系統本身的負擔很重。 其次就是100萬個連接的管理,無論是連接還是業務都會造成一些心智的負擔。 我的設計是這樣的:

架構圖

首先將100萬連接分成多個不同的SET,每個SET是一個獨立,平行的對象。每個SET 只管理幾千個連接,如果單個SET 工作正常,我只需要添加SET就能提高系統處理能力。按照SET分還有一個好處,可以將一個SET作為一個業務單元,在不同性能伺服器上可以負載不同的壓力,比如8核機器管理10個SET,4核機器管理5個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萬連接建立

階段2

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

運行如下命令:

啟動腳本

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

3萬qps

在伺服器端啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包,總共4萬個。此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。

搖紅包

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

發紅包

階段3

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

6wqps

如法炮製,在伺服器端,啟動一個產生紅包的服務,這個服務會以200個每秒的速度下發紅包。總共4萬個。此時觀察客戶端在監控上的日誌,會發現基本上以200個每秒的速度獲取到紅包。 等到所有紅包下發完成後,再啟動一個發紅包的服務,這個服務系統會生成2萬個紅包,每秒也是200個,每個紅包隨機指定3位用戶,並向這3個用戶發出消息,客戶端會自動來拿紅包,最後所有的紅包都被拿走。

最後,實踐完成。

8. 分析數據

在實踐過程中,伺服器和客戶端都將自己內部的計數器記錄發往監控端,成為了日誌。我們利用簡單python 腳本和gnuplt 繪圖工具,將實踐的過程可視化,由此來驗證運行過程。

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

客戶端qps

這張圖的橫坐標是時間,單位是秒,縱坐標是QPS,表示這時刻所有客戶端發送的請求的QPS。 圖的第一區間,幾個小的峰值,是100萬客戶端建立連接的, 圖的第二區間是3萬QPS 區間,我們可以看到數據 比較穩定的保持在3萬這個區間。最後是6萬QPS區間。但是從整張圖可以看到QPS不是完美地保持在我們希望的直線上。這主要是以下幾個原因造成的

  1. 當非常多goroutine 同時運行的時候,依靠sleep 定時並不準確,發生了偏移。我覺得這是golang本身調度導致的。當然如果cpu比較強勁,這個現象會消失。
  2. 因為網路的影響,客戶端在發起連接時,可能發生延遲,導致在前1秒沒有完成連接。
  3. 伺服器負載較大時,1000M網路已經出現了丟包現象,可以通過ifconfig 命令觀察到這個現象,所以會有QPS的波動。

第二張是 伺服器處理的QPS圖

伺服器qps

和客戶端的向對應的,伺服器也存在3個區間,和客戶端的情況很接近。但是我們看到了在大概22:57分,系統的處理能力就有一個明顯的下降,隨後又提高的尖狀。這說明程式碼還需要優化。

整體觀察在3萬QPS區間,伺服器的QPS比較穩定,在6萬QSP時候,伺服器的處理就不穩定了。我相信這和我的程式碼有關,如果繼續優化的話,還應該能有更好的效果。

將2張圖合併起來

qps

基本是吻合的,這也證明系統是符合預期設計的。

這是紅包生成數量的狀態變化圖

生成紅包

非常的穩定。

這是客戶端每秒獲取的搖紅包狀態

獲取紅包

可以發現3萬QPS區間,客戶端每秒獲取的紅包數基本在200左右,在6萬QPS的時候,以及出現劇烈的抖動,不能保證在200這個數值了。我覺得主要是6萬QPS時候,網路的抖動加劇了,造成了紅包數目也在抖動。

最後是golang 自帶的pprof 資訊,其中有gc 時間超過了10ms, 考慮到這是一個7年前的硬體,而且非獨佔模式,所以還是可以接受。

pprof

總結:

按照設計目標,我們模擬和設計了一個支援100萬用戶,並且每秒至少可以支援3萬QPS,最多6萬QPS的系統,簡單模擬了微信的搖紅包和發紅包的過程。可以說達到了預期的目的。 如果600台主機每台主機可以支援6萬QPS,只需要7分鐘就可以完成 100億次搖紅包請求。

雖然這個原型簡單地完成了預設的業務,但是它和真正的服務會有哪些差別呢?我羅列了一下

區別

真正服務

本次模擬

業務複雜

更複雜

非常簡單

協議

Protobuf 以及加密

簡單的協議

支付

複雜

日誌

複雜

性能

更高

用戶分布

用戶id分散在不同伺服器,需要hash以後統一, 複雜。

用戶id 連續,很多優化使程式碼簡單 非常高效

安全控制

複雜

熱更新及版本控制

複雜

監控

細緻

簡單

Refers:

  • 單機百萬的實踐
  • https://github.com/xiaojiaqi/C1000kPracticeGuide
  • 如何在AWS上進行100萬用戶壓力測試
  • https://github.com/xiaojiaqi/fakewechat/wiki/Stress-Testing-in-the-Cloud
  • 構建一個你自己的類微信系統
  • https://github.com/xiaojiaqi/fakewechat/wiki/Design
  • http://djt.qq.com/article/view/1356
  • http://techblog.cloudperf.net/2016/05/2-million-packets-per-second-on-public.html
  • http://datacratic.com/site/blog/1m-qps-nginx-and-ubuntu-1204-ec2
  • @火丁筆記
  • http://huoding.com/2013/10/30/296
  • https://gobyexample.com/non-blocking-channel-operations