转载

【每日一博】HAProxy 高并发问题解决

HAProxy 现网问题解决

1 问题描述

RMI 上线后,现网的接口总是报告异常。

【每日一博】HAProxy 高并发问题解决

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(....)

在函数

【每日一博】HAProxy 高并发问题解决

中可以看到调用了 connect(...)

【每日一博】HAProxy 高并发问题解决

3.2 tcpv4_connect_server 的指定

查看 tcpv4_connect_server 的调用栈

【每日一博】HAProxy 高并发问题解决

【每日一博】HAProxy 高并发问题解决

上面这个代码是在 event_accept 函数中,也就是说在 session 中的 client 建立时,指定 session server 端的 connect 函数,然后后面某个地方触发了 tcpv4_connect_server 函数。

3.3 tcpv4_connect_server 的调用

【每日一博】HAProxy 高并发问题解决

到这里就很清楚了,通过调用 connect_server 函数,然后根据之前指定的连接函数来触发之,由于我们在 3.2 中指定了 tcpv4_connect_server 函数,所以触发它, tcpv4_connect_server 函数中又调用了 connect 函数,所以需要跟踪 connect_server 函数。

3.4 connect_server 的调用

查看调用栈,

【每日一博】HAProxy 高并发问题解决

通过类似的调用机制,尝试定位问题。

更详细的调用栈就不一一列出。

3.4  修改源码添加自定义日志

为了定位问题的准确性,修改 HAProxy 1.4.23 】源码,在每一个 session 创建和后续行为都添加了自己的日志,同时每个日志行都添加了 session 的唯一 ID.

【每日一博】HAProxy 高并发问题解决

这样就可以跟踪每个会话的具体行为。

日志格式如下:

【每日一博】HAProxy 高并发问题解决

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.

于是问题就知道出在什么地方了,这里返回 1 ,导致每次对于某个后端服务器来说,

第一个请求建立连接会被响应,而后续的 2,3. 。。都被拒绝。

再查看日志,完全验证了这一点。

解决方案

既然知道了问题所在,那么怎么解决?

必然是通过此函数的逻辑来解决。

查看 srv_dynamic_maxconn 函数,发现如果在配置中可以有 2 种方法解决

minconn 设置为较大的一个参数

2 直接设置为 minconn maxconn 一样,彻底去掉最小限制,对于并发量按照 maxconn 来配置。

针对第 2 种情况,代码中可以看到

【每日一博】HAProxy 高并发问题解决

也就是如果二者大小一样的话, max 就返回 s->maxconn 。这样也没有问题。

HAProxy 作者的邮件交流

既然是开源软件,那么就可以直接跟作者交流。

下面是跟作者的邮件交流。

5.1  发送邮件描述问题

【每日一博】HAProxy 高并发问题解决

5.2  对方回复

【每日一博】HAProxy 高并发问题解决

【每日一博】HAProxy 高并发问题解决

5.3  再次发送验证答案

于是发送自己的答案过去,看对方对我们的解决方案的评价,同时不忘热情赞美对方的软件之流行度。

【每日一博】HAProxy 高并发问题解决

5.4  对方的最终回应

【每日一博】HAProxy 高并发问题解决

也就是说,作者认为直接去掉 minconn 参数更好,于是我们在 haproxy.cfg 的配置中去掉了这个参数,通过日志打印, minconn 的值会等于 maxconn 参数,也就是走了 static limit 这个分支。

至此问题得以解决 , HAProxy 的理解比之前更进一步。

后记

碰到问题,迎难而上,尤其是在有源码的情况下,直接 debug 或者看源码,肯定可以解决问题。一般在 linux c 采用 gdb,java 采用 jdb 都可逐行跟踪,非常方便准确!

开源软件,网上有很多别人踩过的坑,可以尝试搜索是否已经有解决方案。

相对于所解决的问题,方法论非常重要,这个也需要经验的积累,比如本文中 HAProxy 问题的定位其实就在于 connect(...) api 的入口定位,整理出调用栈,然后添加日志逐步定位问题。

正文到此结束
Loading...