淺談高併發系統性能調優
女主宣言
今天帶來的是一個篇長文,主要講解高併發系統架構指標及調優測試經驗,希望能對您的研究有所幫助。本文最先發佈於 OpsDev,轉載已獲取作者授權。
PS:豐富的一線技術、多元化的表現形式,盡在“HULK一線技術雜談”,點關注哦!
The Future of Space Exploration
by NASA IOTD
高併發系統的優化一直以來都是一個很重要的問題,下面基於筆者的實踐,和大家聊聊高併發系統的一些調優和優化策略。
1
系統性能的關鍵指標
吞吐量(Throughput) 系統單位時間內處理任務的數量
延遲(Latency) 系統對單個任務的平均響應時間
一般來說,考量一個系統的效能主要看這兩個指標。而這兩個指標之間又存在著一些聯絡:對於指定的系統來說,系統的吞吐量越大,處理的請求越多,伺服器就越繁忙,響應速度就會慢下來;而延遲越低的系統,能夠承載的吞吐量也相應的更高一些。
一方面,我們需要提高系統的吞吐量,以便服務更多的使用者,另一方面我們需要將延遲控制在合理的範圍內,以保證服務質量。
2
系統性能測試
業務場景
對於不同的業務系統,可以接受的延遲(Latency)也有所不同,例如郵件服務可以忍受的延遲顯然要比Web服務高得多,所以首先我們需要根據業務場景的不同來定義理想的Latency值。
測試工具
我們需要一個能夠製造高吞吐的工具來測試系統的效能,本文中使用的Tsung,它是一個開源的支援分散式的壓力測試工具,它功能豐富,效能強大,配置簡單,並支援多種協議(HTTP、MySQL、LDAP、MQTT、XMPP等)。
測試流程
測試的過程中需要不斷加大吞吐量,同時注意觀察服務端的負載,如果負載沒有問題,那就觀察延遲。一般這個過程需要反覆很多次才能測出系統的極限值,而每次測試消耗的時間也比較長,需要耐心一些。
3
通用的系統引數調優
Linux核心預設的引數考慮的是最通用的場景,不能夠滿足高併發系統的需求。
4
伺服器引數調優
編輯檔案/etc/sysctl.conf,新增以下內容:
fs.nr_open = 100000000
fs.file-max = 100000000
關於這兩項配置的含義可以檢視系統手冊:
可以看到file-max的含義:它是系統所有程序一共可以開啟的檔案數量,它是系統級別的,因此應該儘量將它調的大一些,這裡將它修改為一億。 這裡說到需要增大/proc/sys/fs/inode-max的值,但是執行如下命令時發現沒有該配置項:
$ cat /proc/sys/fs/inode-max
cat: /proc/sys/fs/inode-max: No such file or directory
再看inode-max說明:
可以看到有的系統中沒有這個引數,所以先不管了。
對於nr_open,系統手冊裡沒有找到關於它的定義,不過我們可以參考kernel文件:
https://www.kernel.org/doc/Documentation/sysctl/fs.txt
nr_open:
This denotes the maximum number of file-handles a process can allocate. Default value is 1024*1024 (1048576) which should be enough for most machines. Actual limit depends on RLIMIT_NOFILE resource limit.
可以看到nr_open的描述與file-max十分相近,不仔細看幾乎分辨不出區別。重點在於,file-max是對所有程序(all processes)的限制,而nr_open是對單個程序(a process)的限制。這裡給出了nr_open的預設值:
1024*1024 = 1048576
編輯檔案/etc/security/limits.conf,新增如下內容:
編輯檔案/etc/sysctl.conf,新增以下內容:
* hard nofile 4194304
* soft nofile 4194304
關於這兩項配置的含義可以檢視系統手冊:
這裡的意思很明確:hard意為硬資源限制:一旦被superuser設定後不能增加;soft為軟資源設定:設定後在程式執行期間可以增加,但不能超過hard的限制。程式讀取的是soft,它是一個告警值。 nofile意為使用者開啟最大檔案描述符數量,在Linux下執行的網路伺服器程式,每個tcp連線都要佔用一個檔案描述符,一旦檔案描述符耗盡,新的連線到來就會返回"Too many open files"這樣的錯誤,為了提高併發數,需要提高這項配置的數值。
這裡有一點需要特別注意,而手冊裡面也沒有細說:在CentOS7下(其他系統還未測試過),nofile的值一定不能高於nr_open,否則使用者ssh登入不了系統,所以操作時務必小心:可以保留一個已登入的root會話,然後換個終端再次嘗試ssh登入,萬一操作失敗,還可以用之前保留的root會話搶救一下。
修改完畢執行:
# sysctl -p
通過以上,我們修改了/etc/security/limits.conf和/etc/sysctl.conf兩個配置檔案,它們的區別在於limits.conf是使用者層面的限制,而sysctl.conf是針對整個系統層面的限制。
5
壓測客戶機引數調優
對於壓測機器來說,為了模擬大量的客戶端,除了需要修改檔案描述符限制外,還需要配置可用埠範圍,可用埠數量決定了單臺壓測機器能夠同時模擬的最大使用者數量。
檔案描述符數量:修改過程同伺服器
可用埠數量:1024以下的埠是作業系統保留的,我們可用的埠範圍是1024-65535,由於每個TCP連線都要用一個埠,這樣單個IP可以模擬的使用者數大概在64000左右 修改/etc/sysctl.conf檔案,新增如下內容:
net.ipv4.ip_local_port_range = 1024 65535
修改完畢執行:
# sysctl -p
需要注意的是,伺服器最好不要這樣做,這是為了避免服務監聽的埠被佔用而無法啟動。如果迫於現實(例如手頭可用的機器實在太少),伺服器必須同時用作壓測機器的話,可以將服務監聽埠新增到ip_local_reserved_ports中。下面舉例說明:
修改/etc/sysctl.conf檔案,新增如下內容:
net.ipv4.ip_local_reserved_ports = 5222, 5269, 5280-5390
修改完畢執行:
# sysctl -p
TCP/IP協議棧從ip_local_port_range中選取埠時,會排除ip_local_reserved_ports中定義的保留埠,因此就不會出現服務埠被佔用而無法啟動的情況。
6
程式調優
對於不同的業務系統,需要有針對性的對其進行調優,本文中測試的目標服務使用Erlang/OTP寫就,Erlang/OTP本身帶有許多的限制,對於一般場景來說這些預設的設定是足夠的;但是為了支援高併發,需要對Erlang虛擬機器進行一些必要的引數調優,具體可以參考官方效能指南:
http://erlang.org/doc/efficiency_guide/advanced.html#system-limits
7
服務程式引數調優
程序(process)數量
Erlang虛擬機器預設的程序數量限制為2^18=262144個,這個值顯然是不夠的,我們可以在erl啟動時新增引數+P來突破這個限制
$ erl +P 10000000
需要注意的是:這樣啟動,erlang虛擬機器的可用程序數量可能會比10000000大,這是因為erlang通常(但不總是)選擇2的N次方的值作為程序數量上限。
原子(atom)數量
Erlang虛擬機器預設的原子數量上限為1048576,假如每個會話使用一個原子,那麼這個預設值就不夠用了,我們可以在erl啟動時新增引數+t:
$ erl +t 10000000
從另一個角度來說,我們在編寫Erlang程式時,使用原子需要特別小心:因為它消耗記憶體,而且不參與GC,一旦建立就不會被移除掉;一旦超出原子的數量上限,Erlang虛擬機器就會Crash,參見 How to Crash Erlang。
https://prog21.dadgum.com/43.html
埠(port)數量 埠提供了與外部世界通訊的基本機制(這裡的埠與TCP/IP埠的概念不同,需要注意區別),每個Socket連線需要消耗1個埠,官方文件裡面說預設埠上限通常是16384,但根據實測,Linux系統預設為65536,Windows系統預設為8192,無論多少,在這裡都是需要調整的:在erl啟動時新增引數+Q Number,其中Number取值範圍是[1024-134217727]:
$ erl +Q 10000000
8
壓測指令碼調優
壓測工具使用Tsung。Tsung支援多種協議,有著豐富的功能,並且配置簡單靈活。下面是幾點需要注意的地方:
記憶體
對於單個Socket連線來說消耗記憶體不多,但是幾萬甚至幾十萬個連線疊加起來就非常可觀了,配置不當會導致壓測端記憶體成為瓶頸。
TCP 傳送、接收快取 Tsung預設的TCP/UDP快取大小為32KB,本例中我們測試的服務採用MQTT協議,它是一種非常輕量級的協議,32KB還是顯得過大了,我們將它設定為4KB大小就足夠了:
<option name="tcp_snd_buffer" value="4096"></option>
<option name="tcp_rcv_buffer" value="4096"></option>
Tsung能夠讓模擬使用者的程序在空閒時間(thinktime)進入休眠,用以降低記憶體消耗,預設空閒10秒觸發,我們可以將它降低到5秒:
<option name="hibernate" value="5"></option>
IO
不要啟用dumptraffic,除非用於除錯,因為它需要將客戶機和伺服器往來的協議寫入磁碟日誌中,是一個IO開銷非常大的行為。筆者曾經遇到過一次這樣的問題,測試部門的同事使用JMeter,壓測過程中,服務端一直處於較低的負載,但JMeter最終得出的壓測報告卻包含很多超時錯誤。經過仔細排查,才定位到原來是壓測端預設開啟了debug日誌,海量的debug日誌生生拖垮了壓測機器。所以遇到這種情況時,可以先檢查一下壓測端配置是否正確。
<tsung loglevel="error" dumptraffic="false" version="1.0">
日誌等級要調高一些,日誌等級過低會列印很多無用資訊:一方面會加大IO開銷,另一方面會讓有用的ERROR資訊淹沒在海量的除錯日誌中。
如果Tsung從CSV檔案讀取使用者名稱密碼,那麼該CSV檔案不能過大,否則讀取該CSV將會變成一個極其耗時的操作,特別是當壓測程式需要每秒產生大量使用者時。
網路
有時候為了避免網路擁塞,需要限制壓測客戶機的頻寬,使流量以比較平滑的速率傳送和接收。
<option name="rate_limit" value="1024"></option>
其採用令牌桶演算法(token bucket),單位KB/s,目前只對流入流量有效。
9
定位系統性能瓶頸
當系統吞吐和延遲上不去時,首先需要定位問題,而不是急於修改程式碼。
常見的效能瓶頸包括CPU/記憶體/磁碟IO/網路頻寬等,其中每一項都有一到多個簡單實用的工具: 對於CPU和記憶體,我們只要使用top就可以了;對於磁碟IO,可以用iotop或iostat;對於網路頻寬,可以使用iftop。
如果依然沒能定位到問題,可能系統配置不當,參考通用的系統引數調優。
最後檢查程式碼是否有單點瓶頸,例如程式被阻塞了:在筆者實測過程中,發現每個使用者建立會話程序都需要對同一個supervisor發起同步請求,同時登入的使用者數量很大時,這些同步請求會排隊,甚至引發超時。
10
結束語
以上就是筆者做壓力測試時遇到的一些問題以及應對辦法,鑑於筆者水平有限,錯漏難免。拋磚引玉,歡迎交流指正。
HULK一線技術雜談
由360雲平臺團隊打造的技術分享公眾號,內容涉及雲端計算、資料庫、大資料、監控、泛前端、自動化測試等眾多技術領域,通過夯實的技術積累和豐富的一線實戰經驗,為你帶來最有料的技術分享