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

yummy 7年前發布 | 17K 次閱讀 服務器 分布式/云計算/大數據

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

1. 前言

前幾天,偶然看到了 《扛住100億次請求——如何做一個“有把握”的春晚紅包系統”》( url )一文,看完以后,感慨良多,收益很多。正所謂他山之石,可以攻玉,雖然此文發表于2015年,我看到時已經是2016年末,但是其中的思想仍然是可以為很多后端設計借鑒,。同時作為一個工程師,看完以后又會思考,學習了這樣的文章以后,是否能給自己的工作帶來一些實際的經驗呢?所謂紙上得來終覺淺,絕知此事要躬行,能否自己實踐一下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億用戶同時在線,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年前的產品,性能應該不是很高要求。

服務器硬件版本:

服務器CPU信息:

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

5. 技術分析和實現

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

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

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萬/秒,要做到這點是非常困難的。

5.4) 發紅包業務

發紅包的業務很簡單,系統隨機產生一些紅包,并且隨機選擇一些用戶,系統向這些用戶提示有紅包。這些用戶只需要發出拆紅包的請求,系統就可以隨機從紅包中拆分出部分金額,分給用戶,完成這個業務。同樣這里也沒有支付這個核心服務。

5.5)監控

最后 我們需要一套監控系統來了解系統的狀況,我借用了我另一個項目里的部分代碼完成了這個監控模塊,利用這個監控,服務器和客戶端會把當前的計數器內容發往監控,監控需要把各個客戶端的數據做一個整合和展示。同時還會把日志記錄下來,給以后的分析提供原始數據。 線上系統更多使用opentsdb這樣的時序數據庫,這里資源有限,所以用了一個原始的方案

監控顯示日志大概這樣

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里都是公平的,其次它的工作強度很低,可以保證業務穩定。

7實踐

實踐的過程分為3個階段

階段1:

分別啟動服務器端和監控端,然后逐一啟動17臺客戶端,讓它們建立起100萬的鏈接。在服務器端,利用ss 命令 統計出每個客戶端和服務器建立了多少連接。

命令如下:

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

結果如下:

階段2:

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

運行如下命令:

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

網絡監控截圖

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

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

階段3

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

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

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

最后,實踐完成。

8 分析數據

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

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

這張圖的橫坐標是時間,單位是秒,縱坐標是QPS,表示這時刻所有客戶端發送的請求的QPS。

圖的第一區間,幾個小的峰值,是100萬客戶端建立連接的, 圖的第二區間是3萬QPS 區間,我們可以看到數據 比較穩定的保持在3萬這個區間。最后是6萬QPS區間。但是從整張圖可以看到QPS不是完美地保持在我們希望的直線上。這主要是以下幾個原因造成的

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

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

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

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

將2張圖合并起來

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

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

非常的穩定。

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

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

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

按照設計目標,我們模擬和設計了一個支持100萬用戶,并且每秒至少可以支持3萬QPS,最多6萬QPS的系統,簡單模擬了微信的搖紅包和發紅包的過程。可以說達到了預期的目的。

如果600臺主機每臺主機可以支持6萬QPS,只需要7分鐘就可以完成 100億次搖紅包請求。

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

區別 真正服務 本次模擬
業務復雜 更復雜 非常簡單
協議 Protobuf 以及加密 簡單的協議
支付 復雜
日志 復雜
性能 更高
用戶分布 用戶id分散在不同服務器,需要hash以后統一, 復雜。 用戶id 連續,很多優化使代碼簡單 非常高效
安全控制 復雜
熱更新及版本控制 復雜
監控 細致 簡單

Refers:

 

來自:https://github.com/xiaojiaqi/10billionhongbaos/wiki/扛住100億次請求?我們來試一試

 

 本文由用戶 yummy 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!