性能优化(图片来自网络)
性能优化(图片来自网络)

本文是为了记录下《QMWS》项目服务器在对外测试期间,性能表现和技术审核时的性能表现差距很大,从而做出的一些优化过程,期间还是比较头疼,接近两个连续通宵来修改。第一个通宵一直在查找问题和猜问题,找问题是如何出现的,第二天主要是解决对应的性能问题。

性能问题主要集中在:

内存使用过快

内存泄露

某些时间内协议过多

逻辑功能处理不当

其他网络消耗什么的暂时也没有考虑,下面详细说说这几个问题是如何引起的以及如何一步步解决的。

1.内存使用过快问题

我们项目使用的是C++后端,用了团队主程之前写的,后来我参与开发的一个开源的服务器数据管理框架NFrame,下面是github地址。

GitHub 仓库挂件 WordPress 插件

ketoo / NoahGameFrame

A fast, scalable, distributed game server engine/framework for C++, include actor library, network library,can be used as a real time multiplayer game engine ( MMO RPG/MOBA ), which support C#/Lua script/ Unity3d, Cocos2dx and plan to support Unreal.

https://github.com/ketoo/NoahGameFrame/wiki

这个框架就不多做介绍了,有兴趣可以从github pull下来研究研究,或者加群讨论。

这个框架的主要问题就是小的变量和内存申请太多了,导致产生了很多碎片,在linux系统上回收不够及时导致的,而且我们错误的估计了shared_ptr的能力了,智能指针确实能减少很多内存管理,但是生命周期却不好把握,所以我们临时用了大google的TCMalloc,每5分钟主动回收一次内存,确实解决了我们的问题。

2.内存泄漏

内存泄漏这块主要的问题还是代码上的漏洞,逻辑不够严谨,释放对象的时候因为一些判断导致对象没有被释放。还有就是一些数据管理上的问题,不是特别严重。

3.某些操作下逻辑处理过多

因为NFrame采用的是事件回调的方式来处理数据变动,我们会在自己感兴趣的属性(Property)和表(Record)数据上注册回调函数,从而做一些逻辑。这个时候就得特别注意逻辑需求了,因为某些时候属性会有多次变化,导致多次计算其他属性,我们遇到的问题就是当属性变化时计算多个武将的战斗力,如果某个操作导致了多个属性变化,就会导致成倍的战斗力计算,加重了逻辑负担。修改方式是去掉部分回调,在逻辑操作中特定的时候处理回调做的事情,从而降低调用次数。

4.逻辑功能处理不当

这个就是一些常见的问题了,经过重构整理以及性能测试就可以很好的看出来问题,就不多说了。

 

推荐的一些工具:

VLD C++内存泄漏检查工具

VTune C++性能内存检查工具,完美和VS IDE结合,非常好用

Valgrind linux下内存检查工具,缺点是没有IDE环境,报告不太容易看,不过分析还是蛮准确的

Border check(DevPartner) 自动化测试/覆盖性测试工具,大borland(估计很多人都没听过了)出品,业界一流产品

 

好的结果是问题基本已经排查到了,所以后续的版本测试基本没有特别大的性能问题,不过还是得争取高的压测结果。

PS:线上环境和本地测试还是有很大差距,前些天看到一个测试方法,觉得很有意思,与大家分享下。是通过线上开一台功能服务器A,然后再开一台同样的功能服务器B,A和B做的事情完全相同,A是对外服务,B是内部测试服务器,A多做一件事情,将收到的消息顺便转发到B,B再按照收到的协议做处理,这样通过观察B的数据和性能就能模拟线上环境,从而更容易发现问题处理问题,这个东西github上也有对应的项目,叫TCP Copy,有兴趣的朋友可以看看。

via:http://www.sudops.com/linux-kernel-tcp-ip-sysctl-optimize.html

Linux下TCP/IP及内核参数优化有多种方式,参数配置得当可以大大提高系统的性能,也可以根据特定场景进行专门的优化,如TIME_WAIT过高,DDOS攻击等等。
如下配置是写在sysctl.conf中,可使用sysctl -p生效,文中附带了一些默认值和中文解释(从网上收集和翻译而来),确有些辛苦,转载请保留链接,谢谢~。
相关参数仅供参考,具体数值还需要根据机器性能,应用场景等实际情况来做更细微调整。

net.core.netdev_max_backlog = 400000
#该参数决定了,网络设备接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目。

net.core.optmem_max = 10000000
#该参数指定了每个套接字所允许的最大缓冲区的大小

net.core.rmem_default = 10000000
#指定了接收套接字缓冲区大小的缺省值(以字节为单位)。

net.core.rmem_max = 10000000
#指定了接收套接字缓冲区大小的最大值(以字节为单位)。

net.core.somaxconn = 100000
#Linux kernel参数,表示socket监听的backlog(监听队列)上限

net.core.wmem_default = 11059200
#定义默认的发送窗口大小;对于更大的 BDP 来说,这个大小也应该更大。

net.core.wmem_max = 11059200
#定义发送窗口的最大大小;对于更大的 BDP 来说,这个大小也应该更大。

net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
#严谨模式 1 (推荐)
#松散模式 0

net.ipv4.tcp_congestion_control = bic
#默认推荐设置是 htcp

net.ipv4.tcp_window_scaling = 0
#关闭tcp_window_scaling
#启用 RFC 1323 定义的 window scaling;要支持超过 64KB 的窗口,必须启用该值。

net.ipv4.tcp_ecn = 0
#把TCP的直接拥塞通告(tcp_ecn)关掉

net.ipv4.tcp_sack = 1
#关闭tcp_sack
#启用有选择的应答(Selective Acknowledgment),
#这可以通过有选择地应答乱序接收到的报文来提高性能(这样可以让发送者只发送丢失的报文段);
#(对于广域网通信来说)这个选项应该启用,但是这会增加对 CPU 的占用。

net.ipv4.tcp_max_tw_buckets = 10000
#表示系统同时保持TIME_WAIT套接字的最大数量

net.ipv4.tcp_max_syn_backlog = 8192
#表示SYN队列长度,默认1024,改成8192,可以容纳更多等待连接的网络连接数。

net.ipv4.tcp_syncookies = 1
#表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;

net.ipv4.tcp_timestamps = 1
#开启TCP时间戳
#以一种比重发超时更精确的方法(请参阅 RFC 1323)来启用对 RTT 的计算;为了实现更好的性能应该启用这个选项。

net.ipv4.tcp_tw_reuse = 1
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

net.ipv4.tcp_tw_recycle = 1
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

net.ipv4.tcp_fin_timeout = 10
#表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。

net.ipv4.tcp_keepalive_time = 1800
#表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为30分钟。

net.ipv4.tcp_keepalive_probes = 3
#如果对方不予应答,探测包的发送次数

net.ipv4.tcp_keepalive_intvl = 15
#keepalive探测包的发送间隔

net.ipv4.tcp_mem
#确定 TCP 栈应该如何反映内存使用;每个值的单位都是内存页(通常是 4KB)。
#第一个值是内存使用的下限。
#第二个值是内存压力模式开始对缓冲区使用应用压力的上限。
#第三个值是内存上限。在这个层次上可以将报文丢弃,从而减少对内存的使用。对于较大的 BDP 可以增大这些值(但是要记住,其单位是内存页,而不是字节)。

net.ipv4.tcp_rmem
#与 tcp_wmem 类似,不过它表示的是为自动调优所使用的接收缓冲区的值。

net.ipv4.tcp_wmem = 30000000 30000000 30000000
#为自动调优定义每个 socket 使用的内存。
#第一个值是为 socket 的发送缓冲区分配的最少字节数。
#第二个值是默认值(该值会被 wmem_default 覆盖),缓冲区在系统负载不重的情况下可以增长到这个值。
#第三个值是发送缓冲区空间的最大字节数(该值会被 wmem_max 覆盖)。

net.ipv4.ip_local_port_range = 1024 65000
#表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为1024到65000。

net.ipv4.netfilter.ip_conntrack_max=204800
#设置系统对最大跟踪的TCP连接数的限制

net.ipv4.tcp_slow_start_after_idle = 0
#关闭tcp的连接传输的慢启动,即先休止一段时间,再初始化拥塞窗口。

net.ipv4.route.gc_timeout = 100
#路由缓存刷新频率,当一个路由失败后多长时间跳到另一个路由,默认是300。

net.ipv4.tcp_syn_retries = 1
#在内核放弃建立连接之前发送SYN包的数量。

net.ipv4.icmp_echo_ignore_broadcasts = 1
# 避免放大攻击

net.ipv4.icmp_ignore_bogus_error_responses = 1
# 开启恶意icmp错误消息保护

net.inet.udp.checksum=1
#防止不正确的udp包的攻击

net.ipv4.conf.default.accept_source_route = 0
#是否接受含有源路由信息的ip包。参数值为布尔值,1表示接受,0表示不接受。
#在充当网关的linux主机上缺省值为1,在一般的linux主机上缺省值为0。
#从安全性角度出发,建议你关闭该功能。

本次的开放测试依然在进行中,昨天关于卡建号的问题大家讨论了一下解决办法,对逻辑方面也检查了一下,不过… 继续阅读 本次开放测试卡建号问题分析——MySQL配置优化方法