08月26, 2018

浅谈高并发系统性能调优

高并发系统的优化一直以来都是一个很重要的问题,下面基于笔者的实践,和大家聊聊高并发系统的一些调优和优化策略

系统性能的关键指标

  • 吞吐量(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中定义的保留端口,因此就不会出现服务端口被占用而无法启动的情况。

    编辑文件/etc/sysctl.conf,添加以下内容:

      net.core.somaxconn=65535
      net.ipv4.tcp_max_syn_backlog=32768
      net.core.netdev_max_backlog=32768
    

程序调优

对于不同的业务系统,需要有针对性的对其进行调优,本文中测试的目标服务使用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发起同步请求,同时登录的用户数量很大时,这些同步请求会排队,甚至引发超时。

结束语

以上就是笔者做压力测试时遇到的一些问题以及应对办法,鉴于笔者水平有限,错漏难免。抛砖引玉,欢迎交流指正。

本文链接:https://www.opsdev.cn/post/tuning-your-system-for-high-concurrency.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。