高併發系統的優化一直以來都是一個很重要的問題,下面基於筆者的實踐,和大家聊聊高併發系統的一些調優和優化策略
系統性能的關鍵指標
吞吐量(Throughput) 系統單位時間內處理任務的數量
延遲(Latency) 系統對單個任務的平均響應時間
一般來說,考量一個系統的性能主要看這兩個指標。而這兩個指標之間又存在着一些聯繫:對於指定的系統來說,系統的吞吐量越大,處理的請求越多,服務器就越繁忙,響應速度就會慢下來;而延遲越低的系統,能夠承載的吞吐量也相應的更高一些。
一方面,我們需要提高系統的吞吐量,以便服務更多的用戶,另一方面我們需要將延遲控制在合理的範圍內,以保證服務質量。
系統性能測試
業務場景 對於不同的業務系統,可以接受的延遲(Latency)也有所不同,例如郵件服務可以忍受的延遲顯然要比Web服務高得多,所以首先我們需要根據業務場景的不同來定義理想的Latency值
測試工具 我們需要一個能夠製造高吞吐的工具來測試系統的性能,本文中使用的Tsung,它是一個開源的支持分佈式的壓力測試工具,它功能豐富,性能強大,配置簡單,並支持多種協議(HTTP、MySQL、LDAP、MQTT、XMPP等)。
測試流程 測試的過程中需要不斷加大吞吐量,同時注意觀察服務端的負載,如果負載沒有問題,那就觀察延遲。一般這個過程需要反覆很多次才能測出系統的極限值,而每次測試消耗的時間也比較長,需要耐心一些。
通用的系統參數調優
Linux內核默認的參數考慮的是最通用的場景,不能夠滿足高併發系統的需求
服務器參數調優
編輯文件
/etc/sysctl.conf
,添加以下內容:關於這兩項配置的含義可以查看系統手冊:
可以看到file-max的含義:它是系統所有進程一共可以打開的文件數量,它是系統級別的,因此應該儘量將它調的大一些,這裏將它修改爲一億。 這裏說到需要增大/proc/sys/fs/inode-max的值,但是執行如下命令時發現沒有該配置項
再看inode-max說明:
可以看到有的系統中沒有這個參數,所以先不管了。
對於
nr_open
,系統手冊裏沒有找到關於它的定義,不過我們可以參考kernel文檔:可以看到
nr_open
的描述與file-max
十分相近,不仔細看幾乎分辨不出區別。重點在於,file-max
是對所有進程(all processes)的限制,而nr_open
是對單個進程(a process)的限制。這裏給出了nr_open
的默認值:1024*1024 = 1048576
編輯文件
/etc/security/limits.conf
,添加如下內容:關於這兩項配置的含義可以查看系統手冊:
這裏的意思很明確:
hard
意爲硬資源限制:一旦被superuser設置後不能增加;soft
爲軟資源設置:設置後在程序運行期間可以增加,但不能超過hard
的限制。程序讀取的是soft
,它是一個告警值。nofile
意爲用戶打開最大文件描述符數量,在Linux下運行的網絡服務器程序,每個tcp連接都要佔用一個文件描述符,一旦文件描述符耗盡,新的連接到來就會返回"Too many open files"這樣的錯誤,爲了提高併發數,需要提高這項配置的數值。這裏有一點需要特別注意,而手冊裏面也沒有細說:在CentOS7下(其他系統還未測試過),nofile的值一定不能高於
nr_open
,否則用戶ssh登錄不了系統,所以操作時務必小心:可以保留一個已登錄的root會話,然後換個終端再次嘗試ssh登錄,萬一操作失敗,還可以用之前保留的root會話搶救一下。修改完畢執行:
通過以上,我們修改了
/etc/security/limits.conf
和/etc/sysctl.conf
兩個配置文件,它們的區別在於limits.conf
是用戶層面的限制,而sysctl.conf
是針對整個系統層面的限制。
壓測客戶機參數調優
對於壓測機器來說,爲了模擬大量的客戶端,除了需要修改文件描述符限制外,還需要配置可用端口範圍,可用端口數量決定了單臺壓測機器能夠同時模擬的最大用戶數量。
文件描述符數量:修改過程同服務器
可用端口數量:1024以下的端口是操作系統保留的,我們可用的端口範圍是1024-65535,由於每個TCP連接都要用一個端口,這樣單個IP可以模擬的用戶數大概在64000左右 修改
/etc/sysctl.conf
文件,添加如下內容:修改完畢執行:
需要注意的是,服務器最好不要這樣做,這是爲了避免服務監聽的端口被佔用而無法啓動。如果迫於現實(例如手頭可用的機器實在太少),服務器必須同時用作壓測機器的話,可以將服務監聽端口添加到
ip_local_reserved_ports
中。下面舉例說明:修改
/etc/sysctl.conf
文件,添加如下內容:修改完畢執行:
TCP/IP協議棧從
ip_local_port_range
中選取端口時,會排除ip_local_reserved_ports
中定義的保留端口,因此就不會出現服務端口被佔用而無法啓動的情況。
程序調優
對於不同的業務系統,需要有針對性的對其進行調優,本文中測試的目標服務使用Erlang/OTP寫就,Erlang/OTP本身帶有許多的限制,對於一般場景來說這些默認的設置是足夠的;但是爲了支持高併發,需要對Erlang虛擬機進行一些必要的參數調優,具體可以參考官方性能指南
服務程序參數調優
進程(process)數量 Erlang虛擬機默認的進程數量限制爲2^18=262144個,這個值顯然是不夠的,我們可以在erl啓動時添加參數
+P
來突破這個限制需要注意的是:這樣啓動,erlang虛擬機的可用進程數量可能會比10000000大,這是因爲erlang通常(但不總是)選擇2的N次方的值作爲進程數量上限。
原子(atom)數量 Erlang虛擬機默認的原子數量上限爲1048576,假如每個會話使用一個原子,那麼這個默認值就不夠用了,我們可以在erl啓動時添加參數
+t
:從另一個角度來說,我們在編寫Erlang程序時,使用原子需要特別小心:因爲它消耗內存,而且不參與GC,一旦創建就不會被移除掉;一旦超出原子的數量上限,Erlang虛擬機就會Crash,參見 How to Crash Erlang。
端口(port)數量 端口提供了與外部世界通訊的基本機制(這裏的端口與TCP/IP端口的概念不同,需要注意區別),每個Socket連接需要消耗1個端口,官方文檔裏面說默認端口上限通常是16384,但根據實測,Linux系統默認爲65536,Windows系統默認爲8192,無論多少,在這裏都是需要調整的:在erl啓動時添加參數
+Q Number
,其中Number取值範圍是[1024-134217727]:
壓測腳本調優
壓測工具使用Tsung。Tsung支持多種協議,有着豐富的功能,並且配置簡單靈活。下面是幾點需要注意的地方:
內存
對於單個Socket連接來說消耗內存不多,但是幾萬甚至幾十萬個連接疊加起來就非常可觀了,配置不當會導致壓測端內存成爲瓶頸
TCP 發送、接收緩存 Tsung默認的TCP/UDP緩存大小爲32KB,本例中我們測試的服務採用MQTT協議,它是一種非常輕量級的協議,32KB還是顯得過大了,我們將它設置爲4KB大小就足夠了:
Tsung能夠讓模擬用戶的進程在空閒時間(thinktime)進入休眠,用以降低內存消耗,默認空閒10秒觸發,我們可以將它降低到5秒:
IO
不要啓用dumptraffic,除非用於調試,因爲它需要將客戶機和服務器往來的協議寫入磁盤日誌中,是一個IO開銷非常大的行爲。筆者曾經遇到過一次這樣的問題,測試部門的同事使用JMeter,壓測過程中,服務端一直處於較低的負載,但JMeter最終得出的壓測報告卻包含很多超時錯誤。經過仔細排查,才定位到原來是壓測端默認開啓了debug日誌,海量的debug日誌生生拖垮了壓測機器。所以遇到這種情況時,可以先檢查一下壓測端配置是否正確。
日誌等級要調高一些,日誌等級過低會打印很多無用信息:一方面會加大IO開銷,另一方面會讓有用的ERROR信息淹沒在海量的調試日誌中。
如果Tsung從CSV文件讀取用戶名密碼,那麼該CSV文件不能過大,否則讀取該CSV將會變成一個極其耗時的操作,特別是當壓測程序需要每秒產生大量用戶時。
網絡
有時候爲了避免網絡擁塞,需要限制壓測客戶機的帶寬,使流量以比較平滑的速率發送和接收
其採用令牌桶算法(token bucket),單位KB/s,目前只對流入流量有效。
定位系統性能瓶頸
當系統吞吐和延遲上不去時,首先需要定位問題,而不是急於修改代碼。
常見的性能瓶頸包括CPU/內存/磁盤IO/網絡帶寬等,其中每一項都有一到多個簡單實用的工具: 對於CPU和內存,我們只要使用top就可以了;對於磁盤IO,可以用iotop或iostat;對於網絡帶寬,可以使用iftop。
如果依然沒能定位到問題,可能系統配置不當,參考通用的系統參數調優。
最後檢查代碼是否有單點瓶頸,例如程序被阻塞了:在筆者實測過程中,發現每個用戶創建會話進程都需要對同一個supervisor發起同步請求,同時登錄的用戶數量很大時,這些同步請求會排隊,甚至引發超時。