高性能微博架構設計與實現

高性能微博架構設計與實現

這是一個高性能微博/朋友圈/空間類系統架構,支持千萬活躍、百萬在線、十萬QPS。服務集羣支持在線縮擴容、熔斷,支持遠程日誌、統一監控。
本框架主體採用golang+grpc實現。

微博類系統我認爲是互聯網業務系統中最複雜和最喫性能的。簡單舉兩個最常用的操作爲例:

  • pull操作分析:假設平均一個用戶關注30個人,那麼他的一次pull就會包含查詢所有這30個人的最新若干條消息。然後拉通按照時間進行從近到遠排序,選出若干條(假設15條)。然後再去查詢出這15條微博的內容,再返回給該用戶。這才完成一個用戶的一次pull。而微博系統中pull的觸發量是非常大的。一個在線用戶如果在瀏覽時間線,那可能一分多鐘就會觸發一次。一百萬在線光pull就會產生10萬的QPS。
  • post操作分析:一個大V就是幾百萬幾千萬的粉絲量。他的一次post,就會觸發對這些粉絲的消息推送。其中有在線的,有不在線的。twitter對推送的要求是一個5千萬粉的大V的一條消息需要在5秒內送達所有粉絲。

本框架是在一個現有類似項目中提取而來,做了性能優化。現在已經放在github上開源。各位同學可以下載共同討論。

代碼實現

代碼可在github上下載https://github.com/zzmmwu/WeiBo

目錄結構

├─common

│ ├─authToken 空

│ ├─dbConnPool 數據庫連接池

│ ├─dbSvrConnPool dbSvr連接池

│ ├─grpcStatsHandler

│ ├─protobuf 整個框架的protobuf定義都在此

│ ├─rlog 遠程日誌和監控數據打點系統的接口定義

│ └─scaleAndBreak 可online線上縮擴容和自動熔斷的服務管理

├─config 所有服務的配置文件

├─contentSvr 微博內容提取服務

├─dbSvr db統一接入服務

├─dbTools

│ └─createDB 生成WeiBo框架mongodb數據庫

├─followedSvr 用戶粉絲數據提取服務(名字沒有起好,叫follower更好)

├─followSvr 用戶關注數據提取服務

├─frontNotifySvr 前端在線推送接入服務

├─frontNotifySvrMng frontNotifySvr的管理

├─frontSvr 前端接入服務

├─frontSvrMng frontSvr的管理

├─msgIdGenerator 全系統所有微博id生成器

├─postSvr 發博

├─pullSvr 拉取

├─pushSvr 在線推送

├─relationChangeSvr 關注(follow)和取關(unfollow)的關係處理

├─rlogSvr 遠程日誌和監控數據打點服務

├─statMonitor

│ ├─h5Monitor 監控數據H5頁面

│ └─monitorWsSvr 監控數據查看服務

├─test

│ ├─performTest 性能測試工具

│ ├─performTestDataGen 性能測試數據生成

│ │ ├─msgContentGen

│ │ └─relationGen

│ └─testClient 功能測試工具

└─usrMsgIdSvr 用戶微博數據服務

整體架構圖

整體架構圖

frontSvrMng簡述

frontSvr是面向客戶端的接入服務。frontSvr不是固定url,每次客戶端登錄都動態分配。frontSvrMng就是面向客戶端對於frontSvr的分配服務。客戶端在接入weibo服務時,只需要知道frontSvrMng的url,通過查詢命令即可得到本次登錄分配給自己的frontSvr的url。

frontSvrMng支持對frontSvr的熱縮擴容,實時熔斷,而負荷分擔則直接採用簡單的循環分配即可。這部分能力可以參照common/scaleAndBreak包說明。

frontSvrMng的查詢量和連接數不會太大(每次客戶端連接微博服務時用短連接查詢一次即可,直到斷開連接後下次需要時再查。)我在性能測試時直接只用一臺服務器就行了,實際生產環境用主從熱備的兩個url應該就ok了。如果登錄量實在密集,比如每秒過十萬級,那麼可以用dns解析到多個url上。或者客戶端上保存多個url進行循環使用,平時多url全部解析到一個地址,忙時分散。frontSvr對frontSvrMng沒有向上依賴關係,所以一個和n個mng對frontSvr是透明的。

frontSvr簡述

frontSvr是客戶端進行業務命令接入服務。採用多服務器平行擴展的方式進行部署(由frontSvrMng面向客戶端進行統一管理)。

frontSvr通過grpc+protobuf接入。目前的實現中沒有做鑑權和加密,實際商用中肯定是需要的,不過這部分比較獨立這裏就暫時不做了(太忙,後續有時間再加)。

服務架構如下
整體架構圖

frontSvr將所有客戶端的請求全部匯聚到10個req管道中,且每一個grpc請求都等待在自己對應的一個rsp接收管道上。每個req管道後面都對應一組routine負責req的發送、rsp的接收以及根據入管道時生成的reqId找到對應的rsp管道。

reqCmd routine從reqChan中取出reqCmd,然後根據cmd發送給對應的svr。frontSvr通過grpc雙向stream連接了pullSvr/postSvr/relationChangeSvr,分別對應pull/post和follow/unfollow命令。

reqCmd routine負責根據配置文件連接對應的服務。所有服務連接支持平行擴展負荷分擔,支持熱擴容。支持實時檢測可用性,如果一個svr不可用則馬上嘗試其他平行svr,直到成功或所有svr都不可用。

這裏有同學可能會問,go語言併發能力那麼強大,幹嘛不在前端的grpcSvr routine中直接調用後端的服務,還要整些匯聚的管道呢?能簡單處理的事情爲什麼要弄這麼複雜。我的回答是前端請求必須匯聚後再傳到後端服務的原因至少有這麼幾個吧:

  1. 避震。客戶端很可能存在併發量的劇烈震盪。如果讓每一個客戶端請求都直接連接和請求後端,那麼這些劇烈震盪將是全系統的,很可能某些服務在某一次震盪中就被搞趴窩了。有了匯聚層,後端所有服務都會以自己能力來主動取請求去盡力處理,而不會被硬塞。每一層的服務都盡能力處理,這是整個系統最佳的設計(在這個weibo框架中其實也還沒有做到,並沒有在每一層服務都做吞吐匯聚。如果要更加健壯更加可靠的話,可以將每一層服務都做成這樣的吞吐模式)。
  2. 降低後端併發連接。一個客戶端就會對應一個grpc連接,如果直接去連接和請求後端服務那麼假如高峯期20臺frontSvr,一個上面10萬併發,那總共就會有200萬併發連接。想象一個每一個連接都直接連接和請求後端服務,那麼後端每一個服務器都要同時接200萬併發,可能每臺服務器都需要幾百核幾百G的配置了。
  3. 防攻擊。全部服務直連,一次DOS攻擊可以把後端服務全部搞趴,而不僅僅只是部分接入服務了。

所以,直連的方式可以適合小規模且安全的網絡環境,但不適合大併發的開放網絡環境。

pullSvr簡述

pullSvr負責處理pull請求。他接受來之frontSvr的pull命令,然後根據用戶id和lastMsgId去查詢該用戶關注的所有人中比lastMsgId更早一些的若干條微博消息(按照時間倒序)。

處理流程大致描述一下。pullSvr拿到pull請求後首先需要從followSvr查詢到該用戶的關注列表。然後去usrMsgIdSvr查詢所有這些用戶比lastMsgId更早一些的若干條消息id。彙總後按照時間倒序再選擇出若干條,然後再去contentSvr查詢這些消息的具體內容。最後返回給frontSvr。

服務架構:
整體架構圖
pullSvr的前端是連接frontSvr的各routine。這些routine會再創建一個負責接收本連接所有rsp的管道並對應一個負責處理rsp的routine。通過一個req 管道將所有pullSvr的請求都匯聚起來。這條集中的req管道後面是若干處理routine(pullAssembleRoutine)。這些處理routine與followSvr/usrMsgSvr/contentSvr保持長連接,從req管道獲取請求後按照流程處理。處理完後將rsp丟進對應的rsp的管道。

pullAssembleRoutine負責根據配置文件連接對應的服務。所有服務連接支持平行擴展負荷分擔,支持熱擴容。支持實時檢測可用性,如果一個svr不可用則馬上嘗試其他平行svr,直到成功或所有svr都不可用。

pullAssembleRoutine的數量需要根據實際運行情況進行調整(我這裏的測試用的100)。pullSvr的吞吐能力還取決於後端followSvr/usrMsgSvr/contentSvr。由於pullSvr是採用直連後端的模式,所以pullAssembleRoutine數量越大,後端的併發壓力就越大;數量越小則pullSvr的併發處理能力會減弱。假設一個完整的pull流程會花費20ms(如果沒有cache命中需要db加載則更長),那麼100個routine的極限吞吐就是100*1/0.02=5000。更加科學和健壯的設計可以不用這種直連模式,所有後端也採用請求匯聚、按能力處理的方式。

pullSvr支持平行擴展,熱縮擴容,故障熔斷。這些能力都由frontSvr中的連接模塊實現。(這裏的實現沒有用統一接入的透明連接模式,因爲是所有svr都是內部開發嘛所以就由連接模塊來實現了)

followSvr簡述

followSvr負責查詢用戶的關注列表。

服務架構:
整體架構圖
followSvr採用直連模式,未做請求隊列匯聚。併發上限爲pullSvr數量乘上pullSvr中pullAssembleRoutine的數量加上relationChangeSvr數量乘上relationChangeSvr中procRoutine的數量。

目前followSvr只做了單臺部署設計,未做負荷分擔等設計。主要是按照設計的量基本夠用,還有更重要的原因是精力和時間有限。

usrMsgIdSvr簡述

usrMsgIdSvr負責根據用戶id和lastMsgId查詢用戶的在lastMsgId之前的歷史微博id。這裏不查詢內容,只查詢id。支持單個和批量用戶查詢。一次批量查詢只會返回所有usrid中時間最近的若干條消息的id。

服務架構:
整體架構圖
usrMsgIdSvr採用直連模式,未做請求隊列匯聚。併發上限爲pullSvr數量乘上pullSvr中pullAssembleRoutine的數量加上postSvr數量乘上postRoutine數量。

usrMsgIdSvr採用userId分片+平行擴展的部署模式。並支持熱縮擴容,故障熔斷。這些能力都由pullSvr和postSvr中的連接模塊實現。(這裏的實現沒有用統一接入的透明連接模式,因爲是所有svr都是內部開發嘛所以就由連接模塊來實現了)
整體架構圖

contentSvr簡述

usrMsgIdSvr負責根據msgid獲取微博消息的具體內容。支持單個和批量查詢。

服務架構:
整體架構圖
contentSvr採用直連模式,未做請求隊列匯聚。併發上限爲pullSvr數量乘上pullSvr中pullAssembleRoutine的數量加上postSvr數量乘上postRoutine數量。

contentSvr採用msgId分片+平行擴展的部署模式。並支持熱縮擴容,故障熔斷。這些能力都由pullSvr和postSvr中的連接模塊實現。(這裏的實現沒有用統一接入的透明連接模式,因爲是所有svr都是內部開發嘛所以就由連接模塊來實現了)
整體架構圖

relationChangeSvr簡述

relationChangeSvr負責處理關注和取關命令。負責刷新數據庫的關係表和實時通知followSvr/followedSvr。

服務架構:
整體架構圖
relationChangeSvr的前端是連接frontSvr的各routine。這些routine會再創建一個負責接收本連接所有rsp的管道並對應一個負責處理rsp的routine。通過一個req 管道將所有請求都匯聚起來。這條集中的req管道後面是若干處理routine(procRoutine)。這些處理routine與followSvr/folledSvr/DB保持長連接,從req管道獲取請求後按照流程處理。處理完後將rsp丟進對應的rsp的管道。

procRoutine的數量需要根據實際運行情況進行調整(我這裏的測試用的100)。pullSvr的吞吐能力主要還取決於後端DB。procRoutine數量越大,DB的併發壓力就越大;數量越小則relationChangeSvr的併發處理能力會減弱。假設一個完整的follow/unfollow流程會花費20ms,那麼100個routine的極限吞吐就是100*1/0.02=5000。如果DB分片處理能力夠強則可以加大併發量,反之可以減小。

relationChangeSvr支持平行擴展,熱縮擴容,故障熔斷。這些能力都由frontSvr中的連接模塊實現。(這裏的實現沒有用統一接入的透明連接模式,因爲是所有svr都是內部開發嘛所以就由連接模塊來實現了)

followedSvr簡述

followedSvr負責查詢用戶的粉絲列表。

服務架構:
整體架構圖
followedSvr採用直連模式,未做請求隊列匯聚。併發上限爲pushSvr數量乘上pushSvr中pushRoutine的數量加上relationChangeSvr數量乘上relationChangeSvr中procRoutine的數量。

followedSvr上除了緩存粉絲列表外,還緩存了在線用戶表。目的是爲了降低通信量。微博的一個大V的粉絲數就有幾千萬,在本次設計的數量量下大V也是百萬粉絲級別。而在線的粉絲至少會低一個數量級。在線推送則只需要獲取在線粉絲列表即可,所以在此緩存了一份實時在線用戶表,作爲過濾。

目前followedSvr只做了單臺部署設計,未做負荷分擔等設計。主要是按照設計的量基本夠用,還有更重要的原因是精力和時間有限。

postSvr簡述

postSvr負責處理post請求(發微博)。負責入DB,通知刷新usrMsgIdSvr,並且通知pushSvr對在線粉絲髮送通知。

服務架構:
整體架構圖
postSvr的前端是連接frontSvr的各routine。這些routine會再創建一個負責接收本連接所有rsp的管道並對應一個負責處理rsp的routine。通過一個req 管道將所有pullSvr的請求都匯聚起來。這條集中的req管道後面是若干處理routine(postRoutine)。這些處理routine與usrMsgIdSvr/DB/pushSvr保持長連接,從req管道獲取請求後按照流程處理。處理完後將rsp丟進對應的rsp的管道。

msgIdGnerator接受多個併發postSvr的請求,互斥的生成不重複的msgId,嚴格保證在全系統中msgId按照時間順序遞增。在pull操作時,無需比較時間戳,直接用msgId就能進行時間排序。

postSvr不會去連接contentSvr,contentSvr中的緩存由粉絲的pull操作來驅動。

postRoutine負責根據配置文件連接對應的服務。所有服務連接支持平行擴展負荷分擔,支持熱擴容。支持實時檢測可用性,如果一個svr不可用則馬上嘗試其他平行svr,直到成功或所有svr都不可用。

postRoutine的數量需要根據實際運行情況進行調整(我這裏的測試用的100)。由於實際的weibo系統中post的量是比較小的(相對與pull)所以這裏基本不會是什麼問題。post引發的對粉絲的push操作會是個考驗,不過這個是pushSvr乾的活了。這裏只是簡單的發送一條通知跟pushSvr就完事。

postSvr支持平行擴展,熱縮擴容,故障熔斷。這些能力都由frontSvr中的連接模塊實現。(這裏的實現沒有用統一接入的透明連接模式,因爲是所有svr都是內部開發嘛所以就由連接模塊來實現了)

msgIdGenerator簡述

msgIdGnerator接受多個併發postSvr的請求,互斥的生成不重複的msgId,嚴格保證在全系統中msgId按照時間順序遞增。msgId的順序即是微博的時間順序。

pushSvr簡述

pushSvr負責在用戶post一條微博後向其所有在線粉絲髮送通知。

服務架構:
整體架構圖
pushSvr中有一個userOnlineCache,這個cache除了緩存在線用戶id還保存了用戶對應的frontNotifySvr。

pushSvr從postSvr接受push命令。然後會將所有push命令通過一個req 管道匯聚起來。這條集中的req管道後面是若干處理routine(pRoutine)。

這些處理pushRoutine與followedSvr保持長連接,從req管道獲取請求後會先向followedSvr查詢在線的粉絲。然後再根據userOnlineCache中的數據將所有粉絲按照frontNotifySvr進行分組。分組完成後再批量向frontNotifySvr發送noitfy。

pushSvr支持平行擴展,熱縮擴容,故障熔斷。這些能力都由postSvr和frontNotifySvr中的連接模塊實現。(這裏的實現沒有用統一接入的透明連接模式,因爲是所有svr都是內部開發嘛所以就由連接模塊來實現了)

pushSvr只負責在線用戶的通知,因爲微博系統中post操作相對與pull操作有數量級的差距,並且在線粉絲數量一般都與粉絲數差一個數量級。所以pushSvr只有在遇到大V發帖時纔會有壓力。在性能測試的數據模型中,大V粉絲數量在百萬級別,在線粉絲大概在十萬,目前的架構基本不會有性能壓力。即使真實的微博,千萬粉絲級的頭部大V發帖,其在線粉絲數也基本在百萬級別,一個pushRoutine也能在1~2秒內處理完並轉發給所有frontNotifySvr。

這裏並未實現離線粉絲的推送功能,這個功能是另一個話題與另外一個系統了。離線粉絲這裏會真正考驗推送系統的能力。目前Twitter的要求好像是5千萬級別的頭部大V要在5秒內完成全球範圍內的粉絲推送。

frontNotifySvr簡述

frontNotifySvr是客戶端進行業務命令接入服務。採用多服務器平行擴展的方式進行部署(由frontNotifySvrMng面向客戶端進行統一管理)。

frontNotifySvr通過grpc+protobuf接入。目前的實現中沒有做鑑權和加密,實際商用中肯定是需要的,不過這部分比較獨立這裏就暫時不做了(太忙,後續有時間再加)。

服務架構如下
整體架構圖
客戶端通過單向stream連接frontNotifySvr來接收notify,並通過心跳來維持在線狀態。客戶端創建notifyStream即表示登錄在線,grpc連接斷開或心跳超時則下線。

所有online/offline命令全部匯聚到reqChan管道中,由onlineProcRoutine負責向pushSvr和followedSvr轉發。

所有本svr服務的用戶的在線信息緩存在UserIdOnlineDataMap中,其中包含了userId對應的通知接收管道notifyChan。當notifyRecvRoutine從pushSvr接收到noitfy消息後就會在此緩存中用userid查找對應的notifyChan,然後插入notify消息。

frontNotifySvrMng簡述

frontNotifySvr是面向客戶端的通知推送服務。frontNotifySvr不是固定url,每次客戶端登錄都動態分配。frontNotifySvrMng就是面向客戶端對於frontNotifySvr的分配服務。客戶端在接入weibo服務時,只需要知道frontNotifySvrMng的url,通過查詢命令即可得到本次登錄分配給自己的frontNotifySvr的url。

frontNotifySvrMng支持對frontNotifySvr的熱縮擴容,實時熔斷,而負荷分擔則直接採用簡單的循環分配即可。這部分能力可以參照common/scaleAndBreak包說明。

frontNotifySvrMng的查詢量和連接數不會太大(每次客戶端連接微博服務時用短連接查詢一次即可,直到斷開連接後下次需要時再查。)我在性能測試時直接只用一臺服務器就行了,實際生產環境用主從熱備的兩個url應該就ok了。如果登錄量實在密集,比如每秒過十萬級,那麼可以用dns解析到多個url上。或者客戶端上保存多個url進行循環使用,平時多url全部解析到一個地址,忙時分散。frontNotifySvr對frontNotifySvrMng沒有向上依賴關係,所以一個和n個mng對frontNotifySvr是透明的。

dbSvr簡述

dbSvr作爲整個系統數據庫的接入服務。所有數據庫操作全部發送到dbSvr,由dbSvr集中處理。這樣可以統一操作,讓db設計對業務系統透明,也能集中進行db性能優化。

服務架構:
整體架構圖
各svr通過grpc與dbSvr通信。dbSvr針對每一個db維護一條req管道。管道後是若干處理routine。這樣保證各db的命令排隊分割開來。每個db都按照自身能力來提供服務。

DB可採用分片的方式,儘量將各DB的壓力均衡開。

rlogSvr簡述

rlogSvr是一個日誌和狀態數據遠程集中式服務。系統中的服務通過common/rlog中的接口輸出日誌、告警和狀態數據打點信息,統一發送到rlogSvr。便於集中日誌查看和狀態監控。

rlog提供如下接口:

  • Printf
  • Fatalf
  • Panicf
  • Warning
  • StatPoint

其中Printf/Fatalf/Panicf/Waring的接口除了會發送信息給rlogSvr,還會在本地通過log包輸出。

statMonitor簡述

rlogSvr會將實時的狀態打點數據刷新到數據庫中,monitorWsSvr定時讀取並提供webSocket服務定時向前端刷新。monitorWsSvr是用php workMan來寫的,一個很簡單的實現。

h5Monitor是一個用h5引擎做的簡陋的狀態打點數據監控頁面,提供數據警示(標黃)和數據告警(標紅)。非常簡陋但還能用:)。實際生產環境可以用更專業的監控系統。

scaleAndBreak包簡述

scaleAndBreak包提供frontSvrMng/frontNotifySvrMng服務的所需要的對管理svr的平行擴展、在線縮擴容、可用性檢測與故障熔斷能力。

我們可以通過直接修改配置文件來隨時增刪所管理的服務器url,服務會每秒定時讀取配置文件來刷新數據,實現在線熱擴容。

所有被管理的svr都需要提供一個可用性檢測的接口供調用,接口的實現由svr決定。通過該接口可以定時檢測該svr的可用性,一旦發現問題即從可用svr列表中刪掉實現故障熔斷。並且在下次刷新時再次檢測,如果發現之前熔斷的svr變得可用,則加入到可用svr列表中。

grpcStatHandler包簡述

grpcStatHandler包提供grpc連接的統計和回調。便於數據統計以及在線狀態維護。

dbConnPool包簡述

dbConnPool包提供數據庫連接池服務。創建固定數量的數據庫連接供循環使用。避免所有併發都去自己連接數據庫,造成數據庫連接數膨脹。

dbSvrConnPool包簡述

dbSvrConnPool包提供dbSvr連接池服務。創建固定數量的dbSvr連接供循環使用。避免所有併發都去自己連接dbSvr,造成dbSvr連接數膨脹。

DB設計

這裏專門介紹一下我針對weibo系統的數據庫設計。

weibo系統只實現了最基本的pull/post/follow/unfollow命令,所以表也只有少數幾個。

  • Follow表,存儲用戶的關注列表。字段有userid和followid。按照userid進行分爲300張表,userid和followid作爲聯合index。
  • UserLevel表,存儲用戶等級。100粉以下爲0級,1萬粉以下爲1級,10萬粉以下爲2級,100萬粉以下爲3級,100萬粉以上爲4級。字段有userid,level,followerCount,和其他字段(後面詳細介紹)。按照userid分20張表。userid爲index。
  • Followed表,存儲用戶粉絲表。字段有userid和followedid(粉絲id)。分表規則按照UserLevel進行。0~3級用戶分別單獨分表,每級100張。4級用戶每個用戶單獨一張表。
  • UserMsgId表,存儲用戶發帖的id。字段有userid和msgid。按照userid分表,userid和msgid作爲聯合index。
  • ContentMsg表,存儲微博消息。字段有msgid和其他內容信息。按照msgid分爲100張表,msgid爲index。

這裏需要詳細說明的userlevel表和Followed。因爲用戶粉絲數差距實在是太大,所以不能一股腦全部同樣對待。這裏就按照分數數進行分級,然後按等級分別對待。

但考慮到用戶等級是可能上升的,怎麼辦呢?這裏就在userlevel表中增加了intrans和transbegintime字段。每晚可以定期用userLevelTrans程序掃描level表,查看粉絲數。如果達到升級標準,則將intrans字段和transBeginTime置位,然後進行數據遷移。dbSvr在follow和unfollow時如果看到intrans字段置位,則不再修改對應的followed表,而將數據寫入TransFollowed和TransUnFollowed表中。userLevelTrans遷移數據完成後,修改level和intrans字段,再將TransFollowed和TransUnFollowed表中記錄的數據轉入Followed表。ok

性能測試報告

微博類系統我認爲是互聯網業務系統中最複雜和最喫性能的。簡單舉兩個最常用的操作爲例:

  • pull操作分析:假設平均一個用戶關注30個人,那麼他的一次pull就會包含查詢所有這30個人的最新若干條消息。然後拉通按照時間進行從近到遠排序,選出若干條(假設15條)。然後再去查詢出這15條微博的內容,再返回給該用戶。這才完成一個用戶的一次pull。而微博系統中pull的觸發量是非常大的。一個在線用戶如果在瀏覽時間線,那可能一分多鐘就會觸發一次。一百萬在線光pull就會產生10萬的QPS。
  • post操作分析:一個大V就是幾百萬幾千萬的粉絲量。他的一次post,就會觸發對這些粉絲的消息推送。其中有在線的,有不在線的。twitter對推送的要求是一個5千萬粉的大V的一條消息需要在5秒內送達所有粉絲。

這個微博系統的設計和實現規格是支持百萬級在線,十萬級QPS。要支撐這個量級大概需要部署一百臺左右的服務器(我算了一下賬,發現個人私房錢有點撐不住),所有退而求其次,準備用一個縮量的配置(20臺左右服務器),做一個十萬在線一萬QPS的性能測試。

測試環境服務器部署和規格設計:

  • frontSvr,2臺(8核16G)

  • frontNotifySvr,2臺(8核16G)

  • pullSvr,3臺(16核32G)

  • usrMsgIdSvr,2x2臺(8核16G)

  • contentSvr,2x2臺(8核16G)

  • followSvr,1臺(8核32G)

  • followedSvr,1臺(8核32G)

  • pushSvr,1臺(8核16G)

  • frontSvrMng,notifySvrMng,releationChangeSvr,msgIdGen,postSvr,共享1臺 (8核16G)

  • dbSvr,1臺(8核16G)

  • mongodb,4臺(8核32G)

數量上,包含db一共22臺服務器。配置上,也算普通不算奢華。

測試數據設計

這個系統的測試數據設計還是花了不少心思,要保證量,還要保證比較符合真實情況,這樣才能模擬出比較真實的壓力環境。

測試數據生成的程序在test/performTestDataGen裏。

用戶關係數據設計如下:

  • //用戶id(10000000~19999999)一千萬用戶,每人10個粉絲。模擬普通用戶。
  • //用戶id(20000000~20009999)一萬用戶,每人一千粉絲。
  • //用戶id(20010000~20007999)八千用戶,每人三千粉絲
  • //用戶id(20018000~20019999)兩千用戶,每人一萬粉絲
  • //用戶id(20020000~20020999)一千用戶,每人兩萬粉絲
  • //用戶id(20021000~20021799)八百用戶,每人三萬粉絲
  • //用戶id(20021800~20019999)兩百用戶,每人十萬粉絲
  • //用戶id(20022000~20022099)一百用戶,每人二十萬粉絲
  • //用戶id(20022100~20022199)一百用戶,每人三十萬粉絲
  • //用戶id(20022200~20022299)一百用戶,每人四十萬粉絲
  • //用戶id(20022300~20022302)三個用戶,每人100萬粉絲
  • //用戶id(20022303~20022305)三個用戶,每人150萬粉絲
  • //用戶id(20022306~20022308)三個用戶,每人200萬粉絲
  • //用戶id(20022309~20022309)一個用戶,每人300萬粉絲

總用戶數10022310。總follow的關係有3億多。粉絲會按照算法隨機到所有一千萬普通用戶中。平均每個用戶大概關注了30多個人。

發帖數設計如下:

  • //普通用戶0~15條微博
  • //10萬粉以下用戶15~150條微博
  • //其他用戶50~300條

搞數據是個艱辛的活。唉,說多了都是淚啊

測試用戶行爲模擬

用戶操作的模擬也是比較費神。

操作模擬如下:

  • 所有在線用戶,每人平均每10秒一次pull操作
  • 每1千在線普通用戶,每秒選1個用戶進行一次post操作
  • 測試時人工手動用功能測試工具登錄大V進行post操作

測試過程簡述

性能測試整個耗費了好幾天,包括做數據(數次),搭環境(數次),正式測試(數次),問題修正(若干)。期間發現了不少功能測試時沒有能發現的問題。服務器+流量花費大概1千RMB左右吧(所以說一百多臺的全性能測試做不起啊)。

測試報告

整體架構圖

上圖是10萬在線1萬QPS時的狀態監控截圖。所有隊列無阻塞,無一條異常日誌輸出。整個系統如絲般潤滑 :)

不過我繼續加壓下到1.4萬QPS時,出現大量的pull超時。當時也是緊張出了一身汗。當時發現ssh終端都動不了了,突然想到是不是帶寬滿了。趕緊上雲監控上看了下,果不其然。兩臺frontSvr設定的40Mbps帶寬全部喫滿,能不超時嗎?趕緊撤下部分客戶端,恢復到1萬QPS。這個時候系統又恢復了美妙的安寧。

系統穩定時,查看了整個系統所有svr的負載情況,發現除了frontSvr的內存和帶寬接近滿負荷,frontNotifySvr內存接近滿負荷外,其他svr都尚有較大餘量。所以大膽推測,如果再加上兩臺frontSvr和兩臺frontNotifySvr,就這樣20來臺的集羣配置大概支撐2萬QPS是沒啥問題的。不過,荷包燒的心痛就不去做了。有同學贊助的話可以再玩玩大的:)

期間我手動登錄大V,發起post。在線推送基本都是一秒內客戶端就收到notify。但由於登錄量有限,實際在線的粉絲大概是幾千級別。

如下是各svr的負載截圖:

整體架構圖

frontSvr兩臺,每臺cpu基本喫掉3個核,內存基本耗盡,帶寬佔到40Mbps。整個系統配置的瓶頸就在這裏。

整體架構圖

pullSvr三臺,每臺喫掉6個核。還剩一半多的富裕。

整體架構圖

userMsgIdSvr,2x2臺,每臺喫掉2個核。富裕較大。

整體架構圖

contentSvr,2x2臺,每臺喫掉不到2個核,富裕較大。

整體架構圖

followSvr一臺,喫掉3個核,內存佔用也較少。富裕較大。不過真實系統的話cache的數據會大很多,畢竟壓力測試時並沒有讓所有用戶id都登錄一遍。

整體架構圖

followedSvr一臺,喫掉1個核,內存佔用也較少。富裕較大。不過真實系統大V的活躍比例會較高,cache的粉絲數會很多。

整體架構圖

pushSvr一臺,喫掉1個核。不過這個不是在我用大v post的時候截圖的。但根據notify收到的迅捷程度來看,大V post時壓力也不過太大。

整體架構圖

frontNotifySvr兩臺,喫掉1核,內存基本耗盡。這裏的內存也是一個配置瓶頸,增加兩臺的配置整體系統負載能提高一倍。

整體架構圖

多服務共享服務器,這上面都是業務量不大的svr,沒啥壓力。

整體架構圖

dbSvr一臺,沒啥壓力。由於大多數據已經被各svr cache了,所以db壓力幾乎沒有。

下圖是一臺frontSvr的網絡統計。可見外網出帶寬40Mbps在高峯期完全被耗盡。

整體架構圖

至此,這一輪性能測試算是圓滿。cheers

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章