前面我们讨论系统调用的时候结论是耗时200ns-15us不等。不过我今天说的我的这个遭遇可能会让你进一步认识系统调用的真正开销。在本节里你会看到一个耗时2.5ms的connect系统调用,注意是毫秒,相当于2500us!

问题描述

当时是我的一个线上云控接口,是nginx+lua写的。正常情况下,单虚机8核8G可以抗每秒2000左右的QPS,负载还比较健康。但是该服务近期开始出现一些500状态的请求了,监控时不时会出现报警。通过sar -u查看峰值时cpu余量只剩下了20-30%。

图3.jpg

第一步、迅速锁定嫌疑人

top命令查看cpu使用,通过top命令发现峰值的时候cpu确实消耗的比较多,idle只有20-30%左右。在使用的cpu里,软中断的占比也比较高,1/3左右。

再通过cat /proc/softirqs查看到软中断是都是网络IO产生的NET_TX,NET_RX,和时钟TIMER。

既然软中断这个贼人吃掉了我这么多的CPU时间,所以案件的嫌疑人就这么初步被我锁定了。

处理,那既然是NET_TX,NET_RX和TIMER都高,那咱就挑可以削减的功能砍一砍呗。

  • 1.砍掉多余的gettimeofday系统调用
  • 2.每个请求砍掉一次非必须Redis访问,只留了必要的。

结果:峰值的cpu余量从确实多出来一些了。报警频率确实下来了,但是还是偶尔会有零星的报警。可见该嫌疑人并非主犯。。

第二步、干掉一大片,真凶在其中

接着查看网络连接的情况ss -n -t -a发现,ESTABLISH状态的链接不是很多,但是TIME-WAIT有11W多。继续研究发现针对..*.122:6390的TIME-WAIT已经超过了3W。所以端口有限。原来呀,上一步执行时只干掉了连接上的数据请求,但是tcp握手请求仍然存在。

处理:彻底干掉了针对..*.122:6390的网络连接请求,只保留了必须保留的逻辑。

结果:问题彻底解决。sar -u查看cpu的idle余量竟然达到了90%多。

Tips:单台机器如果作为TCP的客户端,有如下限制

  1. ESTABLISH状态的连接只能有ip_local_port_range范围内的个数。
  2. 只有针对特定ip,特定port的TIME-WAIT过多,超过或接近ip_local_port_range,再新建立连接可能会出现无端口可用的情况。( 总的TIME-WAIT过多并不一定有问题 )

没想到一个简单砍掉一个对redis server的tcp连接,能把cpu优化到这么多。大大出乎意料,而且也想不明白。 根据我之前的性能测试经验,每个tcp连接的建立大约只需要消耗36usec的cpu时间。我们来估算一下:

当时server的qps大约在2000左右,假设是均匀分布的,则8个核每个核每秒只需要处理250个请求。也就是说每秒一条tcp连接需要消耗的cpu时间为:250*36usec = 9ms.

也就是说,正常来讲砍掉这些握手开销只能节约1%左右的cpu,不至于有这么大的提升。(即使我上面的估算只考虑了建立连接,没有统计释放连接的cpu开销,但是连接释放cpu开销也和建立连接差不多。)

总之,这一步确实解决了问题,但是代价是牺牲了一个业务逻辑。

最终、审出真凶,真相大白于天下

我在某一台机器上把老的有问题的代码回滚了回来,恢复问题现场。然后只修改一下ip_local_port_range。 然后请出了strace这个命令。

通过strace -c 统计到对于所有系统调用的开销汇总。 结果我们发现了connect系统调用这个二货,在正常的机器上只需要22us左右,在有问题的机器上竟然花掉来 2500us,上涨了100倍。我们用strace -c $PID查看一下出问题时和正常时的connect系统调用耗时对比:

图1.png

<centor>图1:正常情况下</centor>

图2.png

<centor>图2:出问题时</centor>

然后回想起了..*.122:6390的TIME-WAIT已经超过了3W,会不会TIME_WAIT占用了太多端口导致端口不足呢。因此查看端口内核参数配置:

# sysctl -a | grep ip_local_port_range

net.ipv4.ip_local_port_range = 32768 65000

果然发现该机器上的端口范围只开了3W多个,也就是说端口已经几乎快用满了。那就提高端口可用数量:

# vim /etc/sysctl.conf
net.ipv4.ip_local_port_range = 10000 65000

connect系统调用恢复理性状态,整体服务器的CPU使用率非常健康。

问题的根本原因是建立TCP连接使用的端口数量上(ip_local_port_range)不充裕,导致connect系统调用开销上涨了将近100倍!

后来我们的一位开发同学帮忙翻到了connect系统调用里的一段源码

int inet_hash_connect(struct inet_timewait_death_row *death_row,

struct sock *sk)

{

return __inet_hash_connect(death_row, sk, inet_sk_port_offset(sk),

__inet_check_established, __inet_hash_nolisten);

}

int __inet_hash_connect(struct inet_timewait_death_row *death_row,

struct sock *sk, u32 port_offset,

int (*check_established)(struct inet_timewait_death_row *,

struct sock *, __u16, struct inet_timewait_sock **),

int (*hash)(struct sock *sk, struct inet_timewait_sock *twp))

{

struct inet_hashinfo *hinfo = death_row->hashinfo;

const unsigned short snum = inet_sk(sk)->inet_num;

struct inet_bind_hashbucket *head;

struct inet_bind_bucket *tb;

int ret;

struct net *net = sock_net(sk);

int twrefcnt = 1;

if (!snum) {

int i, remaining, low, high, port;

static u32 hint;

u32 offset = hint + port_offset;

struct inet_timewait_sock *tw = NULL;

inet_get_local_port_range(&low, &high);

remaining = (high - low) + 1;

local_bh_disable();

for (i = 1; i <= remaining; i++) {

port = low + (i + offset) % remaining;

if (inet_is_reserved_local_port(port))

continue;

......

}

}

static inline u32 inet_sk_port_offset(const struct sock *sk)

{

const struct inet_sock *inet = inet_sk(sk);

return secure_ipv4_port_ephemeral(inet->inet_rcv_saddr,

inet->inet_daddr,

inet->inet_dport);

}

从上面源代码可见,临时端口选择过程是生成一个随机数,利用随机数在ip_local_port_range范围内取值,如果取到的值在ip_local_reserved_ports范围内 ,那就再依次取下一个值,直到不在ip_local_reserved_ports范围内为止。原来临时端口竟然是随机撞。出。来。的。。 也就是说假如就有range里配置了5W个端口可以用,已经使用掉了49999个。那么新建立连接的时候,可能需要调用这个随机函数5W次才能撞到这个没用的端口身上。

所以请记得要保证你可用临时端口的充裕,避免你的connect系统调用进入SB模式。正常端口充足的时候,只需要22usec。但是一旦出现端口紧张,则一次系统调用耗时会上升到2.5ms,整整多出100倍。这个开销比正常tcp连接的建立吃掉的cpu时间(每个30usec左右)的开销要大的多。

解决TIME_WAIT的办法除了放宽端口数量限制外,还可以考虑设置net.ipv4.tcp_tw_recycle和net.ipv4.tcp_tw_reuse这两个参数,避免端口长时间保守地等待2MSL时间。

性能测试 | 服务器CPU使用率高分析实例的更多相关文章

  1. 服务器CPU使用率高的原因分析与解决办法

    我们的服务器在使用操作系统的时候,用着用着系统就变慢了,打开“ 任务管理器 ”一看,才发现CPU使用率达到80%以上.这是怎么回事情呢?遇到病毒了吗?硬件有问题?还是系统设置有问题呢?在本文中将从硬件 ...

  2. (原创)性能测试中,Oracle服务器定位CPU使用率高的瓶颈(SQL)

    本篇博客记录一次性能测试过程中,定位对CPU使用率高的瓶颈问题,主要定位SQL为准 一.用SQL命令定位1.首先用TOP命令监控系统资源,如果是AIX系统,就用topas,进入TOP命令的滚动刷新数据 ...

  3. 查询执行成本高(查询访问表数据行数多)而导致实例 CPU 使用率高是 MySQL 非常常见的问题

    MySQL CPU 使用率高的原因和解决方法_产品性能_常见问题_云数据库 RDS 版-阿里云 https://help.aliyun.com/knowledge_detail/51587.html ...

  4. 排查MongoDB CPU使用率高的问题

    1.公司业务调整,把一部分数据由Redis转至MongoDB,业务在测试环境正常,生产环境上线后发现压力一上来MongoDB的服务直接把CPU占满了,和开发的同学分析了一下也参考了一下百度上类似的问题 ...

  5. Java进程CPU使用率高排查

    Java进程CPU使用率高排查 生产java应用,CPU使用率一直很高,经常达到100%,通过以下步骤完美解决,分享一下.1.jps 获取Java进程的PID.2.jstack pid >> ...

  6. MySQL CPU 使用率高的原因和解决方法

    用户在使用 MySQL 实例时,会遇到 CPU 使用率过高甚至达到 100% 的情况.本文将介绍造成该状况的常见原因以及解决方法,并通过 CPU 使用率为 100% 的典型场景,来分析引起该状况的原因 ...

  7. 交换机CPU使用率高的原因

    交换机CPU的功能 1.管理已配置的软件协议,例如: – 生成树协议(STP) – 路由协议,例如OSPF和EIGRP – 热备路由协议(HSRP) – 思科发现协议(CDP) – 端口聚合协议(PA ...

  8. JVM进程cpu飙高分析

    在项目快速迭代中版本发布频繁  近期上线报错一个JVM导致服务器cpu飙高 但内存充足的原因现象.  对于耗内存的JVM程序来而言,  基本可以断定是线程僵死(死锁.死循环等)问题. 这里是纪录一下排 ...

  9. 线上服务器CPU彪高的调试方式

    原文内容来自于LZ(楼主)的印象笔记,如出现排版异常或图片丢失等问题,可查看当前链接:https://app.yinxiang.com/shard/s17/nl/19391737/2fee7b91-f ...

随机推荐

  1. vue父组件传值和子组件触发父组件方法

    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <scr ...

  2. Python网络编程常用代码

    服务器端代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 # -*- coding: cp936 -*- ...

  3. SPI总线的原理与Verilog实现

    转载地址:https://www.cnblogs.com/liujinggang/p/9609739.html 一. 软件平台与硬件平台 软件平台: 1.操作系统:Windows-8.1 2.开发套件 ...

  4. mongo批量写入es

    import pymongo import math from elasticsearch import Elasticsearch from elasticsearch import helpers ...

  5. 使用PyInstaller将.py文件打包并生成Windows下可执行的.exe文件

    最近要使用Qt写一个简单的GUI程序,因此使用了PyQt5来加快开发,使用PyQt5生成可执行的程序时,在Windows操作系统下可以使用pyinstaller库将.py文件及其相关依赖生成为.exe ...

  6. [转载]Java并发编程:volatile关键字解析

    Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...

  7. js中回调函数(callback)的一些理解

    前言 我个人在学习Node.js相关知识时遇到了回调函数这个概念,虽然之前已经在c,c++等编程语言中用到过它,但还一直未对其机制有深入了解,这次就来好好谈一下它. 概念理解 百度对它的解释是回调函数 ...

  8. nginx限流&健康检查

    Nginx原生限流模块: ngx_http_limit_conn_module模块 根据前端请求域名或ip生成一个key,对于每个key对应的网络连接数进行限制. 配置如下: http模块   ser ...

  9. 引爆炸弹——DFS&&联通块

    题目 链接 在一个$n \times m$方格地图上,某些方格上放置着炸弹.手动引爆一个炸弹以后,炸弹会把炸弹所在的行和列上的所有炸弹引爆,被引爆的炸弹又能引爆其他炸弹,这样连锁下去. 现在为了引爆地 ...

  10. CodeForces 834C - The Meaningless Game | Codeforces Round #426 (Div. 2)

    /* CodeForces 834C - The Meaningless Game [ 分析,数学 ] | Codeforces Round #426 (Div. 2) 题意: 一对数字 a,b 能不 ...