整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          百億流量系統(tǒng),是如何從0開始搭建的?

          百億流量系統(tǒng),是如何從0開始搭建的?
          作者:xiaojiaqi
          來源:https://github.com/xiaojiaqi/10billionhongbaos
          

          . 前言

          前幾天,偶然看到了 《扛住100億次請(qǐng)求——如何做一個(gè)“有把握”的春晚紅包系統(tǒng)”》一文,看完以后,感慨良多,收益很多。正所謂他山之石,可以攻玉,雖然此文發(fā)表于2015年,我看到時(shí)已經(jīng)過去良久,但是其中的思想仍然是可以為很多后端設(shè)計(jì)借鑒。

          同時(shí)作為一微信后端工程師,看完以后又會(huì)思考,學(xué)習(xí)了這樣的文章以后,是否能給自己的工作帶來一些實(shí)際的經(jīng)驗(yàn)?zāi)兀克^紙上得來終覺淺,絕知此事要躬行,能否自己實(shí)踐一下100億次紅包請(qǐng)求呢?否則讀完以后腦子里能剩下的東西 不過就是100億 1400萬QPS整流 這樣的字眼,剩下的文章將展示作者是如何以此過程為目標(biāo),在本地環(huán)境的模擬了此過程。

          實(shí)現(xiàn)的目標(biāo):單機(jī)支持100萬連接,模擬了搖紅包和發(fā)紅包過程,單機(jī)峰值QPS 6萬,平穩(wěn)支持了業(yè)務(wù)。

          注:本文以及作者所有內(nèi)容,僅代表個(gè)人理解和實(shí)踐,過程和微信團(tuán)隊(duì)沒有任何關(guān)系,真正的線上系統(tǒng)也不同,只是從一些技術(shù)點(diǎn)進(jìn)行了實(shí)踐,請(qǐng)讀者進(jìn)行區(qū)分。


          2. 背景知識(shí)


          • QPS:Queries per second 每秒的請(qǐng)求數(shù)目
          • PPS:Packets per second 每秒數(shù)據(jù)包數(shù)目
          • 搖紅包:客戶端發(fā)出一個(gè)搖紅包的請(qǐng)求,如果系統(tǒng)有紅包就會(huì)返回,用戶獲得紅包
          • 發(fā)紅包:產(chǎn)生一個(gè)紅包里面含有一定金額,紅包指定數(shù)個(gè)用戶,每個(gè)用戶會(huì)收到紅包信息,用戶可以發(fā)送拆紅包的請(qǐng)求,獲取其中的部分金額。


          3. 確定目標(biāo)

          在一切系統(tǒng)開始以前,我們應(yīng)該搞清楚我們的系統(tǒng)在完成以后,應(yīng)該有一個(gè)什么樣的負(fù)載能力。

          3.1 用戶總數(shù)

          通過文章我們可以了解到接入服務(wù)器638臺(tái),服務(wù)上限大概是14.3億用戶, 所以單機(jī)負(fù)載的用戶上限大概是14.3億/638臺(tái)=228萬用戶/臺(tái)。但是目前中國肯定不會(huì)有14億用戶同時(shí)在線,參考 http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html的說法,2016年Q2 微信用戶大概是8億,月活在5.4 億左右。所以在2015年春節(jié)期間,雖然使用的用戶會(huì)很多,但是同時(shí)在線肯定不到5.4億。

          3.2. 服務(wù)器數(shù)量

          一共有638臺(tái)服務(wù)器,按照正常運(yùn)維設(shè)計(jì),我相信所有服務(wù)器不會(huì)完全上線,會(huì)有一定的硬件冗余,來防止突發(fā)硬件故障。假設(shè)一共有600臺(tái)接入服務(wù)器。

          3.3 單機(jī)需要支持的負(fù)載數(shù)

          每臺(tái)服務(wù)器支持的用戶數(shù):5.4億/600=90萬。也就是平均單機(jī)支持90萬用戶。如果真實(shí)情況比90萬更多,則模擬的情況可能會(huì)有偏差,但是我認(rèn)為QPS在這個(gè)實(shí)驗(yàn)中更重要。

          3.4. 單機(jī)峰值QPS

          文章中明確表示為1400萬QPS.這個(gè)數(shù)值是非常高的,但是因?yàn)橛?00臺(tái)服務(wù)器存在,所以機(jī)的QPS為 1400萬/600=約為2.3萬QPS, 文章曾經(jīng)提及系統(tǒng)可以支持4000萬QPS,那么系統(tǒng)的QPS 至少要到4000萬/600=約為 6.6萬, 這個(gè)數(shù)值大約是目前的3倍,短期來看并不會(huì)被觸及。但是我相信應(yīng)該做過相應(yīng)的壓力測(cè)試。

          3.5. 發(fā)放紅包

          文中提到系統(tǒng)以5萬個(gè)每秒的下發(fā)速度,那么單機(jī)每秒下發(fā)速度50000/600 =83個(gè)/秒,也就是單機(jī)系統(tǒng)應(yīng)該保證每秒以83個(gè)的速度下發(fā)即可。

          最后考慮到系統(tǒng)的真實(shí)性,還至少有用戶登錄的動(dòng)作,拿紅包這樣的業(yè)務(wù)。真實(shí)的系統(tǒng)還會(huì)包括聊天這樣的服務(wù)業(yè)務(wù)。

          最后整體的看一下 100億次搖紅包這個(gè)需求,假設(shè)它是均勻地發(fā)生在春節(jié)聯(lián)歡晚會(huì)的4個(gè)小時(shí)里,那么服務(wù)器的QPS 應(yīng)該是10000000000/600/3600/4.0=1157. 也就是單機(jī)每秒1000多次,這個(gè)數(shù)值其實(shí)并不高。如果完全由峰值速度1400萬消化 10000000000/(1400*10000)=714秒,也就是說只需要峰值堅(jiān)持11分鐘,就可以完成所有的請(qǐng)求。可見互聯(lián)網(wǎng)產(chǎn)品的一個(gè)特點(diǎn)就是峰值非常高,持續(xù)時(shí)間并不會(huì)很長。


          總結(jié)

          從單臺(tái)服務(wù)器看,它需要滿足下面一些條件:


          1. 支持至少100萬連接用戶
          2. 每秒至少能處理2.3萬的QPS,這里我們把目標(biāo)定得更高一些 分別設(shè)定到了3萬和6萬。
          3. 搖紅包:支持每秒83個(gè)的速度下發(fā)放紅包,也就是說每秒有2.3萬次搖紅包的請(qǐng)求,其中83個(gè)請(qǐng)求能搖到紅包,其余的2.29萬次請(qǐng)求會(huì)知道自己沒搖到。當(dāng)然客戶端在收到紅包以后,也需要確保客戶端和服務(wù)器兩邊的紅包數(shù)目和紅包內(nèi)的金額要一致。因?yàn)闆]有支付模塊,所以我們也把要求提高一倍,達(dá)到200個(gè)紅包每秒的分發(fā)速度
          4. 支持用戶之間發(fā)紅包業(yè)務(wù),確保收發(fā)兩邊的紅包數(shù)目和紅包內(nèi)金額要一致。同樣也設(shè)定200個(gè)紅包每秒的分發(fā)速度為我們的目標(biāo)。


          想完整模擬整個(gè)系統(tǒng)實(shí)在太難了,首先需要海量的服務(wù)器,其次需要上億的模擬客戶端。這對(duì)我來說是辦不到,但是有一點(diǎn)可以確定,整個(gè)系統(tǒng)是可以水平擴(kuò)展的,所以我們可以模擬100萬客戶端,在模擬一臺(tái)服務(wù)器 那么就完成了1/600的模擬。

          和現(xiàn)有系統(tǒng)區(qū)別:和大部分高QPS測(cè)試的不同,本系統(tǒng)的側(cè)重點(diǎn)有所不同。我對(duì)2者做了一些對(duì)比。


          4. 基礎(chǔ)軟件和硬件


          4.1軟件

          Golang 1.8r3 , shell, python (開發(fā)沒有使用c++ 而是使用了golang, 是因?yàn)槭褂胓olang 的最初原型達(dá)到了系統(tǒng)要求。雖然golang 還存在一定的問題,但是和開發(fā)效率比,這點(diǎn)損失可以接受)

          服務(wù)器操作系統(tǒng):Ubuntu 12.04

          客戶端操作系統(tǒng):debian 5.0


          4.2硬件環(huán)境

          服務(wù)端:dell R2950。8核物理機(jī),非獨(dú)占有其他業(yè)務(wù)在工作,16G內(nèi)存。這臺(tái)硬件大概是7年前的產(chǎn)品,性能應(yīng)該不是很高要求。

          服務(wù)器硬件版本:


          服務(wù)器CPU信息:

          客戶端:esxi 5.0 虛擬機(jī),配置為4核 5G內(nèi)存。一共17臺(tái),每臺(tái)和服務(wù)器建立6萬個(gè)連接。完成100萬客戶端模擬


          5. 技術(shù)分析和實(shí)現(xiàn)

          5.1) 單機(jī)實(shí)現(xiàn)100萬用戶連接

          這一點(diǎn)來說相對(duì)簡單,筆者在幾年前就早完成了單機(jī)百萬用戶的開發(fā)以及操作。現(xiàn)代的服務(wù)器都可以支持百萬用戶。相關(guān)內(nèi)容可以查看:

          github代碼以及相關(guān)文檔:

          https://github.com/xiaojiaqi/C1000kPracticeGuide

          系統(tǒng)配置以及優(yōu)化文檔:

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

          5.2) 3萬QPS

          這個(gè)問題需要分2個(gè)部分來看客戶端方面和服務(wù)器方面。


          • 客戶端QPS


          因?yàn)橛?00萬連接連在服務(wù)器上,QPS為3萬。這就意味著每個(gè)連接每33秒,就需要向服務(wù)器發(fā)一個(gè)搖紅包的請(qǐng)求。因?yàn)閱蜪P可以建立的連接數(shù)為6萬左右, 有17臺(tái)服務(wù)器同時(shí)模擬客戶端行為。我們要做的就保證在每一秒都有這么多的請(qǐng)求發(fā)往服務(wù)器即可。

          其中技術(shù)要點(diǎn)就是客戶端協(xié)同。但是各個(gè)客戶端的啟動(dòng)時(shí)間,建立連接的時(shí)間都不一致,還存在網(wǎng)絡(luò)斷開重連這樣的情況,各個(gè)客戶端如何判斷何時(shí)自己需要發(fā)送請(qǐng)求,各自該發(fā)送多少請(qǐng)求呢?

          我是這樣解決的:利用NTP服務(wù),同步所有的服務(wù)器時(shí)間,客戶端利用時(shí)間戳來判斷自己的此時(shí)需要發(fā)送多少請(qǐng)求。

          算法很容易實(shí)現(xiàn):假設(shè)有100萬用戶,則用戶id 為0-999999.要求的QPS為5萬, 客戶端得知QPS為5萬,總用戶數(shù)為100萬,它計(jì)算 100萬/5萬=20,所有的用戶應(yīng)該分為20組,如果 time() % 20==用戶id % 20,那么這個(gè)id的用戶就該在這一秒發(fā)出請(qǐng)求,如此實(shí)現(xiàn)了多客戶端協(xié)同工作。每個(gè)客戶端只需要知道 總用戶數(shù)和QPS 就能自行準(zhǔn)確發(fā)出請(qǐng)求了。

          (擴(kuò)展思考:如果QPS是3萬 這樣不能被整除的數(shù)目,該如何辦?如何保證每臺(tái)客戶端發(fā)出的請(qǐng)求數(shù)目盡量的均衡呢?)


          • 服務(wù)器QPS


          服務(wù)器端的QPS相對(duì)簡單,它只需要處理客戶端的請(qǐng)求即可。但是為了客觀了解處理情況,我們還需要做2件事情。


          • 第一:需要記錄每秒處理的請(qǐng)求數(shù)目,這需要在代碼里埋入計(jì)數(shù)器。
          • 第二:我們需要監(jiān)控網(wǎng)絡(luò),因?yàn)榫W(wǎng)絡(luò)的吞吐情況,可以客觀的反映出QPS的真實(shí)數(shù)據(jù)。為此,我利用python腳本 結(jié)合ethtool 工具編寫了一個(gè)簡單的工具,通過它我們可以直觀的監(jiān)視到網(wǎng)絡(luò)的數(shù)據(jù)包通過情況如何。它可以客觀的顯示出我們的網(wǎng)絡(luò)有如此多的數(shù)據(jù)傳輸在發(fā)生。


          工具截圖:


          5.3) 搖紅包業(yè)務(wù)

          搖紅包的業(yè)務(wù)非常簡單,首先服務(wù)器按照一定的速度生產(chǎn)紅包。紅包沒有被取走的話,就堆積在里面。服務(wù)器接收一個(gè)客戶端的請(qǐng)求,如果服務(wù)器里現(xiàn)在有紅包就會(huì)告訴客戶端有,否則就提示沒有紅包。

          因?yàn)閱螜C(jī)每秒有3萬的請(qǐng)求,所以大部分的請(qǐng)求會(huì)失敗。只需要處理好鎖的問題即可。

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

          注意,在我的測(cè)試環(huán)境里是缺少支付這個(gè)核心服務(wù)的,所以實(shí)現(xiàn)的難度是大大的減輕了。另外提供一組數(shù)字:2016年淘寶的雙11的交易峰值僅僅為12萬/秒,微信紅包分發(fā)速度是5萬/秒,要做到這點(diǎn)是非常困難的。(http://mt.sohu.com/20161111/n472951708.shtml)


          5.4) 發(fā)紅包業(yè)務(wù)

          發(fā)紅包的業(yè)務(wù)很簡單,系統(tǒng)隨機(jī)產(chǎn)生一些紅包,并且隨機(jī)選擇一些用戶,系統(tǒng)向這些用戶提示有紅包。這些用戶只需要發(fā)出拆紅包的請(qǐng)求,系統(tǒng)就可以隨機(jī)從紅包中拆分出部分金額,分給用戶,完成這個(gè)業(yè)務(wù)。同樣這里也沒有支付這個(gè)核心服務(wù)。


          5.5)監(jiān)控

          最后 我們需要一套監(jiān)控系統(tǒng)來了解系統(tǒng)的狀況,我借用了我另一個(gè)項(xiàng)目(https://github.com/xiaojiaqi/fakewechat) 里的部分代碼完成了這個(gè)監(jiān)控模塊,利用這個(gè)監(jiān)控,服務(wù)器和客戶端會(huì)把當(dāng)前的計(jì)數(shù)器內(nèi)容發(fā)往監(jiān)控,監(jiān)控需要把各個(gè)客戶端的數(shù)據(jù)做一個(gè)整合和展示。同時(shí)還會(huì)把日志記錄下來,給以后的分析提供原始數(shù)據(jù)。線上系統(tǒng)更多使用opentsdb這樣的時(shí)序數(shù)據(jù)庫,這里資源有限,所以用了一個(gè)原始的方案。

          監(jiān)控顯示日志大概這樣:


          6. 代碼實(shí)現(xiàn)及分析

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

          首先golang的goroutine 的數(shù)目控制,因?yàn)橹辽儆?00萬以上的連接,所以按照普通的設(shè)計(jì)方案,至少需要200萬或者300萬的goroutine在工作。這會(huì)造成系統(tǒng)本身的負(fù)擔(dān)很重。

          其次就是100萬個(gè)連接的管理,無論是連接還是業(yè)務(wù)都會(huì)造成一些心智的負(fù)擔(dān)。

          我的設(shè)計(jì)是這樣的:

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

          其次謹(jǐn)慎的設(shè)計(jì)了每個(gè)SET里數(shù)據(jù)結(jié)構(gòu)的大小,保證每個(gè)SET的壓力不會(huì)太大,不會(huì)出現(xiàn)消息的堆積。

          再次減少了gcroutine的數(shù)目,每個(gè)連接只使用一個(gè)goroutine,發(fā)送消息在一個(gè)SET里只有一個(gè)gcroutine負(fù)責(zé),這樣節(jié)省了100萬個(gè)goroutine。這樣整個(gè)系統(tǒng)只需要保留 100萬零幾百個(gè)gcroutine就能完成業(yè)務(wù)。大量的節(jié)省了cpu 和內(nèi)存

          系統(tǒng)的工作流程大概是:每個(gè)客戶端連接成功后,系統(tǒng)會(huì)分配一個(gè)goroutine讀取客戶端的消息,當(dāng)消息讀取完成,將它轉(zhuǎn)化為消息對(duì)象放至在SET的接收消息隊(duì)列,然后返回獲取下一個(gè)消息。

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


          1. 客戶端的搖紅包請(qǐng)求消息
          2. 客戶端的其他消息 比如聊天 好友這一類
          3. 服務(wù)器端對(duì)客戶端消息的回應(yīng)


          對(duì)于第1種消息客戶端的搖紅包請(qǐng)求消息 是這樣處理的,從客戶端拿到搖紅包請(qǐng)求消息,試圖從SET的紅包隊(duì)列里 獲取一個(gè)紅包,如果拿到了就把紅包信息 返回給客戶端,否則構(gòu)造一個(gè)沒有搖到的消息,返回給對(duì)應(yīng)的客戶端。

          對(duì)于第2種消息客戶端的其他消息 比如聊天 好友這一類,只需簡單地從隊(duì)列里拿走消息,轉(zhuǎn)發(fā)給后端的聊天服務(wù)隊(duì)列即可,其他服務(wù)會(huì)把消息轉(zhuǎn)發(fā)出去。

          對(duì)于第3種消息服務(wù)器端對(duì)客戶端消息的回應(yīng)。SET 只需要根據(jù)消息里的用戶id,找到SET里保留的用戶連接對(duì)象,發(fā)回去就可以了。

          對(duì)于紅包產(chǎn)生服務(wù),它的工作很簡單,只需要按照順序在輪流在每個(gè)SET的紅包產(chǎn)生對(duì)列里放至紅包對(duì)象就可以了。這樣可以保證每個(gè)SET里都是公平的,其次它的工作強(qiáng)度很低,可以保證業(yè)務(wù)穩(wěn)定。

          見代碼:

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


          7. 實(shí)踐

          實(shí)踐的過程分為3個(gè)階段

          階段1

          分別啟動(dòng)服務(wù)器端和監(jiān)控端,然后逐一啟動(dòng)17臺(tái)客戶端,讓它們建立起100萬的鏈接。在服務(wù)器端,利用ss 命令 統(tǒng)計(jì)出每個(gè)客戶端和服務(wù)器建立了多少連接。

          命令如下:


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

          結(jié)果如下:


          階段2

          利用客戶端的http接口,將所有的客戶端QPS 調(diào)整到3萬,讓客戶端發(fā)出3W QPS強(qiáng)度的請(qǐng)求。

          運(yùn)行如下命令:

          觀察網(wǎng)絡(luò)監(jiān)控和監(jiān)控端反饋,發(fā)現(xiàn)QPS 達(dá)到預(yù)期數(shù)據(jù),網(wǎng)絡(luò)監(jiān)控截圖:

          在服務(wù)器端啟動(dòng)一個(gè)產(chǎn)生紅包的服務(wù),這個(gè)服務(wù)會(huì)以200個(gè)每秒的速度下發(fā)紅包,總共4萬個(gè)。此時(shí)觀察客戶端在監(jiān)控上的日志,會(huì)發(fā)現(xiàn)基本上以200個(gè)每秒的速度獲取到紅包。

          等到所有紅包下發(fā)完成后,再啟動(dòng)一個(gè)發(fā)紅包的服務(wù),這個(gè)服務(wù)系統(tǒng)會(huì)生成2萬個(gè)紅包,每秒也是200個(gè),每個(gè)紅包隨機(jī)指定3位用戶,并向這3個(gè)用戶發(fā)出消息,客戶端會(huì)自動(dòng)來拿紅包,最后所有的紅包都被拿走。


          階段3

          利用客戶端的http接口,將所有的客戶端QPS 調(diào)整到6萬,讓客戶端發(fā)出6W QPS強(qiáng)度的請(qǐng)求。

          如法炮制,在服務(wù)器端,啟動(dòng)一個(gè)產(chǎn)生紅包的服務(wù),這個(gè)服務(wù)會(huì)以200個(gè)每秒的速度下發(fā)紅包。總共4萬個(gè)。此時(shí)觀察客戶端在監(jiān)控上的日志,會(huì)發(fā)現(xiàn)基本上以200個(gè)每秒的速度獲取到紅包。

          等到所有紅包下發(fā)完成后,再啟動(dòng)一個(gè)發(fā)紅包的服務(wù),這個(gè)服務(wù)系統(tǒng)會(huì)生成2萬個(gè)紅包,每秒也是200個(gè),每個(gè)紅包隨機(jī)指定3位用戶,并向這3個(gè)用戶發(fā)出消息,客戶端會(huì)自動(dòng)來拿紅包,最后所有的紅包都被拿走。

          最后,實(shí)踐完成。

          8. 分析數(shù)據(jù)

          在實(shí)踐過程中,服務(wù)器和客戶端都將自己內(nèi)部的計(jì)數(shù)器記錄發(fā)往監(jiān)控端,成為了日志。我們利用簡單python 腳本和gnuplt 繪圖工具,將實(shí)踐的過程可視化,由此來驗(yàn)證運(yùn)行過程。

          第一張是客戶端的QPS發(fā)送數(shù)據(jù):

          這張圖的橫坐標(biāo)是時(shí)間,單位是秒,縱坐標(biāo)是QPS,表示這時(shí)刻所有客戶端發(fā)送的請(qǐng)求的QPS。

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


          1. 當(dāng)非常多goroutine 同時(shí)運(yùn)行的時(shí)候,依靠sleep 定時(shí)并不準(zhǔn)確,發(fā)生了偏移。我覺得這是golang本身調(diào)度導(dǎo)致的。當(dāng)然如果cpu比較強(qiáng)勁,這個(gè)現(xiàn)象會(huì)消失。
          2. 因?yàn)榫W(wǎng)絡(luò)的影響,客戶端在發(fā)起連接時(shí),可能發(fā)生延遲,導(dǎo)致在前1秒沒有完成連接。
          3. 服務(wù)器負(fù)載較大時(shí),1000M網(wǎng)絡(luò)已經(jīng)出現(xiàn)了丟包現(xiàn)象,可以通過ifconfig 命令觀察到這個(gè)現(xiàn)象,所以會(huì)有QPS的波動(dòng)。


          第二張是 服務(wù)器處理的QPS圖:

          和客戶端的向?qū)?yīng)的,服務(wù)器也存在3個(gè)區(qū)間,和客戶端的情況很接近。但是我們看到了在大概22:57分,系統(tǒng)的處理能力就有一個(gè)明顯的下降,隨后又提高的尖狀。這說明代碼還需要優(yōu)化。

          整體觀察在3萬QPS區(qū)間,服務(wù)器的QPS比較穩(wěn)定,在6萬QSP時(shí)候,服務(wù)器的處理就不穩(wěn)定了。我相信這和我的代碼有關(guān),如果繼續(xù)優(yōu)化的話,還應(yīng)該能有更好的效果。

          將2張圖合并起來 :

          基本是吻合的,這也證明系統(tǒng)是符合預(yù)期設(shè)計(jì)的。

          這是紅包生成數(shù)量的狀態(tài)變化圖:

          非常的穩(wěn)定。

          這是客戶端每秒獲取的搖紅包狀態(tài):

          可以發(fā)現(xiàn)3萬QPS區(qū)間,客戶端每秒獲取的紅包數(shù)基本在200左右,在6萬QPS的時(shí)候,以及出現(xiàn)劇烈的抖動(dòng),不能保證在200這個(gè)數(shù)值了。我覺得主要是6萬QPS時(shí)候,網(wǎng)絡(luò)的抖動(dòng)加劇了,造成了紅包數(shù)目也在抖動(dòng)。

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


          總結(jié)

          按照設(shè)計(jì)目標(biāo),我們模擬和設(shè)計(jì)了一個(gè)支持100萬用戶,并且每秒至少可以支持3萬QPS,最多6萬QPS的系統(tǒng),簡單模擬了微信的搖紅包和發(fā)紅包的過程。可以說達(dá)到了預(yù)期的目的。

          如果600臺(tái)主機(jī)每臺(tái)主機(jī)可以支持6萬QPS,只需要7分鐘就可以完成 100億次搖紅包請(qǐng)求。

          雖然這個(gè)原型簡單地完成了預(yù)設(shè)的業(yè)務(wù),但是它和真正的服務(wù)會(huì)有哪些差別呢?我羅列了一下


          Refers:


          • 單機(jī)百萬的實(shí)踐
          • https://github.com/xiaojiaqi/C1000kPracticeGuide
          • 如何在AWS上進(jìn)行100萬用戶壓力測(cè)試
          • https://github.com/xiaojiaqi/fakewechat/wiki/Stress-Testing-in-the-Cloud
          • 構(gòu)建一個(gè)你自己的類微信系統(tǒng)
          • 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

          項(xiàng)目地址:https://github.com/xiaojiaqi/10billionhongbaos

          時(shí)音視頻的開發(fā)學(xué)習(xí)有很多可以參考的開源項(xiàng)目。

          音視頻流媒體在現(xiàn)在的生活中已經(jīng)無處不在,擁有一大批頂級(jí)的音頻/視頻工具確實(shí)派得上用場(chǎng)。修剪文件、編輯視頻、最大化音頻――我們需要滿足社交媒體流的傳播需求,而公司總是需要音頻/視頻內(nèi)容,以便與用戶進(jìn)行最有效的溝通。

          一個(gè)實(shí)時(shí)音視頻應(yīng)用共包括幾個(gè)環(huán)節(jié):采集、編碼、前后處理、傳輸、解碼、緩沖、渲染等很多環(huán)節(jié)。每一個(gè)細(xì)分環(huán)節(jié),還有更細(xì)分的技術(shù)模塊。比如,前后處理環(huán)節(jié)有美顏、濾鏡、回聲消除、噪聲抑制等,采集有麥克風(fēng)陣列等,編解碼有VP8、VP9、H.264、H.265等。

          我們今天匯總了一些能幫助到正在學(xué)習(xí)或進(jìn)行音視頻開發(fā)的實(shí)時(shí)音視頻開發(fā)者們的開源項(xiàng)目與幾個(gè)也在為開源社區(qū)貢獻(xiàn)力量的商業(yè)服務(wù)。這些項(xiàng)目分為幾類:音視頻編解碼類、視頻前后處理、服務(wù)端類等。

          音視頻編解碼類開源項(xiàng)目

          視頻編解碼的作用,就是在設(shè)備的攝像頭采集畫面和前處理后,將圖像進(jìn)行壓縮,進(jìn)行數(shù)字編碼,用于傳輸。編解碼器的優(yōu)劣基本在于:壓縮效率的高低,速度和功耗。

          目前,主流的視頻編碼器分為3個(gè)系列:VPx(VP8,VP9),H.26x(H.264,H.265),AVS(AVS1.0,AVS2.0)。VPx系列是由Google開源的視頻編解碼標(biāo)準(zhǔn)。在保證相同質(zhì)量情況下,VP9相比VP8碼率減少約50%。H.26x系列在硬件支持上比較廣泛,H.265的編碼效率能比上一代提高了30-50%,但是復(fù)雜度和功耗會(huì)比上一代大很多,所以純軟件編碼實(shí)現(xiàn)的話有一定瓶頸,現(xiàn)有的技術(shù)下,還是需要依靠硬件編解碼為主。AVS是我國具備自主知識(shí)產(chǎn)權(quán)的第二代信源編碼標(biāo)準(zhǔn),目前已經(jīng)發(fā)展到第二代。

          WebRTC

          首先會(huì)用到的肯定是WebRTC,是一個(gè)支持網(wǎng)頁瀏覽器進(jìn)行實(shí)時(shí)語音對(duì)話或視頻對(duì)話的開源項(xiàng)目。它提供了包括音視頻的采集、編解碼、網(wǎng)絡(luò)傳輸、顯示等功能。如果你想基于WebRTC開發(fā)實(shí)時(shí)音視頻應(yīng)用,需要注意,由于WebRTC缺少服務(wù)端設(shè)計(jì)和部署方案,你還需要將WebRTC與Janus等服務(wù)端類開源項(xiàng)目結(jié)合即可。

          官網(wǎng)地址: ?webrtc.org/?

          x264

          H.264是目前應(yīng)用最廣的碼流標(biāo)準(zhǔn)。x264則是能夠產(chǎn)生符合H.264標(biāo)準(zhǔn)的碼流的編碼器,它可以將視頻流編碼為H.264、MPEG-4 AVC格式。它提供了命令行接口與API,前者被用于一些圖形用戶接口例如Straxrip、MeGUI,后者則被FFmpeg、Handbrake等調(diào)用。當(dāng)然,既然有x264,就有對(duì)應(yīng)HEVC/H.265的x265。

          官網(wǎng)地址:? ?https://www.videolan.org/developers/x264.html?

          FFmpeg

          FFmpeg大家應(yīng)該不陌生,提供了編碼、解碼、轉(zhuǎn)換、封裝等功能,以及剪裁、縮放、色域等后期處理,支持幾乎目前所有音視頻編碼標(biāo)準(zhǔn)(由于格式眾多,我們就不一一列列舉了,可以在Wikipedia中找到)。

          同時(shí),F(xiàn)Fmpeg還衍生出了libav項(xiàng)目,從中誕生了視頻解碼器LAV,許多播放軟件都可調(diào)用LAV進(jìn)行解碼,并且LAV本身也支持利用顯卡進(jìn)行視頻硬解。很多主流視頻播放器中都以FFmpeg作為內(nèi)核播放器。不僅僅是視頻播放器,就連Chrome這類可以播放網(wǎng)頁視頻的瀏覽器也受益于FFmpeg。很多開發(fā)者也基于FFmpeg做過很多開發(fā)并開源出來,比如大神雷霄驊(代碼可見他的sourceforge)。

          官網(wǎng)地址:? ?ffmpeg.org/?

          ijkplayer

          在介紹ijkplayer之前,要先提到ffplay。ffplay是一個(gè)使用了FFmpeg和sdl庫的可移植的媒體播放器。ijkplay是Bilibili開源的基于ffplay.c實(shí)現(xiàn)的輕量級(jí)iOS/Android視頻播放器,API易于集成,且編譯配置可裁剪,利于控制安裝包大小。

          在編解碼方面,ijkplayer支持視頻軟解和硬解,可以在播放前配置,但在播放過程中則不能切換。iOS和Android上視頻硬解可分別使用大家熟悉的VideoToolbox和MediaCodec。但ijkplayer對(duì)音頻僅支持軟解。

          Github地址:? ?https://github.com/Bilibili/ijkplayer?

          JSMpeg

          JSMpeg是一個(gè)基于JavaScript的MPEG1視頻的解碼器。如果要做H5端的視頻直播,可以考慮使用JSMpeg在移動(dòng)端進(jìn)行解碼。在H5端做音視頻直播,可以使用JSMpeg進(jìn)行視頻解碼,這也是最近比較火的H5抓娃娃的主流策略。

          Github地址:? ?https://github.com/phoboslab/jsmpeg?

          Opus

          Opus是用C語言開發(fā)的一個(gè)高靈活度的音頻編碼器,針對(duì)ARM、x86有特殊優(yōu)化,fix-point實(shí)現(xiàn)。Opus在各方面都有著明顯優(yōu)勢(shì)。它同時(shí)支持語音與音樂的編碼,比特率為6k-510k。它融合了SILK編碼方法和CELT編碼方法。SILK原本被用于Skype中,基于語音信號(hào)的線性預(yù)測(cè)分析(LPC),對(duì)音樂支持并不好。而CELT盡管適用于全帶寬音頻,但對(duì)低比特率語音的編碼效率不高,所以兩者在Opus中形成了互補(bǔ)。

          Opus是“取代”了Speex。但是Speex中有的功能,Opus卻沒有,比如回聲消除。這個(gè)功能已經(jīng)從編碼器中獨(dú)立出來。所以如果想實(shí)現(xiàn)好的回聲消除,可以配合WebRTC的AEC和AECM模塊做二次開發(fā)。

          官網(wǎng)地址:? ?opus-codec.org/?

          live555

          live555是一個(gè)C++流媒體開源項(xiàng)目,其中不僅包括了傳輸協(xié)議(SIP、RTP)、音視頻編碼器(H.264、MPEG4)等,還包括流媒體服務(wù)器的例子,是流媒體項(xiàng)目的首選,里面的傳輸模塊是非常值得視頻會(huì)議開發(fā)作為參考的。

          官網(wǎng)地址:? ?www.live555.com/?

          本文福利, 免費(fèi)領(lǐng)取C++音視頻學(xué)習(xí)資料包+學(xué)習(xí)路線大綱、技術(shù)視頻/代碼,內(nèi)容包括(音視頻開發(fā),面試題,F(xiàn)Fmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,編解碼,推拉流,srs),有需要的可以進(jìn)企鵝裙927239107領(lǐng)取哦~

          音視頻前后處理開源項(xiàng)目

          前后處理包含很多細(xì)分技術(shù),應(yīng)用正確的話,對(duì)視頻質(zhì)量或多或少都有提升。不過每增加一個(gè)處理環(huán)節(jié),必然會(huì)增加運(yùn)算量與延時(shí),所以如何取舍,還要大家各自斟酌。

          Seetaface

          Seetaface是由中科院山世光老師開源的一套完整的人臉檢測(cè),人臉對(duì)齊和人臉驗(yàn)證方案。代碼基于C++實(shí)現(xiàn),開源協(xié)議為BSD-2,可供學(xué)術(shù)界和工業(yè)界免費(fèi)使用。且不依賴于任何第三方的庫函數(shù),在使用對(duì)齊好的LFW圖片上,檢測(cè)對(duì)齊全部使用該開源軟件的情況下可達(dá)到97.1%。

          Github地址:? ?https://github.com/seetaface/SeetaFaceEngine?

          GPUImage

          現(xiàn)在在iOS端做美顏效果、加水印,基本都會(huì)采用GPUImage,它內(nèi)置了125種渲染效果, 還支持腳本自定義。該項(xiàng)目實(shí)現(xiàn)了圖片濾鏡、攝像頭實(shí)時(shí)濾鏡。它優(yōu)勢(shì)在于處理效果是基于GPU實(shí)現(xiàn),相對(duì)于CPU處理性能更高。

          Github地址:? ?https://github.com/BradLarson/GPUImage?

          Open nsfw model

          Open nsfw model是雅虎開源項(xiàng)目,全名是Open Not suitable for work model,專門鑒別不適合工作時(shí)間瀏覽的圖片(言而言之就是小黃圖)。它是基于Caffe框架訓(xùn)練的模型,用于音視頻后處理。不過,它還不能鑒別恐怖、血腥圖片。

          Github地址:? ?https://github.com/yahoo/open_nsfw??

          Soundtouch

          Soundtouch是一個(gè)開源的音頻處理框架,主要功能對(duì)音頻變速、變調(diào),實(shí)現(xiàn)變聲的效果。同時(shí),它也能對(duì)媒體流實(shí)時(shí)處理。采用32位浮點(diǎn)或者16位定點(diǎn),支持單聲道或者雙聲道,采樣率范圍為8k - 48k。

          官網(wǎng)地址:? ?www.surina.net/soundtouch/??

          服務(wù)端類開源項(xiàng)目

          正如開始時(shí)我們所說,WebRTC缺少服務(wù)端的設(shè)計(jì)與部署,利用MCU、SFU實(shí)現(xiàn)多人聊天,提高傳輸質(zhì)量,都需要開發(fā)者自己動(dòng)手。而下面這些開源項(xiàng)目能夠幫到你。

          Jitsi

          Jitsi是開源的視頻會(huì)議系統(tǒng),可以實(shí)現(xiàn)在線視頻會(huì)議,文檔共享和即時(shí)消息的分享。它支持網(wǎng)絡(luò)視頻會(huì)議,使用SFU模式實(shí)現(xiàn)視頻路由器功能。開發(fā)語言是Java。它支持SIP帳號(hào)注冊(cè)電話呼叫。不僅支持單機(jī)本地安裝方式,還支持云平臺(tái)安裝。

          官網(wǎng)地址:? ?jitsi.org/??

          JsSIP

          JsSIP是基于WebRTC的JavaScript SIP協(xié)議實(shí)現(xiàn)的庫,可以在瀏覽器和Node.js中運(yùn)行。它可以與 OverSIP、Kamailio、Asterisk、OfficeSIP等SIP Server一起運(yùn)行。

          Github地址:?https://github.com/versatica/JsSIP

          SRS

          SRS是一個(gè)采用MIT協(xié)議授權(quán)的國產(chǎn)的簡單的RTMP/HLS 直播服務(wù)器。最新版還支持FLV模式,同時(shí)具備了RTMP的實(shí)時(shí)性,以及HLS中屬于HTTP協(xié)議對(duì)各種網(wǎng)絡(luò)環(huán)境高度適應(yīng)性,并且支持更多播放器。它的功能與nginx-rtmp-module類似, 可以實(shí)現(xiàn)RTMP/HLS的分發(fā)。

          Github地址:? ?https://github.com/ossrs/srs

          JRTPLIB

          JRTPLIB 是一個(gè)開源的 RTP協(xié)議實(shí)現(xiàn)庫,支持Windows和unix平臺(tái)。它支持多線程,處理性能較好。它還支持RFC3550、UDP IPV6,支持自定義擴(kuò)展傳輸協(xié)議。但它不支持TCP傳輸,這需要開發(fā)者自己來實(shí)現(xiàn)。同時(shí),它也不支持音視頻的分包,代碼要你自己來實(shí)現(xiàn)。

          Github地址:? ?https://github.com/j0r1/JRTPLIB

          OPAL

          OPAL是OpenH323的下一個(gè)版本,繼承了Openh323協(xié)議,其新包含了SIP協(xié)議棧,是實(shí)現(xiàn)SIP協(xié)議的首選,缺點(diǎn)是參考例子較少。

          代碼地址:? ?https://link.zhihu.com/?target=http://sourceforge.net/projects/opalvoip/files/

          Kurento

          Kurento是一個(gè)基于WebRTC的媒體服務(wù)端,并包含了一系列API,可以簡化web與移動(dòng)端實(shí)時(shí)視頻應(yīng)用的開發(fā)。

          Github地址:? ?https://github.com/Kurento

          Janus

          Janus是一個(gè)WebRTC媒體網(wǎng)關(guān)。不論是做流媒體、視頻會(huì)議、錄制、網(wǎng)關(guān),都可以基于Janus來實(shí)現(xiàn)。

          Github地址:github.com/Kurento

          ? ?Callstats.io??

          實(shí)時(shí)通信過程中的,延時(shí)、丟包、接通率、掉線率等質(zhì)量問題,都影響用戶體驗(yàn)。商用項(xiàng)目尤其需要關(guān)注。Callstats是一家通過對(duì)WebRTC呼叫進(jìn)行專業(yè)監(jiān)測(cè),來幫助用戶搜集通訊數(shù)據(jù),提升通話質(zhì)量的服務(wù)商。

          Callstats也通過Github開放很多案例,可供使用Jitsi-videobridge,、turn-server、JsSIP的開發(fā)者參考。

          Github地址:?https://github.com/callstats-io

          Meetecho

          Meetecho是著名的開源WebRTC網(wǎng)關(guān)項(xiàng)目Janus的開發(fā)者。他們還提供基于Janus開發(fā)的技術(shù)咨詢與部署服務(wù)、建立視頻會(huì)議直播與錄制服務(wù)等。

          Github地址:? ?https://github.com/carlhuda/janus

          聲網(wǎng)Agora

          聲網(wǎng)提供了從編解碼到端到端傳輸?shù)娜追?wù),開發(fā)者可以接入上文所述的音視頻前后處理的開源項(xiàng)目,配合使用聲網(wǎng)SDK可以建立高質(zhì)量的實(shí)時(shí)音視頻應(yīng)用。在Web端,Agora Web SDK可以幫助WebRTC開發(fā)者解決服務(wù)端傳輸中會(huì)遇到的卡頓、延時(shí)、回聲、多人視頻不穩(wěn)定等問題。同時(shí),聲網(wǎng)SDK還對(duì)多個(gè)系統(tǒng)平臺(tái)的應(yīng)用提供實(shí)時(shí)音視頻通訊服務(wù)。

          聲網(wǎng)在Github上有許多可供開發(fā)者參考、實(shí)踐的demo源碼,覆蓋了從網(wǎng)頁端、iOS到Android平臺(tái),以及音視頻直播、游戲連麥、企業(yè)會(huì)議、AR、直播答題、小程序等多種實(shí)時(shí)互動(dòng)應(yīng)用場(chǎng)景。

          Github地址:? ?https://github.com/AgoraIO-Community

          我們?cè)谶@里列出了18個(gè)開源項(xiàng)目,以及3個(gè)能有效保證實(shí)時(shí)音視頻傳輸質(zhì)量的服務(wù)。不過篇幅有限,還有很多開源項(xiàng)目我們沒有詳細(xì)列出,比如在音視頻方面,http://Xiph.org的Speex、FLAC,還有Xvid、libvpx、Lagarith、Daala、Thor等。歡迎大家繼續(xù)補(bǔ)充。

          片來自包圖網(wǎng)

          知乎存儲(chǔ)平臺(tái)團(tuán)隊(duì)基于開源 Redis 組件打造的 Redis 平臺(tái)管理系統(tǒng),經(jīng)過不斷的研發(fā)迭代,目前已經(jīng)形成了一整套完整自動(dòng)化運(yùn)維服務(wù)體系,提供一鍵部署集群,一鍵自動(dòng)擴(kuò)縮容,Redis 超細(xì)粒度監(jiān)控,旁路流量分析等輔助功能。

          目前,Redis 在知乎的規(guī)模如下:

          • 機(jī)器內(nèi)存總量約 70TB,實(shí)際使用內(nèi)存約 40TB。
          • 平均每秒處理約 1500 萬次請(qǐng)求,峰值每秒約 2000 萬次請(qǐng)求。
          • 每天處理約 1 萬億余次請(qǐng)求。
          • 單集群每秒處理最高每秒約 400 萬次請(qǐng)求。
          • 集群實(shí)例與單機(jī)實(shí)例總共約 800 個(gè)。
          • 實(shí)際運(yùn)行約 16000 個(gè) Redis 實(shí)例。
          • Redis 使用官方 3.0.7 版本,少部分實(shí)例采用 4.0.11 版本。

          知乎 Redis 平臺(tái)演進(jìn)歷程

          根據(jù)業(yè)務(wù)的需求,我們將實(shí)例區(qū)分為如下兩種類型:

          • 單機(jī)(Standalone),單機(jī)實(shí)例通常用于容量與性能要求不高的小型存儲(chǔ)。
          • 集群(Cluster),集群則用來應(yīng)對(duì)對(duì)性能和容量要求較高的場(chǎng)景。

          單機(jī)(Standalone)

          對(duì)于單機(jī)實(shí)例,我們采用原生主從(Master-Slave)模式實(shí)現(xiàn)高可用,常規(guī)模式下對(duì)外僅暴露 Master 節(jié)點(diǎn)。由于使用原生 Redis,所以單機(jī)實(shí)例支持所有 Redis 指令。

          對(duì)于單機(jī)實(shí)例,我們使用 Redis 自帶的哨兵(Sentinel)集群對(duì)實(shí)例進(jìn)行狀態(tài)監(jiān)控與 Failover。

          Sentinel 是 Redis 自帶的高可用組件,將 Redis 注冊(cè)到由多個(gè) Sentinel 組成的 Sentinel 集群后,Sentinel 會(huì)對(duì) Redis 實(shí)例進(jìn)行健康檢查。

          當(dāng) Redis 發(fā)生故障后,Sentinel 會(huì)通過 Gossip 協(xié)議進(jìn)行故障檢測(cè),確認(rèn)宕機(jī)后會(huì)通過一個(gè)簡化的 Raft 協(xié)議來提升 Slave 成為新的 Master。

          通常情況我們僅使用 1 個(gè) Slave 節(jié)點(diǎn)進(jìn)行冷備,如果有讀寫分離請(qǐng)求,可以建立多個(gè) Read only slave 來進(jìn)行讀寫分離。



          如上圖所示,通過向 Sentinel 集群注冊(cè) Master 節(jié)點(diǎn)實(shí)現(xiàn)實(shí)例的高可用,當(dāng)提交 Master 實(shí)例的連接信息后,Sentinel 會(huì)主動(dòng)探測(cè)所有的 Slave 實(shí)例并建立連接,定期檢查健康狀態(tài)。

          客戶端通過多種資源發(fā)現(xiàn)策略如簡單的 DNS 發(fā)現(xiàn) Master 節(jié)點(diǎn),將來有計(jì)劃遷移到如 Consul 或 etcd 等資源發(fā)現(xiàn)組件 。

          當(dāng) Master 節(jié)點(diǎn)發(fā)生宕機(jī)時(shí),Sentinel 集群會(huì)提升 Slave 節(jié)點(diǎn)為新的 Master,同時(shí)在自身的 pubsub channel +switch-master 廣播切換的消息,具體消息格式為:

          switch-master <master name> <oldip> <oldport> <newip> <newport> 
          

          Watcher 監(jiān)聽到消息后,會(huì)去主動(dòng)更新資源發(fā)現(xiàn)策略,將客戶端連接指向新的 Master 節(jié)點(diǎn),完成 Failover。

          實(shí)際使用中需要注意以下幾點(diǎn):

          • 只讀 Slave 節(jié)點(diǎn)可以按照需求設(shè)置 slave-priority 參數(shù)為 0,防止故障切換時(shí)選擇了只讀節(jié)點(diǎn)而不是熱備 Slave 節(jié)點(diǎn)。
          • Sentinel 進(jìn)行故障切換后會(huì)執(zhí)行 CONFIG REWRITE 命令將 SLAVEOF 配置落地,如果 Redis 配置中禁用了 CONFIG 命令,切換時(shí)會(huì)發(fā)生錯(cuò)誤,可以通過修改 Sentinel 代碼來替換 CONFIG 命令。
          • Sentinel Group 監(jiān)控的節(jié)點(diǎn)不宜過多,實(shí)測(cè)超過 500 個(gè)切換過程偶爾會(huì)進(jìn)入 TILT 模式,導(dǎo)致 Sentinel 工作不正常,推薦部署多個(gè) Sentinel 集群并保證每個(gè)集群監(jiān)控的實(shí)例數(shù)量小于 300 個(gè)。
          • Master 節(jié)點(diǎn)應(yīng)與 Slave 節(jié)點(diǎn)跨機(jī)器部署,有能力的使用方可以跨機(jī)架部署,不推薦跨機(jī)房部署 Redis 主從實(shí)例。
          • Sentinel 切換功能主要依賴 down-after-milliseconds 和failover-timeout 兩個(gè)參數(shù),down-after-milliseconds 決定了Sentinel 判斷 Redis 節(jié)點(diǎn)宕機(jī)的超時(shí),知乎使用 30000 作為閾值。

          而 failover-timeout 則決定了兩次切換之間的最短等待時(shí)間,如果對(duì)于切換成功率要求較高,可以適當(dāng)縮短 failover-timeout 到秒級(jí)保證切換成功。

          • 單機(jī)網(wǎng)絡(luò)故障等同于機(jī)器宕機(jī),但如果機(jī)房全網(wǎng)發(fā)生大規(guī)模故障會(huì)造成主從多次切換,此時(shí)資源發(fā)現(xiàn)服務(wù)可能更新不夠及時(shí),需要人工介入。

          集群(Cluster)

          當(dāng)實(shí)例需要的容量超過 20G 或要求的吞吐量超過 20 萬請(qǐng)求每秒時(shí),我們會(huì)使用集群(Cluster)實(shí)例來承擔(dān)流量。

          集群是通過中間件(客戶端或中間代理等)將流量分散到多個(gè) Redis 實(shí)例上的解決方案。

          知乎的 Redis 集群方案經(jīng)歷了兩個(gè)階段:

          • 客戶端分片
          • Twemproxy 代理

          客戶端分片(before 2015)

          早期知乎使用 redis-shard 進(jìn)行客戶端分片,redis-shard 庫內(nèi)部實(shí)現(xiàn)了 CRC32、MD5、SHA1 三種哈希算法,支持絕大部分Redis 命令。使用者只需把 redis-shard 當(dāng)成原生客戶端使用即可,無需關(guān)注底層分片。



          基于客戶端的分片模式具有如下優(yōu)點(diǎn):

          • 基于客戶端分片的方案是集群方案中最快的,沒有中間件,僅需要客戶端進(jìn)行一次哈希計(jì)算,不需要經(jīng)過代理,沒有官方集群方案的 MOVED/ASK 轉(zhuǎn)向。
          • 不需要多余的 Proxy 機(jī)器,不用考慮 Proxy 部署與維護(hù)。
          • 可以自定義更適合生產(chǎn)環(huán)境的哈希算法。

          但是也存在如下問題:

          • 需要每種語言都實(shí)現(xiàn)一遍客戶端邏輯,早期知乎全站使用 Python 進(jìn)行開發(fā),但是后來業(yè)務(wù)線增多,使用的語言增加至 Python,Golang,Lua,C/C++,JVM 系(Java,Scala,Kotlin)等,維護(hù)成本過高。
          • 無法正常使用 MSET、MGET 等多種同時(shí)操作多個(gè) Key 的命令,需要使用 Hash tag 來保證多個(gè) Key 在同一個(gè)分片上。
          • 升級(jí)麻煩,升級(jí)客戶端需要所有業(yè)務(wù)升級(jí)更新重啟,業(yè)務(wù)規(guī)模變大后無法推動(dòng)。
          • 擴(kuò)容困難,存儲(chǔ)需要停機(jī)使用腳本 Scan 所有的 Key 進(jìn)行遷移,緩存只能通過傳統(tǒng)的翻倍取模方式進(jìn)行擴(kuò)容。
          • 由于每個(gè)客戶端都要與所有的分片建立池化連接,客戶端基數(shù)過大時(shí)會(huì)造成 Redis 端連接數(shù)過多,Redis 分片過多時(shí)會(huì)造成 Python 客戶端負(fù)載升高。
          • 早期知乎大部分業(yè)務(wù)由 Python 構(gòu)建,Redis 使用的容量波動(dòng)較小,redis-shard 很好地應(yīng)對(duì)了這個(gè)時(shí)期的業(yè)務(wù)需求,在當(dāng)時(shí)是一個(gè)較為不錯(cuò)的解決方案。

          Twemproxy 集群 (2015 - Now)

          2015 年開始,業(yè)務(wù)上漲迅猛,Redis 需求暴增,原有的 redis-shard 模式已經(jīng)無法滿足日益增長的擴(kuò)容需求,我們開始調(diào)研多種集群方案,最終選擇了簡單高效的 Twemproxy 作為我們的集群方案。

          由 Twitter 開源的 Twemproxy 具有如下優(yōu)點(diǎn):

          • 性能很好且足夠穩(wěn)定,自建內(nèi)存池實(shí)現(xiàn) Buffer 復(fù)用,代碼質(zhì)量很高。
          • 支持 fnv1a_64、murmur、md5 等多種哈希算法。
          • 支持一致性哈希(ketama),取模哈希(modula)和隨機(jī)(random)三種分布式算法。

          但是缺點(diǎn)也很明顯:

          • 單核模型造成性能瓶頸。
          • 傳統(tǒng)擴(kuò)容模式僅支持停機(jī)擴(kuò)容。

          對(duì)此,我們將集群實(shí)例分成兩種模式:

          • 緩存(Cache)
          • 存儲(chǔ)(Storage)

          如果使用方可以接受通過損失一部分少量數(shù)據(jù)來保證可用性,或使用方可以從其余存儲(chǔ)恢復(fù)實(shí)例中的數(shù)據(jù),這種實(shí)例即為緩存,其余情況均為存儲(chǔ)。我們對(duì)緩存和存儲(chǔ)采用了不同的策略。

          存儲(chǔ)



          對(duì)于存儲(chǔ)我們使用 fnv1a_64 算法結(jié)合 modula 模式即取模哈希對(duì) Key 進(jìn)行分片。

          底層 Redis 使用單機(jī)模式結(jié)合 Sentinel 集群實(shí)現(xiàn)高可用,默認(rèn)使用 1 個(gè) Master 節(jié)點(diǎn)和 1 個(gè) Slave 節(jié)點(diǎn)提供服務(wù),如果業(yè)務(wù)有更高的可用性要求,可以拓展 Slave 節(jié)點(diǎn)。

          當(dāng)集群中 Master 節(jié)點(diǎn)宕機(jī),按照單機(jī)模式下的高可用流程進(jìn)行切換,Twemproxy 在連接斷開后會(huì)進(jìn)行重連。

          對(duì)于存儲(chǔ)模式下的集群,我們不會(huì)設(shè)置 auto_eject_hosts,不會(huì)剔除節(jié)點(diǎn)。

          同時(shí),對(duì)于存儲(chǔ)實(shí)例,我們默認(rèn)使用 noeviction 策略,在內(nèi)存使用超過規(guī)定的額度時(shí)直接返回 OOM 錯(cuò)誤,不會(huì)主動(dòng)進(jìn)行 Key 的刪除,保證數(shù)據(jù)的完整性。

          由于 Twemproxy 僅進(jìn)行高性能的命令轉(zhuǎn)發(fā),不進(jìn)行讀寫分離,所以默認(rèn)沒有讀寫分離功能。

          而在實(shí)際使用過程中,我們也沒有遇到集群讀寫分離的需求,如果要進(jìn)行讀寫分離,可以使用資源發(fā)現(xiàn)策略在 Slave 節(jié)點(diǎn)上架設(shè) Twemproxy 集群,由客戶端進(jìn)行讀寫分離的路由。

          緩存

          考慮到對(duì)于后端(MySQL/HBase/RPC 等)的壓力,知乎絕大部分業(yè)務(wù)都沒有針對(duì)緩存進(jìn)行降級(jí),這種情況下對(duì)緩存的可用性要求較數(shù)據(jù)的一致性要求更高。

          但是如果按照存儲(chǔ)的主從模式實(shí)現(xiàn)高可用,1 個(gè) Slave 節(jié)點(diǎn)的部署策略在線上環(huán)境只能容忍 1 臺(tái)物理節(jié)點(diǎn)宕機(jī),N 臺(tái)物理節(jié)點(diǎn)宕機(jī)高可用就需要至少 N 個(gè) Slave 節(jié)點(diǎn),這無疑是種資源的浪費(fèi)。



          所以我們采用了 Twemproxy 一致性哈希(Consistent Hashing)策略來配合 auto_eject_hosts 自動(dòng)彈出策略組建 Redis 緩存集群。

          對(duì)于緩存我們?nèi)匀皇褂?fnv1a_64 算法進(jìn)行哈希計(jì)算,但是分布算法我們使用了 ketama 即一致性哈希進(jìn)行 Key 分布。緩存節(jié)點(diǎn)沒有主從,每個(gè)分片僅有 1 個(gè) Master 節(jié)點(diǎn)承載流量。

          Twemproxy 配置 auto_eject_hosts 會(huì)在實(shí)例連接失敗超過server_failure_limit 次的情況下剔除節(jié)點(diǎn)。

          并在 server_retry_timeout 超時(shí)之后進(jìn)行重試,剔除后配合 ketama 一致性哈希算法重新計(jì)算哈希環(huán),恢復(fù)正常使用,這樣即使一次宕機(jī)多個(gè)物理節(jié)點(diǎn)仍然能保持服務(wù)。



          在實(shí)際的生產(chǎn)環(huán)境中需要注意以下幾點(diǎn):

          • 剔除節(jié)點(diǎn)后,會(huì)造成短時(shí)間的命中率下降,后端存儲(chǔ)如 MySQL、HBase 等需要做好流量監(jiān)測(cè)。
          • 線上環(huán)境緩存后端分片不宜過大,建議維持在 20G 以內(nèi),同時(shí)分片調(diào)度應(yīng)盡可能分散,這樣即使宕機(jī)一部分節(jié)點(diǎn),對(duì)后端造成的額外的壓力也不會(huì)太多。
          • 機(jī)器宕機(jī)重啟后,緩存實(shí)例需要清空數(shù)據(jù)之后啟動(dòng),否則原有的緩存數(shù)據(jù)和新建立的緩存數(shù)據(jù)會(huì)沖突導(dǎo)致臟緩存。

          直接不啟動(dòng)緩存也是一種方法,但是在分片宕機(jī)期間會(huì)導(dǎo)致周期性 server_failure_limit 次數(shù)的連接失敗。

          • server_retry_timeout 和 server_failure_limit 需要仔細(xì)敲定確認(rèn),知乎使用 10min 和 3 次作為配置,即連接失敗 3 次后剔除節(jié)點(diǎn),10 分鐘后重新進(jìn)行連接。

          Twemproxy 部署

          在方案早期我們使用數(shù)量固定的物理機(jī)部署 Twemproxy,通過物理機(jī)上的 Agent 啟動(dòng)實(shí)例,Agent 在運(yùn)行期間會(huì)對(duì) Twemproxy 進(jìn)行健康檢查與故障恢復(fù)。

          由于 Twemproxy 僅提供全量的使用計(jì)數(shù),所以 Agent 運(yùn)行時(shí)還會(huì)進(jìn)行定時(shí)的差值計(jì)算來計(jì)算 Twemproxy 的 requests_per_second 等指標(biāo)。

          后來為了更好地故障檢測(cè)和資源調(diào)度,我們引入了 Kubernetes,將 Twemproxy 和 Agent 放入同一個(gè) Pod 的兩個(gè)容器內(nèi),底層 Docker 網(wǎng)段的配置使每個(gè) Pod 都能獲得獨(dú)立的 IP,方便管理。

          最開始,本著簡單易用的原則,我們使用 DNS A Record 來進(jìn)行客戶端的資源發(fā)現(xiàn),每個(gè) Twemproxy 采用相同的端口號(hào),一個(gè) DNS A Record 后面掛接多個(gè) IP 地址對(duì)應(yīng)多個(gè) Twemproxy 實(shí)例。

          初期,這種方案簡單易用,但是到了后期流量日益上漲,單集群 Twemproxy 實(shí)例個(gè)數(shù)很快就超過了 20 個(gè)。

          由于 DNS 采用的 UDP 協(xié)議有 512 字節(jié)的包大小限制,單個(gè) A Record 只能掛接 20 個(gè)左右的 IP 地址,超過這個(gè)數(shù)字就會(huì)轉(zhuǎn)換為 TCP 協(xié)議,客戶端不做處理就會(huì)報(bào)錯(cuò),導(dǎo)致客戶端啟動(dòng)失敗。

          當(dāng)時(shí)由于情況緊急,只能建立多個(gè) Twemproxy Group,提供多個(gè) DNS A Record 給客戶端,客戶端進(jìn)行輪詢或者隨機(jī)選擇,該方案可用,但是不夠優(yōu)雅。

          如何解決 Twemproxy 單 CPU 計(jì)算能力的限制?

          之后我們修改了 Twemproxy 源碼, 加入 SO_REUSEPORT 支持。



          Twemproxy with SO_REUSEPORT on Kubernetes

          同一個(gè)容器內(nèi)由 Starter 啟動(dòng)多個(gè) Twemproxy 實(shí)例并綁定到同一個(gè)端口,由操作系統(tǒng)進(jìn)行負(fù)載均衡,對(duì)外仍然暴露一個(gè)端口,但是內(nèi)部已經(jīng)由系統(tǒng)均攤到了多個(gè) Twemproxy 上。

          同時(shí) Starter 會(huì)定時(shí)去每個(gè) Twemproxy 的 stats 端口獲取 Twemproxy 運(yùn)行狀態(tài)進(jìn)行聚合,此外 Starter 還承載了信號(hào)轉(zhuǎn)發(fā)的職責(zé)。

          原有的Agent 不需要用來啟動(dòng) Twemproxy 實(shí)例,所以 Monitor 調(diào)用 Starter 獲取聚合后的 stats 信息進(jìn)行差值計(jì)算,最終對(duì)外界暴露出實(shí)時(shí)的運(yùn)行狀態(tài)信息。

          為什么沒有使用官方 Redis 集群方案?

          我們?cè)?2015 年調(diào)研過多種集群方案,綜合評(píng)估多種方案后,最終選擇了看起來較為陳舊的 Twemproxy 而不是官方 Redis 集群方案與 Codis,具體原因如下:

          MIGRATE 造成的阻塞問題:Redis 官方集群方案使用 CRC16 算法計(jì)算哈希值并將 Key 分散到 16384 個(gè) Slot 中,由使用方自行分配 Slot 對(duì)應(yīng)到每個(gè)分片中。

          擴(kuò)容時(shí)由使用方自行選擇 Slot 并對(duì)其進(jìn)行遍歷,對(duì) Slot 中每一個(gè) Key 執(zhí)行 MIGRATE 命令進(jìn)行遷移。

          調(diào)研后發(fā)現(xiàn),MIGRATE 命令實(shí)現(xiàn)分為三個(gè)階段:

          • DUMP 階段:由源實(shí)例遍歷對(duì)應(yīng) Key 的內(nèi)存空間,將 Key 對(duì)應(yīng)的 Redis Object 序列化,序列化協(xié)議跟 Redis RDB 過程一致。
          • RESTORE 階段:由源實(shí)例建立 TCP 連接到對(duì)端實(shí)例,并將 DUMP 出來的內(nèi)容使用 RESTORE 命令到對(duì)端進(jìn)行重建,新版本的 Redis 會(huì)緩存對(duì)端實(shí)例的連接。
          • DEL 階段(可選):如果發(fā)生遷移失敗,可能會(huì)造成同名的 Key 同時(shí)存在于兩個(gè)節(jié)點(diǎn)。

          此時(shí) MIGRATE 的 REPLACE 參數(shù)決定是否覆蓋對(duì)端的同名 Key,如果覆蓋,對(duì)端的 Key 會(huì)進(jìn)行一次刪除操作,4.0 版本之后刪除可以異步進(jìn)行,不會(huì)阻塞主進(jìn)程。

          經(jīng)過調(diào)研,我們認(rèn)為這種模式并不適合知乎的生產(chǎn)環(huán)境。Redis 為了保證遷移的一致性, MIGRATE 所有操作都是同步操作,執(zhí)行 MIGRATE 時(shí),兩端的 Redis 均會(huì)進(jìn)入時(shí)長不等的 BLOCK 狀態(tài)。

          對(duì)于小 Key,該時(shí)間可以忽略不計(jì),但如果一旦 Key 的內(nèi)存使用過大,一個(gè) MIGRATE 命令輕則導(dǎo)致 P95 尖刺,重則直接觸發(fā)集群內(nèi)的 Failover,造成不必要的切換。

          同時(shí),遷移過程中訪問到處于遷移中間狀態(tài)的Slot 的 Key 時(shí),根據(jù)進(jìn)度可能會(huì)產(chǎn)生 ASK 轉(zhuǎn)向,此時(shí)需要客戶端發(fā)送 ASKING 命令到 Slot 所在的另一個(gè)分片重新請(qǐng)求,請(qǐng)求時(shí)延則會(huì)變?yōu)樵瓉淼膬杀丁?/p>

          同樣,方案初期時(shí)的 Codis 采用的是相同的 MIGRATE 方案,但是使用 Proxy 控制 Redis 進(jìn)行遷移操作而非第三方腳本(如 redis-trib.rb),基于同步的類似 MIGRATE 的命令,實(shí)際跟 Redis 官方集群方案存在同樣的問題。

          對(duì)于這種 Huge Key 問題決定權(quán)完全在于業(yè)務(wù)方,有時(shí)業(yè)務(wù)需要不得不產(chǎn)生 Huge Key 時(shí)會(huì)十分尷尬,如關(guān)注列表。

          一旦業(yè)務(wù)使用不當(dāng)出現(xiàn)超過 1MB 以上的大 Key 便會(huì)導(dǎo)致數(shù)十毫秒的延遲,遠(yuǎn)高于平時(shí) Redis 亞毫秒級(jí)的延遲。

          有時(shí),在 Slot 遷移過程中業(yè)務(wù)不慎同時(shí)寫入了多個(gè)巨大的 Key 到 Slot 遷移的源節(jié)點(diǎn)和目標(biāo)節(jié)點(diǎn),除非寫腳本刪除這些 Key ,否則遷移會(huì)進(jìn)入進(jìn)退兩難的地步。

          對(duì)此,Redis 作者在 Redis 4.2 的 roadmap[5] 中提到了 Non blocking MIGRATE。

          但是截至目前,Redis 5.0 即將正式發(fā)布,仍未看到有關(guān)改動(dòng),社區(qū)中已經(jīng)有相關(guān)的 Pull Request [6],該功能可能會(huì)在 5.2 或者 6.0 之后并入 Master 分支,對(duì)此我們將持續(xù)觀望。

          緩存模式下高可用方案不夠靈活:還有,官方集群方案的高可用策略僅有主從一種,高可用級(jí)別跟 Slave 的數(shù)量成正相關(guān)。

          如果只有一個(gè) Slave,則只能允許一臺(tái)物理機(jī)器宕機(jī),Redis 4.2 roadmap 提到了 cache-only mode,提供類似于 Twemproxy 的自動(dòng)剔除后重分片策略,但是截至目前仍未實(shí)現(xiàn)。

          內(nèi)置 Sentinel 造成額外流量負(fù)載:另外,官方 Redis 集群方案將 Sentinel 功能內(nèi)置到 Redis 內(nèi),這導(dǎo)致在節(jié)點(diǎn)數(shù)較多(大于 100)時(shí)在 Gossip 階段會(huì)產(chǎn)生大量的 PING/INFO/CLUSTER INFO 流量。

          根據(jù) issue 中提到的情況,200 個(gè)使用 3.2.8 版本節(jié)點(diǎn)搭建的 Redis 集群,在沒有任何客戶端請(qǐng)求的情況下,每個(gè)節(jié)點(diǎn)仍然會(huì)產(chǎn)生 40Mb/s 的流量。

          雖然到后期 Redis 官方嘗試對(duì)其進(jìn)行壓縮修復(fù),但按照 Redis 集群機(jī)制,節(jié)點(diǎn)較多的情況下無論如何都會(huì)產(chǎn)生這部分流量,對(duì)于使用大內(nèi)存機(jī)器但是使用千兆網(wǎng)卡的用戶這是一個(gè)值得注意的地方。

          Slot 存儲(chǔ)開銷:最后,每個(gè)Key 對(duì)應(yīng)的 Slot 的存儲(chǔ)開銷,在規(guī)模較大的時(shí)候會(huì)占用較多內(nèi)存,4.x 版本以前甚至?xí)_(dá)到實(shí)際使用內(nèi)存的數(shù)倍。

          雖然 4.x 版本使用 rax 結(jié)構(gòu)進(jìn)行存儲(chǔ),但是仍然占據(jù)了大量內(nèi)存,從非官方集群方案遷移到官方集群方案時(shí),需要注意這部分多出來的內(nèi)存。

          總之,官方 Redis 集群方案與 Codis 方案對(duì)于絕大多數(shù)場(chǎng)景來說都是非常優(yōu)秀的解決方案。

          但是我們仔細(xì)調(diào)研發(fā)現(xiàn)并不是很適合集群數(shù)量較多且使用方式多樣化的我們,場(chǎng)景不同側(cè)重點(diǎn)也會(huì)不一樣,但在此仍然要感謝開發(fā)這些組件的開發(fā)者們,感謝你們對(duì) Redis 社區(qū)的貢獻(xiàn)。

          擴(kuò)容

          靜態(tài)擴(kuò)容

          對(duì)于單機(jī)實(shí)例,如果通過調(diào)度器觀察到對(duì)應(yīng)的機(jī)器仍然有空閑的內(nèi)存,我們僅需直接調(diào)整實(shí)例的 maxmemory 配置與報(bào)警即可。

          同樣,對(duì)于集群實(shí)例,我們通過調(diào)度器觀察每個(gè)節(jié)點(diǎn)所在的機(jī)器,如果所有節(jié)點(diǎn)所在機(jī)器均有空閑內(nèi)存,我們會(huì)像擴(kuò)容單機(jī)實(shí)例一樣直接更新 maxmemory 與報(bào)警。

          動(dòng)態(tài)擴(kuò)容

          但是當(dāng)機(jī)器空閑內(nèi)存不夠,或單機(jī)實(shí)例與集群的后端實(shí)例過大時(shí),無法直接擴(kuò)容,需要進(jìn)行動(dòng)態(tài)擴(kuò)容:

          • 對(duì)于單機(jī)實(shí)例,如果單實(shí)例超過 30GB 且沒有如 sinterstore 之類的多 Key 操作,我們會(huì)將其擴(kuò)容為集群實(shí)例。
          • 對(duì)于集群實(shí)例,我們會(huì)進(jìn)行橫向的重分片,我們稱之為 Resharding 過程。



          Resharding 過程

          原生 Twemproxy 集群方案并不支持?jǐn)U容,我們開發(fā)了數(shù)據(jù)遷移工具來進(jìn)行 Twemproxy 的擴(kuò)容,遷移工具本質(zhì)上是一個(gè)上下游之間的代理,將數(shù)據(jù)從上游按照新的分片方式搬運(yùn)到下游。

          原生 Redis 主從同步使用 SYNC/PSYNC 命令建立主從連接,收到 SYNC 命令的 Master 會(huì) fork 出一個(gè)進(jìn)程遍歷內(nèi)存空間生成 RDB 文件并發(fā)送給 Slave。

          期間所有發(fā)送至 Master 的寫命令在執(zhí)行的同時(shí)都會(huì)被緩存到內(nèi)存的緩沖區(qū)內(nèi),當(dāng) RDB 發(fā)送完成后,Master 會(huì)將緩沖區(qū)內(nèi)的命令及之后的寫命令轉(zhuǎn)發(fā)給 Slave 節(jié)點(diǎn)。

          我們開發(fā)的遷移代理會(huì)向上游發(fā)送 SYNC 命令模擬上游實(shí)例的 Slave,代理收到 RDB 后進(jìn)行解析。

          由于 RDB 中每個(gè) Key 的格式與 RESTORE 命令的格式相同,所以我們使用生成 RESTORE 命令按照下游的 Key 重新計(jì)算哈希并使用 Pipeline 批量發(fā)送給下游。

          等待 RDB 轉(zhuǎn)發(fā)完成后,我們按照新的后端生成新的 Twemproxy 配置,并按照新的 Twemproxy 配置建立 Canary 實(shí)例。

          從上游的 Redis 后端中取 Key 進(jìn)行測(cè)試,測(cè)試 Resharding 過程是否正確,測(cè)試過程中的 Key 按照大小,類型,TTL 進(jìn)行比較。

          測(cè)試通過后,對(duì)于集群實(shí)例,我們使用生成好的配置替代原有 Twemproxy 配置并 restart/reload Twemproxy 代理。

          我們修改了 Twemproxy 代碼,加入了 config reload 功能,但是實(shí)際使用中發(fā)現(xiàn)直接重啟實(shí)例更加可控。

          而對(duì)于單機(jī)實(shí)例,由于單機(jī)實(shí)例和集群實(shí)例對(duì)于命令的支持不同,通常需要和業(yè)務(wù)方確定后手動(dòng)重啟切換。

          由于 Twemproxy 部署于 Kubernetes ,我們可以實(shí)現(xiàn)細(xì)粒度的灰度,如果客戶端接入了讀寫分離,我們可以先將讀流量接入新集群,最終接入全部流量。

          這樣相對(duì)于 Redis 官方集群方案,除在上游進(jìn)行 BGSAVE 時(shí)的 fork 復(fù)制頁表時(shí)造成的尖刺以及重啟時(shí)造成的連接閃斷,其余對(duì)于 Redis 上游造成的影響微乎其微。

          這樣擴(kuò)容存在的問題:

          對(duì)上游發(fā)送 SYNC 后,上游fork 時(shí)會(huì)造成尖刺:對(duì)于存儲(chǔ)實(shí)例,我們使用Slave 進(jìn)行數(shù)據(jù)同步,不會(huì)影響到接收請(qǐng)求的 Master 節(jié)點(diǎn)。

          對(duì)于緩存實(shí)例,由于沒有 Slave 實(shí)例,該尖刺無法避免,如果對(duì)于尖刺過于敏感,我們可以跳過 RDB 階段,直接通過 PSYNC 使用最新的 SET 消息建立下游的緩存。

          切換過程中有可能寫到下游,而讀在上游:對(duì)于接入了讀寫分離的客戶端,我們會(huì)先切換讀流量到下游實(shí)例,再切換寫流量。

          一致性問題:兩條具有先后順序的寫同一個(gè) Key 命令在切換代理后端時(shí)會(huì)通過 1)寫上游同步到下游 2)直接寫到下游兩種方式寫到下游。

          此時(shí),可能存在應(yīng)先執(zhí)行的命令卻通過 1)執(zhí)行落后于通過 2)執(zhí)行,導(dǎo)致命令先后順序倒置。

          這個(gè)問題在切換過程中無法避免,好在絕大部分應(yīng)用沒有這種問題,如果無法接受,只能通過上游停寫排空 Resharding 代理保證先后順序。

          官方 Redis 集群方案和 Codis 會(huì)通過 blocking 的 MIGRATE 命令來保證一致性,不存在這種問題。

          實(shí)際使用過程中,如果上游分片安排合理,可實(shí)現(xiàn)數(shù)千萬次每秒的遷移速度,1TB 的實(shí)例 Resharding 只需要半小時(shí)左右。

          另外,對(duì)于實(shí)際生產(chǎn)環(huán)境來說,提前做好預(yù)期規(guī)劃比遇到問題緊急擴(kuò)容要快且安全得多。

          旁路分析

          由于生產(chǎn)環(huán)境調(diào)試需要,有時(shí)會(huì)需要監(jiān)控線上 Redis 實(shí)例的訪問情況,Redis 提供了多種監(jiān)控手段,如 MONITOR 命令。

          但由于 Redis 單線程的限制,導(dǎo)致自帶的 MONITOR 命令在負(fù)載過高的情況下會(huì)再次跑高 CPU,對(duì)于生產(chǎn)環(huán)境來說過于危險(xiǎn)。

          而其余方式如 Keyspace Notify 只有寫事件,沒有讀事件,無法做到細(xì)致的觀察。

          對(duì)此我們開發(fā)了基于 libpcap 的旁路分析工具,系統(tǒng)層面復(fù)制流量,對(duì)應(yīng)用層流量進(jìn)行協(xié)議分析,實(shí)現(xiàn)旁路 MONITOR,實(shí)測(cè)對(duì)于運(yùn)行中的實(shí)例影響微乎其微。

          同時(shí)對(duì)于沒有 MONITOR 命令的 Twemproxy,旁路分析工具仍能進(jìn)行分析。

          由于生產(chǎn)環(huán)境中絕大部分業(yè)務(wù)都使用 Kubernetes 部署于 Docker 內(nèi) ,每個(gè)容器都有對(duì)應(yīng)的獨(dú)立 IP。

          所以可以使用旁路分析工具反向解析找出客戶端所在的應(yīng)用,分析業(yè)務(wù)方的使用模式,防止不正常的使用。

          將來的工作

          由于 Redis 5.0 發(fā)布在即,4.0 版本趨于穩(wěn)定,我們將逐步升級(jí)實(shí)例到 4.0 版本,由此帶來的如 MEMORY 命令、Redis Module 、新的 LFU 算法等特性無論對(duì)運(yùn)維方還是業(yè)務(wù)方都有極大的幫助。

          最后

          知乎架構(gòu)平臺(tái)團(tuán)隊(duì)是支撐整個(gè)知乎業(yè)務(wù)的基礎(chǔ)技術(shù)團(tuán)隊(duì),開發(fā)和維護(hù)著知乎幾乎全量的核心基礎(chǔ)組件。

          包括容器、Redis、MySQL、Kafka、LB、HBase 等核心基礎(chǔ)設(shè)施,團(tuán)隊(duì)小而精,每個(gè)同學(xué)都獨(dú)當(dāng)一面負(fù)責(zé)上面提到的某個(gè)核心系統(tǒng)。

          隨著知乎業(yè)務(wù)規(guī)模的快速增長,以及業(yè)務(wù)復(fù)雜度的持續(xù)增加,團(tuán)隊(duì)面臨的技術(shù)挑戰(zhàn)也越來越大。

          參考資料:

          • Redis Official site

          https://redis.io/

          • Twemproxy Github Page

          https://github.com/twitter/twemproxy

          • Codis Github Page

          https://github.com/CodisLabs/codis

          • SO_REUSEPORT Man Page

          http://man7.org/linux/man-pages/man7/socket.7.html

          • Kubernetes

          https://kubernetes.io/

          作者:陳鵬

          簡介:現(xiàn)知乎存儲(chǔ)平臺(tái)組 Redis 平臺(tái)技術(shù)負(fù)責(zé)人,2014 年加入知乎技術(shù)平臺(tái)組從事基礎(chǔ)架構(gòu)相關(guān)系統(tǒng)的開發(fā)與運(yùn)維,從無到有建立了知乎 Redis 平臺(tái),承載了知乎高速增長的業(yè)務(wù)流量。


          主站蜘蛛池模板: 国产精品亚洲午夜一区二区三区| 免费无码VA一区二区三区| 综合久久久久久中文字幕亚洲国产国产综合一区首 | 中文字幕av日韩精品一区二区 | 国语精品一区二区三区| 国内精品视频一区二区八戒| 亚洲一区二区在线视频| 一区二区三区久久精品| 国产精品第一区揄拍无码| 国产吧一区在线视频| 国产成人一区二区三区视频免费| 日韩社区一区二区三区| 国产成人免费一区二区三区| 亚洲一本一道一区二区三区| 亚洲av无码一区二区三区不卡| www一区二区www免费| 一区二区三区视频免费| 一区二区免费在线观看| 无码精品蜜桃一区二区三区WW| 成人毛片无码一区二区| 无码国产精品一区二区免费I6| 无码人妻一区二区三区一| 国产精品揄拍一区二区久久| 日韩亚洲一区二区三区| 精品久久一区二区| 国产精品无码一区二区三区电影| 东京热无码av一区二区| 国产成人精品无码一区二区三区| 久久亚洲中文字幕精品一区四| 国产精久久一区二区三区| 国产精品乱码一区二区三区| 中文字幕亚洲一区二区三区| 中文字幕精品亚洲无线码一区应用| 99久久精品国产免看国产一区| 美女啪啪一区二区三区| 91香蕉福利一区二区三区| 国产精品香蕉一区二区三区| 日韩精品国产一区| 亚洲第一区在线观看| 另类一区二区三区| 国产精品电影一区|