从零开始自制实现WebServer(八)---- 花费两天解决性能瓶颈问题 介绍一路调试历程以及推荐各种好用的工具


全流程实现博客链接


从零开始自制实现C++ High-Performance WebServer 全流程记录(基于muduo网络库)


前引


刚刚在写博客前 想到了好多好多以前自己的往事
回想到 其实这一路走过来 真的觉得自己与绝大部分人走过的 小学初中高中都不同 也包括自己的大学

回想起来自己原来真的自己也做过好多错事 犯过太多太多错误 也做过很多对不起别人的事情 但是距离真的自己感觉到做了错事的时候 在我记忆中 应该是停留在高二的时间了 距离现在也快三年多四年了

有时想想啊 人啊 很多时候做事情真的得三思 思考现在 思考以往 思考未来 也许是这三四年内自己没有在做过什么错事 心地也还算是善良 一直以来都遇到好的人 善良的人
到现在 写博客前 吃晚饭的之前去买汉堡奶茶的期间 跟我的家人通话 真的有的时候啊 回想到以往 觉得真的人真的太奇妙了 回想我自己这一路走来 尤其是这几年 真的是啊

一直以来我都觉得我运气真的很好 很多时候都能遇到对我人生有启示作用的 对我未来指明道路的人或事情 包括选择了我喜欢的专业 我也还算不笨 一直以来我都可以做自己想做的事情 我的家人也一直都是无理由的支持我

我很感恩 我现在所拥有的一切
我一直也很相信 人在做 天在看 无论现在做的是好事情 还是坏事情 总会有一天会全部返还回来 不论是好的结果还是坏的结果
希望之后自己也一直能像现在这样

原来一直不懂得一句话的含义
但行好事 莫问前程

现在也能感受到一点了 真的可能是这样的吧

做善良的人 做一个对人善良的人 做一个心地善良的人
多做好事 心中做到问心无愧
人会是一帆风顺的 会有好结果的

好了 这部分就先写到这里吧


(八)---- 继续整改代码 优化代码逻辑


1、兜兜转转了一天 熟悉了很多工具 但是没有找到优化


哎 无奈啊 苦恼啊
应该是哪里的逻辑还是有问题的

我目前的代码框架c10k 应该是承受不了的 每次都会出现failed…
而且最明显的问题就是 估计每次现在代码主要循环 循环在主线程太明显了
但是 不幸中的万幸是在哪里呢 现在基本上文件描述符是全部正常关闭了

很明显 现在有代码逻辑 估计不知道是哪里出现了问题 刚刚我仔细对比了一下 muduo库跑的时候 和我的代码跑的时候的 文件描述符打开情况

就是用lsof -p 随时都在看 我的文件描述符最多的一个时刻 我看了看 大概就是在打开80多个还是什么 这里的80多个应该是 此刻接受比关闭多的80多个此刻 而muduo库多的时候 我看到了990u 也就是某一刻能够达到990多个 每一瞬间几百个是非常正常的情况


但是现在我确实不知道哪里的代码逻辑出现了问题… 是卡在IO太多了 还是什么地方有问题 反正看出来我的主线程的负担太重了 不论是C1000 还是C10k 基本上我的主线程都是CPU 80左右…
而且最烦人的是什么 我的服务器 目前还没有实现Logging 也就是日记库 而muduo库的日记库其实是占比cpu最多的… 如果完全像我的代码目前一样没有任何输出的话 肯定也是远超我的代码的…

哎 头疼 不知道从哪个地方入手 也不知道是什么地方主线程负担会这么重… 干脆先从主线程入手 对比一下我和muduo库的代码 哪里有非常明显的差异的地方 我再来分析一下这种差异会导致什么结果


strace是个好用的工具
我用这个工具 逐步逐步分析了我的代码 和 muduo库的运行流程

分析了一下午 东改改 西改改 我还是没找到是什么问题导致的 为什么主线程的负荷会这么重…

真的很头疼 算了 晚上回来就先不看代码了
因为长期的 运算一段时间 就ctrl+cstrace给中断看结果 一下午虚拟机死机了好几次 我发现ctrl+z 再关闭终端 基本上不会死机 而ctrl+c则会导致死机率异常之高

现在看到代码都头疼 晚上回来算了 先看看书 能不能找到一些突破口 有没有一些灵感发现自己主线程哪里出现了问题

在这里插入图片描述


昨天自己找了一天的问题 删删补补 左改右改 修复了一些bug 就是很明显的编程问题 自己也通过strace尝试去找到主线程究竟是哪里 这么拖慢了服务器的运行速度 昨天真的找了一天都没有找到

… 自己今天上午又去找到了一些很明显的代码错误
但还是没有找到服务器 究竟是哪里拖慢了主线程

找了那么久 我自己也测试了那么久 发现主线程负荷是在80%左右 也就是基本上CPU都轮转都在主线程上了 由于某个非常核心的代码段 严重影响了主线程接收新的文件描述符 而muduo的话 则是cpu非常均匀 我只要电脑开了性能模式 在10000并发的情况下 电脑是被彻底带活了 风扇乱吹

我在测试连续300秒的压力测试情况下 我的服务器是出现了问题的… 如果是30/60秒的话 都是没有出现任何failed的
之后在认真看看muduo库代码了 仔细找一下究竟是哪里是核心问题 哎…
真的是脑袋上的头发都快拧没了 都没找到 吃完午饭再回来看看了


在我刚刚仔细思考了 会不会由于muduo在测试的时候 是通过cout来输出了HEADER GET /hello 而我们的代码没有输出I/O 而主要集中运行代码子线程的任务相对而言就轻松的多 自然而然主线程则CPU率就会高得多…

果不其然 在我关闭了I/O 后 httpserver 也就是muduo库的httpserver明显主线程 CPU率高了起来 但是同样的 子线程也是一样的火力全开

我的电脑 刚刚去百度了一下 i5-10400发现是6核12线程。。 其实就是6核 我还一直以为是8核呢
之后就都改了 改成6核好了
好了 不是几核的问题 我的子线程给我的感觉就是 不干活… 明显的不干活 主线程挺累的 感觉应该还是代码逻辑出现了问题 才会导致这么大差异

没办法 还是一点点的改吧

在这里插入图片描述

在这里插入图片描述


2、解决了部分问题 但还是存在性能瓶颈…


刚刚找到一个非常严重的问题 也是解决了部分的性能问题
问题就是我channel 发现有事件时 比如有EPOLLINEPOLLOUT 竟然敲代码的时候 是用if-else 来判断的。。。。 那么就会造成一次触发不能执行着两个事件

子进程开始稍微动起来了 但是刚刚测试了c10k 还是性能差了一些 还是无法在c10k的情况下 稳定的执行 还是存在10000多的failed…
我在想muduo会不会也是跟我的情况差不多呢 30秒、60秒不出问题 300秒就会出现failed 但是没想到muduo c10k也能正常处理

说明我的代码还是存在有一些地方的问题 下面两张截图的是muduo的 子线程cpu能到50%
我改过的代码也只能到35%… 但至少这个算是这两天第一个有突破性的改进了…

muduo库 300s c10k(10k并发连接)在这里插入图片描述
在这里插入图片描述



love'6 poor webserver(temporary will be better)
c10k在这里插入图片描述在这里插入图片描述


感觉分析 系统调用调用了多少次没啥用… 因为毕竟可以正常运行 只是哪里确实逻辑可能写的不是很好 而导致效率比较低


3、终于解决性能瓶颈问题 从来没有考虑的地方出了偏差 两天得来不易的问题终于得以解决!


1、问题刚开始出现的出处

哈哈哈 昨晚开开心心终于解决问题了! 今天一觉睡到10点钟 由于找了两天的问题解决了 晚上请室友吃火锅了 今天就不出寝室了 不去图书馆了1

说实话 在这两天的折磨中 我真的到最后 昨天出图书馆的时候 想起来真的找不到问题出在哪里 因为我用strace 也实实在在的跟踪了我的tiny_muduo 和muduo库的代码 实在不知道哪里出了问题 因为运行轨迹也是这么多

之前我还在想 是不是因为重复的write read 哪里重复读/写而导致的性能瓶颈 但是已经检查两天了 从早到晚的看代码都没有发现哪里出了问题
有些代码有问题的 也不是核心的性能瓶颈的源头 因为说白了 核心就在于epoll_wait tcpconnection acceptor channel

对于一些核心函数 哪怕性能有差距 自己没考虑什么优化 也不至于会差距这么大
真的昨晚在最绝望的时候 甚至产生了 要不把这个项目删了 一切从新来过的想法 直接完完全全抄muduo 看看哪里出了问题 就算这20天的时间打了水漂

可能是老天不负有心人吧 也可能是真的自己在学习的路上看了一些书籍 导致这个地方我写的时候出了偏差 我马上去拿一下书籍 因为我记得很清楚那两本书里面这个地方的参数都是这样的 等一下…


下面是书里面 关于listen后面的backlog 为什么我会在这里犯错…
写到这里 应该各位读者就知道了 问题出现在了listen的第二个参数
如果感兴趣的读者 或者确实想看一下原来我写的代码的话 可以去我写的第五篇webserver连载博客看一下 我当时设置的backlog就是5

至于当时为什么我要设置成5 因为刚开始接触网络编程 而且写到现在 都是一步步迭代过来的 当时根本没考虑到10000并发 最开始先想的是 怎么写一个能够正确运行的echo server 先把正确运行给弄出来了 再说高并发的事情

而且主要刚开始学习网络编程的时候 确实这些细节也不知道这么重要…
当时也想的是 一个这么经典的backlog值5 这么多本书里面也写到了 也就继续用下去 当时理解成这个值是一个比较好的比较中立的值…

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


2、解决问题尝试过的各种方法 心路历程

其实这部分 也算是分享一下 遇到这些问题 我目前尝试过的方法
因为我觉得这部分还是挺宝贵的 写像这样的网络库 尤其是要求比较高并发的

更容易遇到各种问题

写出一个能够正常响应的服务器 只要有思路 有代码逻辑框架 其实个人觉得实现起来 就没有那么困难了
主要重要的是 知道从哪里入手 从哪里开始下手 这部分其实我在最开始的几篇博客中 写的还是挺多的 因为那个时候我也很迷茫 也没有引路人能够借鉴指导我怎么在这条路走下去

这也是我写这些博客的真的原因吧 真的希望我花了时间能够帮助到各位
至少留下点属于我自己的东西

Digi ghetto这是我们的季节
疯狂的钻石才是黄金体验

这是我原来写操作系统最喜欢听的歌 自立巷 这里就不说了 哈哈


这里就来正式介绍一下 这一路上 到现在我大概用了哪些方法调试吧
感觉网上写这种的还是挺少的…
我把我觉得很有用的一些工具也放在下面


1、printf(输出调试法)

这个是我还算比较喜欢的一个方法 其实原来做很多东西 最开始的时候 都是通过这个方法来调试的 不管是 原来学数据结构 还是写操作系统 还是平时刷题 有的时候 感觉有些值 或者有些条件 或者有些运行逻辑不符合自己的预期的时候 在一些适当的地方 设置一些printf 然后自己用一些测试用例去尝试 然后逐步找到问题 我觉得这个还算是非常实用的方法了


2、strace调试(适合运行指令较多/需要看代码运行轨迹 的调试)

strace 调试的话 我觉得还是非常好用的
如果我这个问题不是一些比较隐晦的运行逻辑 或者是确实是通过代码轨迹看不出来的问题的话

我觉得这个是绝对的利器了
尽管在解决我的问题上没有有很大的用 但是通过这两天 我觉得之后在工作中 如果出现问题了 那肯定是可以派上大用场的

这里就浅显的介绍一下我比较常用的一些组合技吧

先用pidof <process_name> 把进程的PID知道了 或者用top 如果你的进程占用率高的话 也可以直接看到pid
知道pid后 可以有两个方式 我觉得是解决问题比较好的呈现方式

1、观察运行轨迹 把运行轨迹记录下来
sudo strace -p <pid> -o <strace_file_name> -o是指定输出轨迹在哪个文件 -p是指定跟踪进程
然后觉得快结束的适合 可以ctrl+c运行进程 或者也可以ctrl+c strace 然后记录就可以去查看了

但是需要注意一下 strace在运行的时候 cpu占用比相当高 估计是在不停的IO导致的… 会使你的运行进程明显被拖慢
下面是示例图
在这里插入图片描述


2、记录系统调用次数 记录系统调用的次数 可以观察有哪些系统调用明显拖慢了进程运行
sudo strace -c -p <pid> -o <strace_file_name>
其实也就加了个c 如果有些系统调用明显调用的太多了 看这个也能看出问题来 下面给张我看我的代码运行系统调用的图

下面的截图 只运行了3.8秒 正常的话 应该要多运行一会的…

在这里插入图片描述


3、top(监视服务器或者进线程状态)

这个top 真的也是帮了我大忙了
我分析我的服务器是否运行正常 基本上就是靠top
我看cpu率 还有 内存占用率来判断是否运行

当然 压力测试正常运行的连接数 才是最主要判断是否允许的最主要的

top有几个参数是我非常常用的

例如 top就是直接的包括进程的 没有筛选的
-p 可以指定进程号监视(配合pidof)
-H 监视包括同一进程下的所有线程

在监视的时候 就这个项目而言
启动后 先pidof查看进程号 后用top -H -p <pid>即可
还有一些其他的参数啊 那些就烦请各位自己去网上或者相关博客再深入了


4、tcpdump(linux下网络抓包工具)

尽管在交互的时候 tcpdump也只帮我过我一次忙 但是也算有用
由于是本机测试 而且 tcpdump参数太多了 我也用的不是太多
下面就介绍一下 我一般常用的指令吧

sudo tcpdump -i lo tcp port <port>
-i 主要就是指定主机
后面的tcp port 就是tcp协议 指定端口 端口一般的话是服务器的设置端口

我其实用的最多的就是这个指令了 有的时候看服务器没有close文件描述符 不太清楚网页发出的交互是不是关闭还是保持连接的 就需要抓一下包 看看是否发过来请求了 确定服务器 客户端目前的交互状态

由于tcpdump的指令太多了 这里也就不介绍了 详细的还有一些指令大家自己去查一下吧


5、netcat(好用的不行 模拟客户端)

netcat是刚开始我测试是否成功交互的工具 太好用了

直接讲一下我最常用的指令吧

nc <tcp address> <port> 例如 nc 127.0.0.1 233
然后就充当客户端与其交流了

如果是以HTTP协议交互的话 就需要加个-C
例如 nc -C <tcp address> <port> 例如 nc -C 127.0.0.1 233
加个-C 其实也就是每句话回车后 自动加个\r\n 也没其他的

还有 有的时候希望 断开的时间自动一点 就可以加个 -w <time>
例如 nc 127.0.0.1 2333 -w 60

表示60s内没有消息交互的话 就在60s后自动断开连接了


5、pstack(用过一次 查看进/线程栈帧的)

尽管这里用过一次 没啥用 但觉得有些场合还是挺有用的
… 这里还是介绍一下吧 而且gdb好像自带的有些问题 必须要自己再把源码弄过来 然后再链接
最后会友情提供正确的pstack代码 具体怎么把错误的pstack重新链接这里就不介绍了 网上查一下就好了

pstack <pid> 运行示例如下 有的时候有些程序卡在一个循坏 有的时候查看详细的此时的线程栈 还是有奇效的

在这里插入图片描述


这里友情提供正确的pstack代码

#!/bin/sh
 
if test $# -ne 1; then
    echo "Usage: `basename $0 .sh` <process-id>" 1>&2
    exit 1
fi
 
if test ! -r /proc/$1; then
    echo "Process $1 not found." 1>&2
    exit 1
fi
 
# GDB doesn't allow "thread apply all bt" when the process isn't
# threaded; need to peek at the process to determine if that or the
# simpler "bt" should be used.
 
backtrace="bt"
if test -d /proc/$1/task ; then
    # Newer kernel; has a task/ directory.
    if test `/bin/ls /proc/$1/task | /usr/bin/wc -l` -gt 1 2>/dev/null ; then
    backtrace="thread apply all bt"
    fi
elif test -f /proc/$1/maps ; then
    # Older kernel; go by it loading libpthread.
    if /bin/grep -e libpthread /proc/$1/maps > /dev/null 2>&1 ; then
    backtrace="thread apply all bt"
    fi
fi
 
GDB=${GDB:-gdb}
 
# Run GDB, strip out unwanted noise.
# --readnever is no longer used since .gdb_index is now in use.
$GDB --quiet -nx $GDBARGS /proc/$1/exe $1 <<EOF 2>&1 |
set width 0
set height 0
set pagination no
$backtrace
EOF
/bin/sed -n \
    -e 's/^\((gdb) \)*//' \
    -e '/^#/p' \
    -e '/^Thread/p'

6、gdb(原来做csapp用过 目前用的很少)

如果可以单步调试 或者要查看内存中的数据的话
gdb绝对是神器 如果是掌控全局的角度的话 我觉得strace相对gdb是更好的工具

这里就不详细介绍gdb了 没有贬低或者说gdb不好的意思
各有各的优势 只是确实我现在没怎么用gdb… 这里不详细介绍了


7、webbench(核心web压力测试工具)

其实类似这样的工具还是挺多的
但是确实 这个工具感觉还是超级好用
这里就不贴链接了 百度一下 webbench git clone到本地 make install一下就好了

下面详细写一下我在压力测试最常用的指令吧
webbench -c <并发数> -t <测试多少秒> <压力测试URL 例如http://127.0.0.1:80/hello>

基本上我最常用的就是这个了 哈哈 用一下就好了
有的时候等不及的时候 就直接ctrl+c就好了 但是我觉得ctrl+z再关闭终端 没有那么容易死机…


3、成功解决问题 性能测试

这里就用 300秒的 c10k来测试一下 我的WebServer和 muduo吧
下一篇再来贴一下写到现在的代码 肯定到后面还需要去优化的 最后我贴到github的时候 肯定是我觉得最好的状态了 写的我觉得 thats good了 我才会停下来

下面就贴一下性能测评吧 还有top检测出来的cpu 子线程主线程的情况
哈哈 姜还是老的辣啊 没想到性能还是有所差距
但是没有关系 后面还是找一下原因 把一些地方进行优化一下
但现在大部头的地方也已经解决好了 也不至于像之前那样性能有那么多的差距了

但看起来 我的子线程都挺忙的啊…
比muduo还忙 会不会是我的子线程里面有些工作代码相较于muduo写的没有那么好… 不太清楚 之后再来研究研究 但这部分也就到这里就先结束吧~

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


结束语


那这一篇就到这里结束了 不容易啊
后面可能还要优化一下 工作线程里面的代码 感觉应该是工作线程里面的代码 写的没有那么优化导致的…

后面再改改了 各位下一篇再见啦~

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Love 6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值