=========================html
中華石杉老師最新力做:
python
《21天互聯網Java進階面試訓練營》nginx
(分佈式篇)c++
完全解決 Java 工程師面試一線互聯網大廠的各類痛點git
掃描下方二維碼,試聽課程:github
(詳細課程目錄參見文末)golang
======================================面試
項目地址:https://github.com/xiaojiaqi/10billionhongbaos算法
前幾天,偶然看到了 《扛住100億次請求——如何作一個「有把握」的春晚紅包系統」》一文,看完之後,感慨良多,收益不少。shell
正所謂他山之石,能夠攻玉,雖然此文發表於2015年,我看到時已通過去良久,可是其中的思想仍然是能夠爲不少後端設計借鑑。
同時做爲一微信後端工程師,看完之後又會思考,學習了這樣的文章之後,是否能給本身的工做帶來一些實際的經驗呢?
所謂紙上得來終覺淺,絕知此事要躬行,可否本身實踐一下100億次紅包請求呢?
不然讀完之後腦子裏能剩下的東西不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展現做者是如何以此過程爲目標,在本地環境的模擬了此過程。
實現的目標:單機支持100萬鏈接,模擬了搖紅包和發紅包過程,單機峯值QPS 6萬,平穩支持了業務。
QPS:Queries per second 每秒的請求數目
PPS:Packets per second 每秒數據包數目
搖紅包:客戶端發出一個搖紅包的請求,若是系統有紅包就會返回,用戶得到紅包
發紅包:產生一個紅包裏面含有必定金額,紅包指定數個用戶,每一個用戶會收到紅包信息,用戶能夠發送拆紅包的請求,獲取其中的部分金額。
在一切系統開始之前,咱們應該搞清楚咱們的系統在完成之後,應該有一個什麼樣的負載能力。
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分鐘,就能夠完成全部的請求。可見互聯網產品的一個特色就是峯值很是高,持續時間並不會很長。
從單臺服務器看,它須要知足下面一些條件:
支持至少100萬鏈接用戶
每秒至少能處理2.3萬的QPS,這裏咱們把目標定得更高一些 分別設定到了3萬和6萬。
搖紅包:支持每秒83個的速度下發放紅包,也就是說每秒有2.3萬次搖紅包的請求,其中83個請求能搖到紅包,其他的2.29萬次請求會知道本身沒搖到。
固然客戶端在收到紅包之後,也須要確保客戶端和服務器兩邊的紅包數目和紅包內的金額要一致。由於沒有支付模塊,因此咱們也把要求提升一倍,達到200個紅包每秒的分發速度
支持用戶之間發紅包業務,確保收發兩邊的紅包數目和紅包內金額要一致。一樣也設定200個紅包每秒的分發速度爲咱們的目標。
想完整模擬整個系統實在太難了,首先須要海量的服務器,其次須要上億的模擬客戶端。這對我來講是辦不到
可是有一點能夠肯定,整個系統是能夠水平擴展的,因此咱們能夠模擬100萬客戶端,在模擬一臺服務器 那麼就完成了1/600的模擬。
和現有系統區別:和大部分高QPS測試的不一樣,本系統的側重點有所不一樣。我對2者作了一些對比。
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.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
服務器端的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這樣的時序數據庫,這裏資源有限,因此用了一個原始的方案。
監控顯示日誌大概這樣:
在代碼方面,使用到的技巧實在很少,主要是設計思想和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種消息客戶端的搖紅包請求消息 是這樣處理的,從客戶端拿到搖紅包請求消息,試圖從SET的紅包隊列裏 獲取一個紅包
若是拿到了就把紅包信息 返回給客戶端,不然構造一個沒有搖到的消息,返回給對應的客戶端。
對於第2種消息客戶端的其餘消息 好比聊天 好友這一類,只需簡單地從隊列裏拿走消息,轉發給後端的聊天服務隊列便可,其餘服務會把消息轉發出去。
對於第3種消息服務器端對客戶端消息的迴應。SET 只須要根據消息裏的用戶id,找到SET裏保留的用戶鏈接對象,發回去就能夠了。
對於紅包產生服務,它的工做很簡單,只須要按照順序在輪流在每一個SET的紅包產生對列裏放至紅包對象就能夠了。這樣能夠保證每一個SET裏都是公平的,其次它的工做強度很低,能夠保證業務穩定。
見代碼:
https://github.com/xiaojiaqi/10billionhongbaos
實踐的過程分爲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個用戶發出消息,客戶端會自動來拿紅包,最後全部的紅包都被拿走。
最後,實踐完成。
在實踐過程當中,服務器和客戶端都將本身內部的計數器記錄發往監控端,成爲了日誌。
咱們利用簡單python 腳本和gnuplt 繪圖工具,將實踐的過程可視化,由此來驗證運行過程。
第一張是客戶端的QPS發送數據:
這張圖的橫座標是時間,單位是秒,縱座標是QPS,表示這時刻全部客戶端發送的請求的QPS。
圖的第一區間,幾個小的峯值,是100萬客戶端創建鏈接的, 圖的第二區間是3萬QPS 區間,咱們能夠看到數據 比較穩定的保持在3萬這個區間。
最後是6萬QPS區間。可是從整張圖能夠看到QPS不是完美地保持在咱們但願的直線上。這主要是如下幾個緣由形成的
當很是多goroutine 同時運行的時候,依靠sleep 定時並不許確,發生了偏移。我以爲這是golang自己調度致使的。固然若是cpu比較強勁,這個現象會消失。
由於網絡的影響,客戶端在發起鏈接時,可能發生延遲,致使在前1秒沒有完成鏈接。
服務器負載較大時,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億次搖紅包請求。
雖然這個原型簡單地完成了預設的業務,可是它和真正的服務會有哪些差異呢?我羅列了一下
Refers:
《21天互聯網Java進階面試訓練營(分佈式篇)》詳細目錄,掃描圖片末尾的二維碼,試聽課程