作者:Micheal,腾讯资深后台开发工程师。
WeTest导读
服务器性能测试是一项非常重要而且必要的工作,本文是作者Micheal在对服务器进行性能测试的过程中不断摸索出来的一些实用策略,通过定位问题,分析原因以及解决问题,实现对服务器进行更有针对性的优化,提升服务器的性能。
1.服务器性能测试小结
讲到服务器性能大部分人会想到这个服务器的架构是什么样子的,用的什么epoll,select,spring,tornado之类的。其实从本质上来看的话目前大部分的服务器主要包括逻辑层以及DB层,我们采用的各种框架组件处于逻辑服务器中,如下图所示。
服务器架构本质
服务器性能测试是一项比较繁琐的事情,作为没有做过性能测试的同学可能需要理清楚以下几个事情。
1.1. 协议分析
首先是协议分析,性能测试本质上是我们用代码来模拟真实的用户请求,所以我们必须要知道发送出去的请求内容才能模拟。在典型的CS服务器中很多使用了protobuf,thrift,tdr(腾讯自研)来序列化以及反序列号请求内容。
序列化之后一方面可以对数据进行压缩处理,另一方面也避免请求内容明文传输造成被抓包·泄漏数据的危险。之前有过服务器传输数据的时候使用的是明文直接发送,而且这个数据是一些敏感的sql语句,这样首先暴露了数据库的表结构,同时不法分子可以通过模拟发包造成“脱裤”甚至是数据被清空。
1.1.1. Protobuf
谷歌出品,必属精品。Protobuf使用起来很方便,学习成本非常低,而且序列化和反序列号的接口很容易使用。同时它相对于xml以及json,极大的的减小了数据占用的空间,减少了传输成本。目前支持包括C++,java以及python等多个语言。Protobuf目前用的比较多,打解包也很方便,比较推荐使用。
1.1.2. Thrift
Thrift是一个跨语言的轻量级rpc消息和数据交换框架。Thrift支持几乎绝大部分主流的语言,包括C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk, and OCaml,虽然大部分我都没有用过。相对于protobuf,thrift提供了全套RPC解决方案,包括序列化机制、传输层、并发处理框架等,也因为如此thrift的学习成本比较高。
1.1.3. 腾讯自研协议tdr
Tdr是腾讯自研跨平台多语言数据表示组件,主要用于基于数据的序列化反序列化(支持二进制方式和XML文本方式)以及ORM数据存储。广泛用于互娱自研游戏和部分代理游戏。在性能上基本和protobuf差不多,主要应用在C++程序中。
在做服务器性能测试之前,我们需要了解它的协议是怎么定义的。
1.2. 机器人管理
本质上机器人管理就是一个调度控制器,在获取需要发送的请求协议之后,需要有一个框架来管理所有的机器人,控制机器人的启动,发送请求以及停止的动作。框架的选择需要根据服务器的实际情况来,不同的业务场景,使用不同的框架产生的压力上限也会不一样。
以一个简单的多线程框架为例,主线程负责控制逻辑,管理所有的机器人状态信息。子线程执行每个机器人的任务,包括连接服务器,发送数据,接收数据,断开连接等。
1.3. 结果统计
机器人发送请求包之后,一般是要等待服务器的响应回包。服务器那边可以计算本次压测过程中各项业务数据,包括TPS,总的收发包量等。
不可能在测试过程中一直盯着各个数据看,我们需要把每项数据记录下来,后续综合各项结果进行分析。
这里的结果统计除了需要统计每个机器人收到回包的结果,还需要统计服务器在压测过程中的各项性能数据变化。一旦客户端的压力上到一定值时,服务器某项资源支撑不了的话,说明这个资源可能存在短板,存在可以优化的空间。
- 性能结果分析
性能结果分析是一个比较复杂的过程。需要综合硬件、操作系统、应用程序等多方面来定位。
2.1. 硬件的影响
硬件对服务器性能影响还是蛮大的,如果是土豪的话,可以直接买最好的。我们分析硬件主要是希望选择合适的配置,节约资源,避免出现高射炮打蚊子的情况。
2.1.1. CPU
在资金的充足下,一般来说CPU的数量越多,主频越高,那么服务器的性能也就会越好。在实际测试过程中,如果在大压力下持续观察CPU的使用率很低,那么CPU的资源基本上是可以满足服务器要求的。这样的情况其实是有点浪费CPU资源的,比较理想的情况是压力不大的时候CPU利用率比较低,压力上来之后CPU利用率保持在60%-70%。
大部分的CPU在同一时间内只能运行一个线程,但是超线程的处理器可以在同一个时间运行多个线程,我们可以利用处理前超线程特性提高系统性能。虽然采用超线程技术能同时执行两个线程,但它并不象两个真正的CPU那样,每个CPU都具有独立的资源。当两个线程都同时需要某一个资源时,其中一个要暂时停止,并让出资源,直到这些资源闲置后才能继续。因此超线程的性能并不等于两颗CPU的性能。
2.1.2. 内存
内存的大小也是影响服务器性能的一个重要因素。内存太小,系统进程要被阻塞,应用程序会变得缓慢,甚至是失去响应;如果太大的话,也是造成一种浪费。Linux系统中采用物理内存和虚拟内存两种方式,使用虚拟内存可以缓解物理内存的不足,但是如果占用过多的虚拟内存的话,应用程序的性能会明显的下降。
2.1.3. 网络带宽
网络带宽的大小限制了客户端与服务器交互的流量,相对其他硬件资源,网络带宽在价格上更贵。这需要我们合理预估服务器的可服务器能力,需要占用的带宽资源。
2.1.4. 磁盘IO
目前磁盘都是机械方式运作的,主要体现在磁盘读写前寻找磁道的过程。磁盘自带的读写缓存大小,对于磁盘读写速度至关重要。读写速度快的磁盘,通常都带有较大的读写缓存。磁盘的寻道过程是机械方式,决定了其随机读写速度将明显低于顺序读写。在我们做系统设计和实现时,需要考虑到磁盘的这一特性。
2.2. 操作系统及软件
2.2.1. 版本
不同的操作系统在内核实现上可能各不相同,因而对运行在上面的应用程序来说可能影响比较大。
笔者并没有做过分析不同操作系统对服务器性能的影响,因为只用过Linux开发服务器程序。Linux操作系统在这十几年发展的异常迅猛,目前大部分的服务器都是运行在Linux操作系统上的。Linux目前具有最好的生态系统,服务器端的各种软件都为它而设计,默认都认为你是在 Linux 上跑,你要是整一个非 Linux 的服务器,你得有足够的心理准备,因为出现任何问题,你可能未必能找到能帮你解决问题的人。
2.2.2. 参数配置
先说一个小故事。福特公司一套重要设备出现故障,找了很多人来维修,结果都没有维修好,没办法了,就在购买设备的英国公司高价聘请一位工程师过来维修,工程师来到之后,反复查找原因,最后在一个小零件上划了一条线,然后对旁边福特公司的人说,在划线的地方切掉就好了,果不其然,切掉之后故障真的解除了,按照合约,福特公司应支付公司一万美元,周围的人都唏嘘不已,感叹一条线就可以价值一万美元,工程师回答到:那条线只值一美元,而怎样找到那条线值9999美元。
我们在测试服务器的过程中,经常会遇到性能上不去。查看CPU,网络,IO消耗都挺低的,就是定位不到问题的原因。有经验的程序员可能会告诉你你把某个参数修改一下,立马性能噌噌噌上去了。比如mysql相关设置,系统文件描述符,缓冲区大小,time_wait快速回收设置等,甚至是线程池配置的线程个数也会对服务器的性能产生较大的影响。
关于数据库参数的设置,比如mysql的配置文件my.cnf文件中,修改不同的配置(比如innodb_flush_log_at_trx_commit 设置为0,1还是2 )可能会对数据库的读写性能影响很大。
2.2.3. 应用程序本身实现
比如程序中需要频繁申请内存,使用bzero和memset对服务器性能影响差距可能会很大。
另外程序中的一些查询操作,采用不同的数据结构,可以实现时间和空间上的相互转化,从而影响服务器的性能。
- Linux下的数据监控工具
3.1. Vmstat
Vmstat,virtual memmory statistics(虚拟内存统计),主要是对操作系统的内存信息、进程状态、cpu活动等进行监视,但是它不能对某个进程进行深入的分析。
Procs中r列表示运行和等待CPU时间片的进程数,如果r值长期大于CPU个数,说明CPU资源不够用啦,可以适当增加CPU数量。
Procs中b列表示当前等待资源的进程数,包括等待I/O,内存等。
Swpd列表示切换到内存交换区的KB数,一般si,so为0的话基本不影响系统的性能。
Cache是page cache的内存数量,Linux会把空闲的物理内存的一部分拿来做文件和目录的缓存,以便提高程序执行的性能。如果cache的值较大的话,说明缓存了太多的文件,如果bi值小的话,说明文件系统效率比较高。
Si是每秒从磁盘读入虚拟内存的大小,如果这个值一直大于0,表示物理内存不够用或者内存泄露了,需要查找耗内存进程解决掉。
Bi,bo是表示从块设备读入数据的总量以及写到块设备的数据总量。如果bi+bo值比较大,而且wa值也比较大的话,说明系统磁盘I/O可能有问题,性能不高。
In和cs是每秒钟的设备中断数以及上下文切换数。它们很大的话,表面内核消耗的CPU时间较多。
3.2. Top
Top是一个动态显示过程,即可以通过用户按键来不断刷新当前状态。它可以按照系统中当前进程的CPU利用率以及占用的内存大小进行排序,可以比较快速定位出系统响应迟钝的原因。如果在前台执行该命令,它将独占前台,直到用户终止该程序为止。
top是一个显示数据较多的工具,第一行显示的是系统的开机运行时间,机器的CPU负载信息;第二行显示当前系统任务的总数,以及各个状态的进程数;第三行显示的是CPU资源的使用情况总览;第四行显示内存的使用情况总览;第五行显示的是内存交换区的使用情况总览;后面开始是每个进程对资源使用的情况。
3.3. Nmon
Nmon提供对CPU、内存、网络、磁盘等系统资源占用情况分析,相比其他Linux命令获取到的数据,nmon的功能更为集中、配置性更强。通过nmon采集到数据之后可以在windows系统中使用nmon_analyser做数据的展示以及分析工作,可视化效果比较好。
由于一般Linux系统都不自带nmon,使用之前需要下载安装。
3.4. Uptime
Uptime命令显示系统已经运行了多长时间,它依次显示当前时间、系统已经运行了多长时间、目前有多少登陆用户、系统在过去的1分钟、5分钟和15分钟内的平均负载。
关于系统平均负载,它表示在特定时间间隔内运行队列中的平均进程数。如果一个进程满足以下条件则其就会位于运行队列中:没有在等待I/O操作的结果;没有主动进入等待状态;没有被停止。
3.5. Netstat
Netstat命令可以显示本机的网络连接情况,监听端口以及路由表等各种网络相关信息。Netstat用于显示与IP、TCP、UDP和ICMP协议相关的统计数据,一般用于检验本机各端口的网络连接情况。
比较常用的可以用次命令查看当前开启监听的服务器进程信息以及端口信息。
3.6. Free
Free是监控Linux使用情况最常用的命令。
“Free -m”可以查看以M为单位的使用情况,这里主要观察free和cached两列。
一般来说,如果应用程序可用内存/系统物理内存>70%时,表明目前系统内存资源比较充足,不影响系统性能;如果应用程序可用内存/系统物理内存<20%时,表明目前系统内存资源比较紧缺,需要释放其他程序内存或者增加内存;如果应用程序可用内存/系统物理内存在20%-70%之间,表明目前系统的内存资源基本满足应用需求,暂时不影响系统的性能。
3.7. Sar
Sar也是一个强大的分析系统性能的工具,它可以比较全面的获取系统的CPU,运行队列,磁盘IO,分页,内存,CPU中断,网络等多项数据。
上图是使用sar获取系统CPU的整体负责情况,每隔1秒统计一次,统计3次,最后会给出3次的平均值。需要查看其他的数据可以查看手册使用。
3.8. Iostat
Iostat是I/O statistics的缩写,主要功能是对系统的磁盘I/O操作进行监控。它的输出主要显示磁盘读写操作的统计信息,同时也会给出CPU的使用情况。
这里显示的是查看CPU和磁盘的信息,统计间隔2秒,共3次。
3.9. Valgrind
Valgrind是一款广泛用于监控程序运行过程进行内存调试、内存泄漏检测以及性能分析的工具。它会给出内存泄漏的统计,包括definitely lost,indirectly lost,possibly lost,still reachable ,suppressed等,我们可以使用valgrind来测试程序中内存不规范使用的部分。同时对于地址越界问题也可以通过valgrind扫出来,它会统计invalid write的情况。
- 服务器的性能优化
在优化之前,先要搞清楚服务器的具体业务需求是什么,据此来优化其中的短板。
4.1. 存储的优化
IO相对来说比较耗时,我们都知道越靠近CPU的存储,其访问速度越快,但是其价格越贵。下图来展示了不同存储的容量以及访问时间。
目前很多同学在优化服务器性能的时候都会从存储这方面入手。
储存的容量以及访问时间
4.1.1. 用内存换时间
4.1.1.1. 增加缓存
很多web应用是有大量的静态内容,这些静态内容主要都是一些小文件,并且会被频繁的读,采用Apache以及nginx作为web服务器。在web访问量不大的时候,这两个http服务器可以说是非常的迅速和高效,如果负载量很大的时候,我们可以采用在前端搭建cache服务器,将服务器中的静态资源文件缓存到操作系统内存中直接进行读操作,因为直接从内存读取数据的速度要远大于从硬盘读取。这个其实也是增加内存的成本来降低访问磁盘带来的时间消耗。
4.1.1.2. 内存数据库
内存数据库,其实就是将数据放在内存中直接操作的数据库。相对于磁盘,内存的数据读写速度要高出几个数量级,将数据保存在内存中相比从磁盘上访问能够极大地提高应用的性能。内存数据库抛弃了磁盘数据管理的传统方式,基于全部数据都在内存中重新设计了体系结构,并且在数据缓存、快速算法、并行操作方面也进行了相应的改进,所以数据处理速度比传统数据库的数据处理速度要快很多。
但是安全性的问题可以说是内存数据库最大的硬伤。因为内存本身有掉电丢失的天然缺陷,因此我们在使用内存数据库的时候,通常需要,提前对内存上的数据采取一些保护机制,比如备份,记录日志,热备或集群,与磁盘数据库同步等方式。
对于一些重要性不高但是又想要快速响应用户请求的部分数据可以考虑内存数据库来存储,同时可以定期把数据固化到磁盘。
4.1.1.3. RDD
这里图个新鲜,说说内存换时间在大数据云计算相关领域的一些应用。Spark最近很火,它的核心要数RDD了,RDD最早来源与Berkeley实验室的一篇论文《Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing》。现有的数据流系统对两种应用的处理并不高效:一是迭代式算法,这在图应用和机器学习领域很常见;二是交互式数据挖掘工具。这两种情况下,将数据保存在内存中能够极大地提高性能。这里不详细说RDD了,只是想说程序员一直是觊觎内存的读取速度的。
4.1.2. 使用SSD等
除了对内存方面的优化,还可以对磁盘这边进行优化。跟传统机械硬盘相比,固态硬盘具有快速读写、质量轻、能耗低以及体积小等特点。但是ssd的价格相比传统机械硬盘要贵,有条件的可以使用ssd来代替机械硬盘。
4.2. 数据库优化
大部分的服务器请求最终都是要落到数据库中,随着数据量的增加,数据库的访问速度也会越来越慢。想要提升请求处理速度,必须要对原来的单表进行动刀了。目前主流的Linux服务器使用的数据库要属mysql了,如果我们使用mysql存储的数据单个表的记录达到千万级别的话,查询速度会很慢的。
根据业务上合适的规则对数据库进行分区分表,可以有效提高数据库的访问速度,提升服务器的整体性能。
另外对于业务上查询请求,在建表的时候可以根据相关需求设置索引等,以提高查询速度。
4.3. 利用多核优势
现在运行服务器的主流机器配置都是多核CPU的,我们在设计服务器的时候可以利用多核心的特点,采用多进程或者多线程的框架。
关于选择多线程还是多进程可以根据实际的需求,结合各自的优缺点进行选择。
对于多线程的使用,特别是使用线程池的时候可以通过测试不同线程池服务器的性能来设置合适的线程池。
4.4. 选择合适的IO模型
《UNIX网络编程卷1:套接字联网API》中有一幅图比较经典。
阻塞I/O模型:数据没到达之前,I/O一直阻塞,如果数据到达,则会返回。典型的是recvfrom,一般的默认都是阻塞的。非阻塞的I/O模型:和阻塞相反,只要不能得到结果的时候,I/O立刻返回。不会阻塞当前线程。
IO复用模型:也就是自己要学习的部分。多路复用的意思是,将多路信号合并到一路上进行处理,类似多个管道汇集到一个管道,与之相反的是多路分解。
IO复用模型主要是select,poll,epoll;对一个IO端口,两次调用,两次返回,比阻塞IO并没有什么优越性;关键是能实现同时对多个IO端口进行监听;函数也会使进程阻塞,但是和阻塞I/O所不同的的,这两个函数可以同时阻塞多个I/O操作。而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数。
信号驱动:首先开启套接口信号驱动I/O功能,并通过系统调用sigaction安装一个信号处理函数。当数据报准备好被读时,就为该进程生成一个SIGIO信号。随即可以在信号处理程序中调用recvfrom来读数据报,井通知主循环数据已准备好被处理中。也可以通知主循环,让它来读数据报。
异步的IO模型:告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核拷贝到用户自己的缓冲区)通知我们。
这里并不是说一定要用某个模型,epoll也并不是在所有情况下都比select性能要好的,在选择的时候还是要结合业务需求来。
4.5. 分布式部署程序
当单机服务器已经找不到合适的优化点时,我们可以通过分布式部署来提高服务器的响应能力。优秀的服务器开发都会为自己的服务器的扩容,容灾提出一些解决方案。个人觉得服务器设计的时候简单点比较好,这样后期扩容的时候会很方便。
总结
服务器性能测试是一项比较繁琐的事情,作为没有做过性能测试的同学需要事先了解服务器的协议是如何定义的,建立框架管理机器人、统计测试中机器人收到回包的结果以及压测过程中各项性能数据的变化。在完成了测试的过程后,可以从硬件、操作系统以及应用程序等多个方面进行对性能结果进行定位。最后在明确业务需求的前提下,通过存储优化、数据库优化以及分布式部署程序等手段完成服务器的性能优化。