一次压力测试Bug排查-epoll使用避坑指南

2024-04-29 02:38

本文主要是介绍一次压力测试Bug排查-epoll使用避坑指南,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文始发于个人公众号:两猿社,原创不易,求个关注

Bug复现

使用Webbench对服务器进行压力测试,创建1000个客户端,并发访问服务器10s,正常情况下有接近8万个HTTP请求访问服务器。

结果显示仅有7个请求被成功处理,0个请求处理失败,服务器也没有返回错误。此时,从浏览器端访问服务器,发现该请求也不能被处理和响应,必须将服务器重启后,浏览器端才能访问正常。


排查过程

通过查询服务器运行日志,对服务器接收HTTP请求连接,HTTP处理逻辑两部分进行排查。

日志中显示,7个请求报文为:GET / HTTP/1.0的HTTP请求被正确处理和响应,排除HTTP处理逻辑错误

因此,将重点放在接收HTTP请求连接部分。其中,服务器端接收HTTP请求的连接步骤为socket -> bind -> listen -> accept;客户端连接请求步骤为socket -> connect。

listen

#include<sys/socket.h>
int listen(int sockfd, int backlog)
  • 函数功能,把一个未连接的套接字转换成一个被动套接字,指示内核应接受指向该套接字的连接请求。根据TCP状态转换图,调用listen导致套接字从CLOSED状态转换成LISTEN状态
  • backlog是队列的长度,内核为任何一个给定的监听套接口维护两个队列:
    • 未完成连接队列(incomplete connection queue),每个这样的 SYN 分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的 TCP 三次握手过程。这些套接口处于 SYN_RCVD 状态
    • 已完成连接队列(completed connection queue),每个已完成 TCP 三次握手过程的客户对应其中一项。这些套接口处于ESTABLISHED状态
connect
  • 当有客户端主动连接(connect)服务器,Linux 内核就自动完成TCP 三次握手,该项就从未完成连接队列移到已完成连接队列的队尾,将建立好的连接自动存储到队列中,如此重复。
accept
  • 函数功能,从处于ESTABLISHED状态的连接队列头部取出一个已经完成的连接(三次握手之后)。
  • 如果这个队列没有已经完成的连接,accept函数就会阻塞,直到取出队列中已完成的用户连接为止。
  • 如果,服务器不能及时调用 accept取走队列中已完成的连接,队列满掉后,TCP就绪队列中剩下的连接都得不到处理,同时新的连接也不会到来。

从上面的分析中可以看出,accept如果没有将队列中的连接取完,就绪队列中剩下的连接都得不到处理,也不能接收新请求,这个特性与压力测试的Bug十分类似


定位accept


//对文件描述符设置非阻塞
int setnonblocking(int fd){int old_option=fcntl(fd,F_GETFL);int new_option=old_option | O_NONBLOCK;fcntl(fd,F_SETFL,new_option);return old_option;
}//将内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT
void addfd(int epollfd,int fd,bool one_shot)
{epoll_event event;event.data.fd=fd;event.events=EPOLLIN|EPOLLET|EPOLLRDHUP;if(one_shot)event.events|=EPOLLONESHOT;epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);setnonblocking(fd);
}//创建内核事件表
epoll_event events[MAX_EVENT_NUMBER];
int epollfd=epoll_create(5);
assert(epollfd!=-1);//将listenfd设置为ET边缘触发
addfd(epollfd,listenfd,false);int number=epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);if(number<0&&errno!=EINTR)
{printf("epoll failure\n");break;
}for(int i=0;i<number;i++)
{int sockfd=events[i].data.fd;//处理新到的客户连接if(sockfd==listenfd){struct sockaddr_in client_address;socklen_t client_addrlength=sizeof(client_address);//定位accept//从listenfd中接收数据int connfd=accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);if(connfd<0){printf("errno is:%d\n",errno);continue;}//TODO,逻辑处理}
}

分析代码发现,web端和服务器端建立连接,采用epoll的边缘触发模式同时监听多个文件描述符。

epoll的ET、LT
  • LT水平触发模式
    • epoll_wait检测到文件描述符有事件发生,则将其通知给应用程序,应用程序可以不立即处理该事件。
    • 当下一次调用epoll_wait时,epoll_wait还会再次向应用程序报告此事件,直至被处理。
  • ET边缘触发模式
    • epoll_wait检测到文件描述符有事件发生,则将其通知给应用程序,应用程序必须立即处理该事件。
    • 必须要一次性将数据读取完,使用非阻塞I/O,读取到出现eagain

从上面的定位分析,问题可能是错误使用epoll的ET模式


代码分析修改

尝试将listenfd设置为LT阻塞,或者ET非阻塞模式下while包裹accept对代码进行修改,这里以ET非阻塞为例。

for(int i=0;i<number;i++)
{int sockfd=events[i].data.fd;//处理新到的客户连接if(sockfd==listenfd){struct sockaddr_in client_address;socklen_t client_addrlength=sizeof(client_address);//从listenfd中接收数据//这里的代码出现使用错误while ((connfd = accept (listenfd, (struct sockaddr *) &remote, &addrlen)) > 0){if(connfd<0){printf("errno is:%d\n",errno);continue;}//TODO,逻辑处理}}
}

将代码修改后,重新进行压力测试,问题得到解决,服务器成功完成75617个访问请求,且没有出现任何失败的情况。压测结果如下:


复盘总结

  • Bug原因
    • established状态的连接队列backlog参数,历史上被定义为已连接队列和未连接队列两个的大小之和,大多数实现默认值为5。当连接较少时,队列不会变满,即使listenfd设置成ET非阻塞,不使用while一次性读取完,也不会出现Bug
    • 若此时1000个客户端同时对服务器发起连接请求,连接过多会造成established 状态的连接队列变满。但accept并没有使用while一次性读取完,只读取一个。因此,连接过多导致TCP就绪队列中剩下的连接都得不到处理,同时新的连接也不会到来。
  • 解决方案
    • 将listenfd设置成LT阻塞,或者ET非阻塞模式下while包裹accept即可解决问题。

该Bug的出现,本质上对epoll的ET和LT模式实践编程较少,没有深刻理解和深入应用。

如果觉得有所收获,请顺手点个关注吧,你们的举手之劳对我来说很重要。

这篇关于一次压力测试Bug排查-epoll使用避坑指南的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/944914

相关文章

某云服务器ECS使用记录

最近为了进行linux学习,买了某云的ECS服务器,选了linux系统,1C2G 远程连接 就是linux的命令终端环境,写了个 hello world  程序使用 g++ 编译通过 也下载了xshell进行远程连接,没问题 后续用来进行 linux c++开发学习使用,还有远程开发学习使用。

linux select/epoll

转载自https://www.jianshu.com/p/ed1f9e9a1982,如有侵权,联系删除

交流回馈电网测试电源的技术突破

交流回馈电网测试电源:电力系统中的关键组件的深度解析析 交流回馈电网测试电源作为电力系统的关键组成部分,其重要性和作用不容忽视。交流回馈电网测试电源是一种电力测试设备,主要用于模拟真实电网环境,对电力电子设备进行长时间的工作负载测试,以评估其性能、稳定性和可靠性。这种测试电源能够模拟电网的电压、频率和各种电网扰动,同时可以将多余的电能回馈到电网中,实现能量的双向流动。与传统的电网测试电源相

WHAT - 前端安全性测试和常见攻击手段

目录 一、安全性测试二、前端安全性测试三、跨站脚本(XSS)攻击1. 介绍2. 三大类型反射型 XSS(Reflected XSS)存储型 XSS(Stored XSS)DOM 型 XSS(DOM-based XSS) 3. xss 盲打4. xss 水坑攻击:XSS Watering Hole Attack5. 防范措施 四、跨站请求伪造(CSRF)1. 介绍防范措施 五、身份认证机制现状

【Jsp】第五课 过滤器的概念与使用

概念 什么是过滤器? Filter也称之为过滤器          过滤器本质上就是一个特殊的类 通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截。 Servlet过滤器负责过滤的web组件有servlet、JSP或者是HTML文件,其过滤过程如下图 Servlet过滤器具有以下特点          Servlet过滤器可以检查和修改re

【Java】导入别人的项目至开发软件中后,无法使用和运行,而且还会产生报错的解决办法

概述 我们在学习编程过程中,或者工作过程中,经常需要查阅别人的项目代码,学习和借鉴别人的代码等等,总会遇到导入别人代码之后,运行不了,代码报错,而不知道什么原因,无法解决,导致学习不下去,或者工作无法进行受阻,这是所有开发者心灰意冷的时候。那么,我们来解决一下, 当将从网上查找的项目,或者是CSDN上下载的项目,或者是初学者下载老师的项目后,产生报错,如图   本质上不是代码报错,而且

JDBC的使用五大步骤以及查询操作-数据库编程(二)

jdbc的使用步骤 1.加载jdbc的驱动。 2.打开数据库的连接。 3.建立一个会话,然后执行增删改查等基本的操作。 4.对结果进行处理 5.对环境进行清理,比如关闭会话等。 查询操作 首先用Class类的forname方法来实例化一个驱动实例。 然后分别初始化:Connection Statement ResultSet三个类,这三个类分别用来是建立连接,执行操作和对结果进行处

使用百度地图开发一个导航定位demo-android学习之旅(77)

首先介绍如何导入百度地图 步骤(其实官方文档写的很清楚了)http://developer.baidu.com/map/index.php?title=androidsdk/guide/introduction## 1.注册开发者账号 2.注册你的应用,登陆控制台,然后输入数字签名和包名,得到开发Id 3. 下载android sdk进行配置,我用的是Android studio,配置步骤

Spring 使用 Groovy 实现动态server

本人在项目中遇到这么个需求,有一个模块的server方法需要频繁修改经阅读可以使用 Groovy 使用java脚本来时 pom坐标 <dependency><groupId>org.codehaus.groovy</groupId><artifactId>groovy</artifactId><version>3.0.9</version></dependency> 实例代码 pu

Qt程序打包命令windeployqt.exe的使用方法

Qt程序打包命令windeployqt.exe的使用方法: 一、该命令是Qt自带的程序,位于d:\Qt\Qt5.14.2\5.14.2\mingw74_64\bin文件夹中。 二、添加环境变量。 三、把Qt生成的debug或release文件夹中的bin文件兲中的内容拷贝至D:\temp文件夹中,并在文件管理器的路径行中输入cmd三个字并回车。进入cmd窗口。 四、qml工程的发布方法:如果是qm