HAProxy高并发问题解决
2015-06-25 08:43
274 查看
HAProxy现网问题解决
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/03a71d4c9b46a1f30ff3b8da9967995a.png)
网络问题一般抓包可以定位,于是通过抓包发现失败的连接有个共同的现象,就是在5秒钟被HAProxy主动关闭,考虑到HAProxy的配置有个connectTimeout参数为5秒,应该是HAProxy连接后端的RMI服务器时失败。
通过抓包也验证了这一点,因为在5秒钟内,并没有搜到从HAProxy发起的对后端的SYN报文(除了check导致的握手)。
于是大胆怀疑问题出在HAProxy这边,否则至少HAProxy应该发起主动连接才对。
此时猜测HAProxy没有拿到可用的服务器。
考虑到如果HAProxy如果需要对远程服务器建立连接的话,肯定需要调用connect(...)这个C语言的API,所以全文搜索connect(....)
在函数
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/167be316c13b6fd920b9491dd20a4846.jpg)
中可以看到调用了connect(...)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/3a7df9216b171d57dcb750f893139dcd.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/f4e0960ed1924dcd3e3ee8fac4d5a1d4.jpg)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/38bef2cf1e8d5ff33dc4c79f0b9f8661.png)
上面这个代码是在event_accept函数中,也就是说在session中的client建立时,指定session的server端的connect函数,然后后面某个地方触发了tcpv4_connect_server函数。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/b31d64c7719fd99abf228e87ae678eab.png)
到这里就很清楚了,通过调用connect_server函数,然后根据之前指定的连接函数来触发之,由于我们在3.2中指定了tcpv4_connect_server函数,所以触发它,tcpv4_connect_server函数中又调用了connect函数,所以需要跟踪connect_server函数。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/78a8e95598f73d7cc042b54f2d50d1f8.png)
通过类似的调用机制,尝试定位问题。
更详细的调用栈就不一一列出。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/dd140d4e6af2b2b8bfc7548fb2514bfc.png)
这样就可以跟踪每个会话的具体行为。
日志格式如下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/d0212eb94b8989048e9259ed3ce2a2ce.png)
跟踪下代码:
于是在此段代码中添加日志,发现在蚂蚁窝环境下,每次此函数都返回1.
于是问题就知道出在什么地方了,这里返回1,导致每次对于某个后端服务器来说,
第一个请求建立连接会被响应,而后续的2,3.。。都被拒绝。
再查看日志,完全验证了这一点。
必然是通过此函数的逻辑来解决。
查看srv_dynamic_maxconn函数,发现如果在配置中可以有2种方法解决
1 将minconn设置为较大的一个参数
2直接设置为minconn与maxconn一样,彻底去掉最小限制,对于并发量按照maxconn来配置。
针对第2种情况,代码中可以看到
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/9b23aed969e7e104d3dc533a6e5fc35d.png)
也就是如果二者大小一样的话,max就返回s->maxconn。这样也没有问题。
下面是跟作者的邮件交流。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/edc72c7e3461a7a95385feefece5c376.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/d89f5b8279e319932d4b4d1319ae8f15.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/484a552f80974e71177c000181ecdaa7.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/32617459b60268b7d2c637d58d77ec8b.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/e595359b267aad95a786b043ff516e87.png)
也就是说,作者认为直接去掉minconn参数更好,于是我们在haproxy.cfg的配置中去掉了这个参数,通过日志打印,minconn的值会等于maxconn参数,也就是走了static limit这个分支。
至此问题得以解决,对HAProxy的理解比之前更进一步。
2 开源软件,网上有很多别人踩过的坑,可以尝试搜索是否已经有解决方案。
3 相对于所解决的问题,方法论非常重要,这个也需要经验的积累,比如本文中HAProxy问题的定位其实就在于connect(...) api的入口定位,整理出调用栈,然后添加日志逐步定位问题。
1问题描述
RMI上线后,现网的接口总是报告异常。![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/03a71d4c9b46a1f30ff3b8da9967995a.png)
2问题分析
通过对RMI源码的理解,这个是在RMI客户端那边没有可用的连接时,需要创建一个新的连接,但是连接失败。网络问题一般抓包可以定位,于是通过抓包发现失败的连接有个共同的现象,就是在5秒钟被HAProxy主动关闭,考虑到HAProxy的配置有个connectTimeout参数为5秒,应该是HAProxy连接后端的RMI服务器时失败。
通过抓包也验证了这一点,因为在5秒钟内,并没有搜到从HAProxy发起的对后端的SYN报文(除了check导致的握手)。
于是大胆怀疑问题出在HAProxy这边,否则至少HAProxy应该发起主动连接才对。
此时猜测HAProxy没有拿到可用的服务器。
3 HAProxy定位
3.1 connect(...)
刚开始怀疑是HAProxy没有拿到可用的服务器,那么从哪里入手解决问题呢?考虑到如果HAProxy如果需要对远程服务器建立连接的话,肯定需要调用connect(...)这个C语言的API,所以全文搜索connect(....)
在函数
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/167be316c13b6fd920b9491dd20a4846.jpg)
中可以看到调用了connect(...)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/3a7df9216b171d57dcb750f893139dcd.png)
3.2 tcpv4_connect_server的指定
查看tcpv4_connect_server的调用栈![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/f4e0960ed1924dcd3e3ee8fac4d5a1d4.jpg)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/38bef2cf1e8d5ff33dc4c79f0b9f8661.png)
上面这个代码是在event_accept函数中,也就是说在session中的client建立时,指定session的server端的connect函数,然后后面某个地方触发了tcpv4_connect_server函数。
3.3 tcpv4_connect_server的调用
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/b31d64c7719fd99abf228e87ae678eab.png)
到这里就很清楚了,通过调用connect_server函数,然后根据之前指定的连接函数来触发之,由于我们在3.2中指定了tcpv4_connect_server函数,所以触发它,tcpv4_connect_server函数中又调用了connect函数,所以需要跟踪connect_server函数。
3.4 connect_server的调用
查看调用栈,![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/78a8e95598f73d7cc042b54f2d50d1f8.png)
通过类似的调用机制,尝试定位问题。
更详细的调用栈就不一一列出。
3.4 修改源码添加自定义日志
为了定位问题的准确性,修改HAProxy【1.4.23】源码,在每一个session创建和后续行为都添加了自己的日志,同时每个日志行都添加了session的唯一ID.![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/dd140d4e6af2b2b8bfc7548fb2514bfc.png)
这样就可以跟踪每个会话的具体行为。
日志格式如下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/d0212eb94b8989048e9259ed3ce2a2ce.png)
3.5 srv_dynamic_maxconn
通过日志,我们发现,其实并不是HAProxy拿不到可用的服务器,而是拿到了之后,通过这个函数动态计算这个服务器当前的动态maxconn.跟踪下代码:
unsigned int srv_dynamic_maxconn(const struct server *s,struct session* session) { unsigned int max; if (s->proxy->beconn >= s->proxy->fullconn) { /* no fullconn or proxy is full */ max = s->maxconn; } else if (s->minconn == s->maxconn) { /* static limit */ max = s->maxconn; } else { max = MAX(s->minconn, s->proxy->beconn * s->maxconn / s->proxy->fullconn); } if ((s->state & SRV_WARMINGUP) && now.tv_sec < s->last_change + s->slowstart && now.tv_sec >= s->last_change) { unsigned int ratio; ratio = 100 * (now.tv_sec - s->last_change) / s->slowstart; max = MAX(1, max * ratio / 100); } return max; } |
于是问题就知道出在什么地方了,这里返回1,导致每次对于某个后端服务器来说,
第一个请求建立连接会被响应,而后续的2,3.。。都被拒绝。
再查看日志,完全验证了这一点。
4 解决方案
既然知道了问题所在,那么怎么解决?必然是通过此函数的逻辑来解决。
查看srv_dynamic_maxconn函数,发现如果在配置中可以有2种方法解决
1 将minconn设置为较大的一个参数
2直接设置为minconn与maxconn一样,彻底去掉最小限制,对于并发量按照maxconn来配置。
针对第2种情况,代码中可以看到
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/9b23aed969e7e104d3dc533a6e5fc35d.png)
也就是如果二者大小一样的话,max就返回s->maxconn。这样也没有问题。
5 与HAProxy作者的邮件交流
既然是开源软件,那么就可以直接跟作者交流。下面是跟作者的邮件交流。
5.1 发送邮件描述问题
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/edc72c7e3461a7a95385feefece5c376.png)
5.2 对方回复
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/d89f5b8279e319932d4b4d1319ae8f15.png)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/484a552f80974e71177c000181ecdaa7.png)
5.3 再次发送验证答案
于是发送自己的答案过去,看对方对我们的解决方案的评价,同时不忘热情赞美对方的软件之流行度。![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/32617459b60268b7d2c637d58d77ec8b.png)
5.4 对方的最终回应
![](https://oscdn.geek-share.com/Uploads/Images/Content/201711/e595359b267aad95a786b043ff516e87.png)
也就是说,作者认为直接去掉minconn参数更好,于是我们在haproxy.cfg的配置中去掉了这个参数,通过日志打印,minconn的值会等于maxconn参数,也就是走了static limit这个分支。
至此问题得以解决,对HAProxy的理解比之前更进一步。
6 后记
1 碰到问题,迎难而上,尤其是在有源码的情况下,直接debug或者看源码,肯定可以解决问题。一般在linux中c采用gdb,java采用jdb都可逐行跟踪,非常方便准确!2 开源软件,网上有很多别人踩过的坑,可以尝试搜索是否已经有解决方案。
3 相对于所解决的问题,方法论非常重要,这个也需要经验的积累,比如本文中HAProxy问题的定位其实就在于connect(...) api的入口定位,整理出调用栈,然后添加日志逐步定位问题。
相关文章推荐
- 内存的分配与回收
- Python标准库3.4.3-webbrowser-21.1
- SQL存储过程使用参考代码
- sierrawireless
- 实验四
- Win10最新手机预览版Build 10145要来了
- Apache Felix环境配备
- 小米与乐视的战争,全面剖析口水战背后的商业逻辑
- Linux在中国的没落
- iOS 阶段学习第七天笔记(函数、递归)
- Win10价格是多少?Windows 10欧美地区零售价官方公布
- Oracle dblink初始(一)
- 乐视2千字长文开喷小米:战胜也只能停留在发布会上
- iOS: 如何正确的绘制1像素的线
- 配置Maven
- 平台服务器句柄泄露问题的排查与解决
- Bootstrap系列 -- 20. 禁用状态
- 面试题总结(三)
- TQ210 交叉编译环境搭建
- vs2013中将原来在iis express中的网站转到iis中时,报“无法在web服务器上调试”的解决方法