春節(jié)初一~初三期間,紅包活動頁左上角會有春節(jié)排行榜的入口,前期預(yù)估峰值:15w/s。
排行榜server優(yōu)化前后數(shù)據(jù)對比如下(備注:以下數(shù)據(jù),均假設(shè)從CKV中拿到數(shù)據(jù),忽略掉oidb相關(guān)邏輯):
測試條件:CPU占比不超過85%,超時時間不超過900ms
| 優(yōu)化前單機QPS | 優(yōu)化后單機QPS | |
|---|---|---|
| 獲取排行榜首頁數(shù)據(jù)(實時拉取好友步數(shù)并進行排序) | 2200 | 5200 ( +3000) |
| 獲取排行榜分頁數(shù)據(jù)(從Redis快照中直接獲取數(shù)據(jù)) | 3000 | 5500 ( +2500) |
| 用戶點贊 | 12000 | 12000 |
| 發(fā)送C2C消息 | 12000 | 12000 |
針對春節(jié)排行榜運動側(cè)準備了120臺V8機器。以排行榜首頁為例,優(yōu)化前,系統(tǒng)QPS(極限值) = 2200*120 = 26.4w/s,優(yōu)化后,系統(tǒng)QPS(極限值) = 5200*120 = 62.4w/s,提升136.4%。
以CPU占比75%平穩(wěn)運行而言,系統(tǒng)QPS = 4700*120 = 56.4w/s,此時平均每個請求從發(fā)包到收包耗時約40ms。

所有接口中,由于排行榜首頁,做的邏輯更多更復(fù)雜(包括拉取關(guān)系鏈、取所有好友步數(shù)、排序、取用戶信息、取會員標記、取點贊數(shù)據(jù)等),因此,不出意外,首頁QPS是最低的。另外,春節(jié)期間排行榜所有請求中,首頁的請求應(yīng)該是最多的,畢竟,用戶進入頁面首先就會請求首頁數(shù)據(jù)。
綜上所述,優(yōu)化排行榜首頁性能成為了整個系統(tǒng)的關(guān)鍵所在。這次優(yōu)化,也是根據(jù)這條思路進行的,下面簡要介紹下優(yōu)化的思路和過程。
一、排行榜邏輯架構(gòu)圖

要點如下:
- 存儲:主要采用CKV,
- 外部接口:能異步就異步(除oidb查會員標記位外)
- 框架:SPP微線程,相關(guān)網(wǎng)絡(luò)操作均采用異步。
- 備注:SSO尋址走hash一致性尋址,server本地采用Redis做快照,防止排名錯亂的問題。
二、壓測數(shù)據(jù)
工欲善其事,必先利其器。這里不得不提到test.server.com這個壓測利器,文中所有數(shù)據(jù)和結(jié)果均來自test.server.com提供的壓測客戶端。
優(yōu)化的步驟無外乎:
A. 壓測得到當前Server性能(CPU不超過80%,延遲不超過900ms)
B. Perf查看CPU消耗點在哪里(因為排行榜這里是CPU Bound型)
C. 針對B的結(jié)果相應(yīng)優(yōu)化,再重復(fù)進行步驟A。
1. 原始壓測數(shù)據(jù)(未做優(yōu)化前):壓測到2200/s時,CPU占比已經(jīng)飆升至80%。


分析后得知,Server中的Json和map的相關(guān)操作吃了很大一部分CPU,約占用30%左右。
2. 優(yōu)化map操作,將代碼中所有涉及到map的邏輯全部替換為hash_map和vector。思路:map底層采用RB_Tree的方式實現(xiàn),查找復(fù)雜度為OLog(n);hash_map底層采用Hash_table實現(xiàn),查找復(fù)雜度為O(1)。
| 優(yōu)化前QPS | 優(yōu)化后QPS | 優(yōu)化措施 |
|---|---|---|
| 2200 | 3200 (+1000) | hash_map和vector替換map操作 |
備注:C99里面的hash_map,不是標準庫,是gcc實現(xiàn)的:__gnu_cxx::hash_map。這里性能提升明顯,主要是獲取步數(shù)數(shù)據(jù)、用戶關(guān)系鏈數(shù)據(jù)及相互匹配時,查找操作較多。
優(yōu)化后Perf圖如下(搜索關(guān)鍵字<map>如下圖,CPU占比11%左右):
右圖中,之所以map匹配CPU占比超過11%,因為Json相關(guān)的操作中大量的使用了map,而Json還沒有進行優(yōu)化。搜索hash_map,CPU占比只有5%左右。
3. 優(yōu)化Json操作。思路:將Json庫替換為簡單字符串拼接(這里由于是和前端交互,限定了只能使用json交互,因此優(yōu)化的思路還是從組裝json方面考慮,而不是替換為二進制協(xié)議,例如pb等)。
| 優(yōu)化前QPS | 優(yōu)化后QPS | 優(yōu)化措施 |
|---|---|---|
| 3200 | 4000 (** +800**) | json組裝,改為自己拼接字符串 |


右圖中,可以看到,優(yōu)化完成后,占用CPU最多的已經(jīng)變?yōu)閟sdasn::的相關(guān)操作,這些操作是CKV存儲的編解碼封裝,也就是說,后續(xù)的性能優(yōu)化已經(jīng)和業(yè)務(wù)無關(guān)了。
此時CPU使用如下,占比約80%左右:
4. 減少日志流水操作。思路:忽略正常處理的請求,只打印出錯請求處理日志。
| 優(yōu)化前QPS | 優(yōu)化后QPS | 優(yōu)化措施 |
|---|---|---|
| 4000 | 4500 (+500) | 減少日志打印,只打印出錯日志 |



5. gcc編譯增加O2選項。思路:利用編譯器自身的編譯優(yōu)化選項,從編譯執(zhí)行的底層嘗試優(yōu)化性能(業(yè)務(wù)無關(guān))
| 優(yōu)化前QPS | 優(yōu)化后QPS | 優(yōu)化措施 |
|---|---|---|
| 4500 | 5200 (** +700**) | 使用gcc O2優(yōu)化選項 |



三、走過的彎路
在初步壓測出單機QPS=2200后,由于缺乏經(jīng)驗,沒有從火焰圖去分析CPU到底消耗在哪里。因此只能嘗試各種經(jīng)驗上的方法進行優(yōu)化,例如:
- 去掉同步快照操作(傷害用戶體驗)
- 去掉關(guān)系鏈CKV同步操作(更換成本高)
- 調(diào)整批量獲取CKV的數(shù)量(經(jīng)驗值)
- 調(diào)大進程數(shù)
- 關(guān)閉系統(tǒng)日志
- 關(guān)閉排序(排行榜基礎(chǔ)功能)
等...
這些效果都不明顯,提升作用有效,最多的時候,有損體驗的前提下,QPS才提升到2500左右。
后面在查閱相關(guān)資料后,系統(tǒng)化的使用perf、火焰圖等工具進行分析,抓到性能瓶頸后,有的放矢,才能在后面的優(yōu)化過程中,有效的提升系統(tǒng)QPS。
這次優(yōu)化,從接觸學(xué)習壓測工具開始,到昨天優(yōu)化告一段落,斷斷續(xù)續(xù)持續(xù)了有3、4天左右。
參考資料
gcc 編譯優(yōu)化選項O2相關(guān)知識,可以參考這篇文章GCC中-O1 -O2 -O3 優(yōu)化的原理
另外,O2選項優(yōu)化打開時,注意另外一個選項:-fno-strict-aliasing,如果沒有配合使用的話,程序可能產(chǎn)生未定義的行為,大意是說O2選項默認認為不同類型的指針不能指向同一片內(nèi)存區(qū),如果業(yè)務(wù)代碼中,有強制類型轉(zhuǎn)換,需要注意下這里。具體參考:gcc 編譯參數(shù) -fno-strict-aliasing
官方說明在此:Options That Control Optimization
我的理解:O2性能優(yōu)化選項打開前后代碼語義必然相同,最主要的性能優(yōu)化點,可能還是:未打開前,Gcc編譯生成的代碼是獨立的,每一行代碼都可以打斷,方便調(diào)試;打開后,Gcc編譯生成的代碼是相關(guān)的,并根據(jù)一些相關(guān)性進行了優(yōu)化,當然這時候,調(diào)試的難度就很大了。
