女主宣言
今天带来的是一个篇长文,主要讲解高并发系统架构指标及调优测试经验,希望能对您的研究有所帮助。本文最先发布于 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大小就足够了:
Tsung能够让模拟用户的进程在空闲时间(thinktime)进入休眠,用以降低内存消耗,默认空闲10秒触发,我们可以将它降低到5秒:
IO
不要启用dumptraffic,除非用于调试,因为它需要将客户机和服务器往来的协议写入磁盘日志中,是一个IO开销非常大的行为。笔者曾经遇到过一次这样的问题,测试部门的同事使用JMeter,压测过程中,服务端一直处于较低的负载,但JMeter最终得出的压测报告却包含很多超时错误。经过仔细排查,才定位到原来是压测端默认开启了debug日志,海量的debug日志生生拖垮了压测机器。所以遇到这种情况时,可以先检查一下压测端配置是否正确。
日志等级要调高一些,日志等级过低会打印很多无用信息:一方面会加大IO开销,另一方面会让有用的ERROR信息淹没在海量的调试日志中。
如果Tsung从CSV文件读取用户名密码,那么该CSV文件不能过大,否则读取该CSV将会变成一个极其耗时的操作,特别是当压测程序需要每秒产生大量用户时。
网络
有时候为了避免网络拥塞,需要限制压测客户机的带宽,使流量以比较平滑的速率发送和接收。
其采用令牌桶算法(token bucket),单位KB/s,目前只对流入流量有效。
9
定位系统性能瓶颈
当系统吞吐和延迟上不去时,首先需要定位问题,而不是急于修改代码。
常见的性能瓶颈包括CPU/内存/磁盘IO/网络带宽等,其中每一项都有一到多个简单实用的工具: 对于CPU和内存,我们只要使用top就可以了;对于磁盘IO,可以用iotop或iostat;对于网络带宽,可以使用iftop。
如果依然没能定位到问题,可能系统配置不当,参考通用的系统参数调优。
最后检查代码是否有单点瓶颈,例如程序被阻塞了:在笔者实测过程中,发现每个用户创建会话进程都需要对同一个supervisor发起同步请求,同时登录的用户数量很大时,这些同步请求会排队,甚至引发超时。
10
结束语
以上就是笔者做压力测试时遇到的一些问题以及应对办法,鉴于笔者水平有限,错漏难免。抛砖引玉,欢迎交流指正。
HULK一线技术杂谈
由360云平台团队打造的技术分享公众号,内容涉及云计算、数据库、大数据、监控、泛前端、自动化测试等众多技术领域,通过夯实的技术积累和丰富的一线实战经验,为你带来最有料的技术分享