前面讨论系统调用的时候得出的结论是耗时200ns-15us。但是我今天说的我的遭遇,可能会让你更加了解系统调用的真正开销。在本节中,您将看到一个耗时2.5毫秒的连接系统调用。注意是毫秒,相当于2500us!问题描述是我的一个nginx+lua写的在线云控接口。正常情况下,单台8核8G的虚拟机每秒可以抗2000QPS左右,负载比较健康。但是最近服务开始有500左右的状态请求,监控会时不时的报警。通过sar-u查看峰值时,只剩下20-30%的CPU余量。第一步是快速定位嫌疑人,使用top命令查看CPU占用情况。通过top命令找到峰值时,CPU消耗很大,idle只有20-30%左右。在使用的CPU中,软中断的比例比较高,大概有1/3。然后用cat/proc/softirqs查看软中断都是NET_TX、NET_RX、网络IO产生的时钟TIMER。由于窃贼,软中断,已经吃掉了我这么多的CPU时间,案件的嫌疑人最初被我锁定了。解决办法,既然NET_TX、NET_RX、TIMER都高,那我们就选择能??减的函数砍掉。1、砍掉多余的gettimeofday系统调用2、为每个请求砍掉一个非必要的Redis访问,只留下必要的。结果:peakcpumargin确实多了一点。报警频率确实下降了,但是零星报警还是时有发生。可见,嫌疑人并非主犯。.第二步,大面积杀青。真凶接着查看网络连接状态ss-n-t-a,发现ESTABLISH状态的链接不多,TIME-WAIT状态的却有11W多。继续研究发现..*.122:6390的TIME-WAIT已经超过了3W。所以端口是有限的。原来执行上一步时只有连接上的数据请求被kill掉了,但是tcp握手请求依然存在。解决方法:彻底杀掉..*.122:6390的网络连接请求,只保留必须保留的逻辑。结果:问题完全解决。sar-u查看CPU空闲余量达到90%以上。Tips:如果单机作为TCP客户端,有如下限制:ESTABLISH状态的连接数只能在ip_local_port_range范围内。只是对于特定的ip,特定的端口TIME-WAIT过多,超过或接近ip_local_port_range,然后可能会出现没有端口可用的新连接。(一般TIME-WAIT太多不一定有问题。)没想到简单的切断redis服务器的一个tcp连接就可以把cpu优化到这么多。很意外,想不通。根据我之前的性能测试经验,每次tcp连接的建立只需要消耗36usec左右的cpu时间。我们估算一下:当时服务器的qps在2000左右,假设均匀分布的话,8个核心每个每秒只需要处理250个请求。也就是说每秒一个tcp连接消耗的cpu时间为:250*36usec=9ms。也就是说,正常情况下,砍掉这些握手开销只能节省1%左右的cpu,并没有这么大的提升。(虽然我上面的估算只考虑了连接的建立,没有计算释放连接的cpu开销,但释放连接的cpu开销和建立连接的cpu开销差不多。)总之,这一步确实解决了问题,但是以牺牲业务逻辑为代价。最终查明真凶,真相大白。我在某台机器上回滚了旧有问题的代码,并恢复了问题站点。然后只需修改ip_local_port_range即可。然后请发出strace命令。所有系统调用的开销汇总可以通过strace-c统计。结果发现connect系统调用是二手货。在正常机器上只需要22us左右,但在有问题的机器上需要2500us,增加了100倍。下面用strace-c$PID看看connect系统调用出现问题时和正常时的耗时对比:图1:正常情况下图2:当有问题那么回想一下..*.122:6390的TIME-WAIT已经超过了3W,会不会TIME_WAIT占用太多端口导致端口不足?于是查看端口内核参数配置:#sysctl-a|grepip_local_port_rangenet.ipv4.ip_local_port_range=3276865000结果发现本机上的端口范围只开了3W多,也就是说端口快满了。然后增加可用端口数:#vim/etc/sysctl.confnet.ipv4.ip_local_port_range=1000065000connect系统调用回归理性状态,整体服务器CPU使用率非常健康。问题的根本原因是用于建立TCP连接的端口数(ip_local_port_range)不够,导致connect系统调用的开销增加了近100倍!后来我们一个开发同学帮忙找了一段connect系统调用的源码__inet_hash_connect(structinet_timewait_death_row*death_row,structsock*sk,u32port_offset,int(*check_established)(structinet_timewait_death_row*,structsock*,__u16,structinet_timewait_sock**),int(*washuct,instruct_strsuct)(*twp)){structinet_hashinfo*hinfo=death_row->hashinfo;constunsignedshortsnum=inet_sk(sk)->inet_num;结构inet_bind_hashbucket*头;结构inet_bind_bucket*tb;诠释;structnet*net=sock_net(sk);inttwrefcnt=1;如果(!snum){inti,剩余,低,高,端口;静态u32提示;u32offset=hint+port_offset;结构inet_timewait_sock*tw=NULL;inet_get_local_port_range(&low,&high);剩余=(高-低)+1;local_bh_disable();for(i=1;i<=remaining;i++){port=low+(i+offset)%remaining;如果(inet_is_reserved_local_port(端口))继续;......}}staticinlineu32inet_sk_port_offset(conststructsock*sk){conststructinet_sock*inet=inet_sk(sk);返回secure_ipv4_port_ephemeral(inet->inet_rcv_saddr,inet->inet_daddr,inet->inet_dport);选择过程是产生一个随机数,使用ip_local_port_ra中的随机数取nge范围内的值。如果得到的值在ip_local_reserved_ports范围内,则依次取下一个值,直到不在ip_local_reserved_ports范围内。事实证明临时端口是随机命中的。出去。来。的。.也就是说,如果在可以使用的范围内配置了5W个端口,则已经使用了49999个端口。那么当建立新的连接时,可能需要调用这个随机函数5W次才能命中这个无用的端口。所以请记住确保您有足够的临时端口可用,以防止您的连接系统调用进入SB模式。当普通端口足够时,只需要22usec。但是一旦端口吃紧,一个系统调用的耗时就会上升到2.5ms,长了100倍。这个开销远远大于建立一个普通的tcp连接所消耗的CPU时间(每次大约30usec)。作为TIME_WAIT的解决方案,除了放宽端口数量限制外,还可以考虑设置net.ipv4.tcp_tw_recycle和net.ipv4.tcp_tw_reuse这两个参数,避免端口保守地等待2MSL时间过长。练内功专用CPU:1、你以为你的多核CPU是真核生物吗?多核“错觉”2、听说你只知道内存,不知道缓存?CPU表示很伤心!3.TLB缓存是个鬼,如何查看TLBmiss?4、进程/线程切换需要多少开销?5、协程比线程好在哪里?6.softirq会吃掉多少CPU?7、一次系统调用的开销是多少?8、一个简单的php请求redis的开销是多少?9、函数调用过多会不会有性能问题?我的公众号是“练内功练功”。在这里,我不是简单地介绍技术理论,也不是只介绍实践经验。而是理论联系实际,用实践加深对理论的理解,用理论提高技术实践能力。欢迎关注我的公众号,分享给你的朋友吧~~~