前面我们讨论系统调用的时候结论是耗时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. element之table自定义表头

    1.实现效果 2.使用render-header可以自定义表头 <el-table-column prop="date" label="日期" sorta ...

  2. windows 10 mysql-8.0.17-winx64的安装

    1.官网下载,并解压 https://dev.mysql.com/downloads/mysql/ 下载下来之后是一个zip的压缩包文件:mysql-5.7.26-winx64.zip,然后对这个文件 ...

  3. Image Processing and Analysis_8_Edge Detection:The Design and Use of Steerable Filters——1991

    此主要讨论图像处理与分析.虽然计算机视觉部分的有些内容比如特 征提取等也可以归结到图像分析中来,但鉴于它们与计算机视觉的紧密联系,以 及它们的出处,没有把它们纳入到图像处理与分析中来.同样,这里面也有 ...

  4. 【异常】诡异的mysql错误,Pagehelper插件混乱导致吗

    1 详细的异常信息 Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in ...

  5. redis cluster(集群)模式的创建方式

    redis常用的架构有三种,单例.哨兵.集群,其他的都说过了,这里只简单介绍集群搭建. 单例最简单没什么好说的. 哨兵之前说过,该模式下有哨兵节点监视master和slave,若master宕机可自动 ...

  6. java8 stream两个集体交集、差集、并集操作

    业务场景: 页面左右两个datagrid,双击左边datagrid行,移动到右边datagrid,右边datagrid行双击,移动到左边datagrid 点击保存,提交修改的数据到后台 后台要把查询到 ...

  7. 一个 TCP 连接可以发多少个 HTTP 请求?

    曾经有这么一道经典面试题:从 URL 在浏览器被被输入到页面展现的过程中发生了什么?相信大多数准备过的同学都能回答出来,但是如果继续问:收到的 HTML 如果包含几十个图片标签,这些图片是以什么方式. ...

  8. jQuery 实现手风琴菜单

    main.js $(function(){ var tmp = null, $title = $('.title'), $con = $('.title > ul'); $title.click ...

  9. C语言Ⅰ作业-05

    这个作业属于哪个课程 C语言程序设计Ⅰ 这个作业要求在哪里 https://www.cnblogs.com/tongyingjun/p/11722665.html 我在这个课程的目标是 熟练掌握如何用 ...

  10. struts2之登陆拦截

    针对登录拦截功能,我们需要设置拦截哪些方法和不拦截哪些方法 action action类中,处理登录时,将用户.密码绑定到session ActionContext ac = ActionContex ...