淺談高併發系統性能調優

高併發系統的優化一直以來都是一個很重要的問題,下面基於筆者的實踐,和大家聊聊高併發系統的一些調優和優化策略

系統性能的關鍵指標

  • 吞吐量(Throughput) 系統單位時間內處理任務的數量

  • 延遲(Latency) 系統對單個任務的平均響應時間

一般來說,考量一個系統的性能主要看這兩個指標。而這兩個指標之間又存在着一些聯繫:對於指定的系統來說,系統的吞吐量越大,處理的請求越多,服務器就越繁忙,響應速度就會慢下來;而延遲越低的系統,能夠承載的吞吐量也相應的更高一些。

一方面,我們需要提高系統的吞吐量,以便服務更多的用戶,另一方面我們需要將延遲控制在合理的範圍內,以保證服務質量。

系統性能測試

  • 業務場景 對於不同的業務系統,可以接受的延遲(Latency)也有所不同,例如郵件服務可以忍受的延遲顯然要比Web服務高得多,所以首先我們需要根據業務場景的不同來定義理想的Latency值

  • 測試工具 我們需要一個能夠製造高吞吐的工具來測試系統的性能,本文中使用的Tsung,它是一個開源的支持分佈式的壓力測試工具,它功能豐富,性能強大,配置簡單,並支持多種協議(HTTP、MySQL、LDAP、MQTT、XMPP等)。

  • 測試流程 測試的過程中需要不斷加大吞吐量,同時注意觀察服務端的負載,如果負載沒有問題,那就觀察延遲。一般這個過程需要反覆很多次才能測出系統的極限值,而每次測試消耗的時間也比較長,需要耐心一些。

通用的系統參數調優

Linux內核默認的參數考慮的是最通用的場景,不能夠滿足高併發系統的需求

服務器參數調優

  • 編輯文件/etc/sysctl.conf,添加以下內容:

      fs.nr_open = 100000000
      fs.file-max = 100000000

    關於這兩項配置的含義可以查看系統手冊:

      $ man proc
      /proc/sys/fs/file-max
                  This  file defines a system-wide limit on the number of open files for all processes.  (See also setrlimit(2), which can be              used by a process to set the per-process limit, RLIMIT_NOFILE, on the number of files it may open.)  If  you  get  lots  of              error  messages  in the kernel log about running out of file handles (look for "VFS: file-max limit <number> reached"), try
                  increasing this value:
                      echo 100000 > /proc/sys/fs/file-max
    
                  The kernel constant NR_OPEN imposes an upper limit on the value that may be placed in file-max.
                  If you increase /proc/sys/fs/file-max,  be  sure  to  increase  /proc/sys/fs/inode-max  to  3-4  times  the  new  value  of
                  /proc/sys/fs/file-max, or you will run out of inodes.
                  Privileged processes (CAP_SYS_ADMIN) can override the file-max limit.

    可以看到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說明:

      $ man proc
          /proc/sys/fs/inode-max
                  This  file  contains  the  maximum  number  of in-memory inodes.  On some (2.4) systems, it may not be present.  This value
                  should be 3-4 times larger than the value in file-max, since stdin, stdout and network sockets also need an inode to handle              them.  When you regularly run out of inodes, you need to increase this value.

    可以看到有的系統中沒有這個參數,所以先不管了。

    對於nr_open,系統手冊裏沒有找到關於它的定義,不過我們可以參考kernel文檔

      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,添加如下內容:

      *      hard   nofile      4194304
      *      soft   nofile      4194304

    關於這兩項配置的含義可以查看系統手冊:

      $ man limits.conf
                  <...skipping...>
             hard             for enforcing hard resource limits. These limits are set by the superuser and enforced by the Kernel. The user cannot
                 raise his requirement of system resources above such values.
    
             soft             for enforcing soft resource limits. These limits are ones that the user can move up or down within the permitted range by
                 any pre-existing hard limits. The values specified with this token can be thought of as default values, for normal system             usage.
                  <...skipping...>
             nofile
                 maximum number of open file descriptors

    這裏的意思很明確: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是針對整個系統層面的限制。

壓測客戶機參數調優

對於壓測機器來說,爲了模擬大量的客戶端,除了需要修改文件描述符限制外,還需要配置可用端口範圍,可用端口數量決定了單臺壓測機器能夠同時模擬的最大用戶數量。

  • 文件描述符數量:修改過程同服務器

  • 可用端口數量: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中定義的保留端口,因此就不會出現服務端口被佔用而無法啓動的情況。

程序調優

對於不同的業務系統,需要有針對性的對其進行調優,本文中測試的目標服務使用Erlang/OTP寫就,Erlang/OTP本身帶有許多的限制,對於一般場景來說這些默認的設置是足夠的;但是爲了支持高併發,需要對Erlang虛擬機進行一些必要的參數調優,具體可以參考官方性能指南

服務程序參數調優

  • 進程(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

  • 端口(port)數量 端口提供了與外部世界通訊的基本機制(這裏的端口與TCP/IP端口的概念不同,需要注意區別),每個Socket連接需要消耗1個端口,官方文檔裏面說默認端口上限通常是16384,但根據實測,Linux系統默認爲65536,Windows系統默認爲8192,無論多少,在這裏都是需要調整的:在erl啓動時添加參數+Q Number,其中Number取值範圍是[1024-134217727]:

      $ erl +Q 10000000

壓測腳本調優

壓測工具使用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,目前只對流入流量有效。

定位系統性能瓶頸

當系統吞吐和延遲上不去時,首先需要定位問題,而不是急於修改代碼。

常見的性能瓶頸包括CPU/內存/磁盤IO/網絡帶寬等,其中每一項都有一到多個簡單實用的工具: 對於CPU和內存,我們只要使用top就可以了;對於磁盤IO,可以用iotop或iostat;對於網絡帶寬,可以使用iftop。

如果依然沒能定位到問題,可能系統配置不當,參考通用的系統參數調優。

最後檢查代碼是否有單點瓶頸,例如程序被阻塞了:在筆者實測過程中,發現每個用戶創建會話進程都需要對同一個supervisor發起同步請求,同時登錄的用戶數量很大時,這些同步請求會排隊,甚至引發超時。


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