服务器CURL卡死事故

记录一次因CURL卡死造成的服务器事故

前一段时间, 项目在外网服务器进行了首次测试, 测试开始不到15分钟, 服务器就开始告警, HTTP服务异常.
告警的服务是埋点日志模块, 对libcurl进行二次封装, 异步向HTTP服务器发送埋点日志, 该服务内不有一个分钟定时器, 定时检测http请求处理速度, 当堆积的http请求数量超过一定限制时, 就开始报警, 并暂停服务功能.

问题排查

查看了服务器CPU, 网络, IO, 除了内存因为http请求堆积而开始缓慢增长, 均无异常, 但是发送的请求包就像泥牛入海, 发出后一直未收到服务器回包, 又检查了HTTP服务器, 状态也无异常.
在之后两个多小时的时间里, 内存持续增长到了4个G, 迅速关闭该服务入口.
然后在服务器上执行命令netstat -an | grep HTTP服务器IP地址.
此时发现连接到HTTP服务器的TCP连接只有1个, 而且状态正常, 看服务器状态已经无法排查问题, 只能检查代码排除问题.

故障定位

发送HTTP请求的部分代码如下:

1
2
3
4
5
6
curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, ENABLE_FOLLOWLOCATION);
curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT_MS, connect_timeout_ms);
// curl_easy_setopt(handle, CURLOPT_TIMEOUT, 5L);
curl_easy_setopt(handle, CURLOPT_URL, url);
...

最关键的一行代码, 被人误注释了…
CURLOPT_TIMEOUT选项设置的是整个请求的超时时间, 如果不设置该选项, 就会导致在出现网络异常等情况时, 请求失败后卡死的问题
而且我们关闭了CURLOPT_FORBID_REUSE选项, 所以libcurl底层会复用TCP连接, 在项目中, http请求的目标主机始终是不变的, 发起请求时, libcurl发现目标主机项目, 当前有存活可用的TCP连接, CURLOPT_FORBID_REUSE选项也被关闭, 此时就会复用TCP连接, 但是, 在请求过程中, 因为网络波动等异常, 导致这条请求处理失败, 此时又没有设置请求的超时时间, 导致这条请求直接卡死, 而后续的请求也就因为这个原因一起被卡死, 被一个请求, 卡死了一整个功能.