生成式AI领域的最新成果都在这里!抢 QCon 展区门票 了解详情
写点什么

携程一次 Dubbo 连接超时问题的排查

  • 2020-02-19
  • 本文字数:4238 字

    阅读完需:约 14 分钟

携程一次Dubbo连接超时问题的排查

工作中,常常会遇到连接超时的问题,一般都是先检查端口状态,然后再检查 CPU、Memory、GC、Connection 等机器指标是否正常。如果都在合理范围内就会怀疑到网络或者容器上,甩手丢给网络组同事去排查。


今天,我们想分享一个高并发场景导致的 connect timeout,对原因以及过程的分析或许可以帮助大家从容地面对类似问题。

一、问题背景

携程度假事业部的某个核心服务在两个机房一共有 80 台机器,每台机器都是 4C8G 的 docker 容器。这个服务的调用方比较多,几十个调用方的机器加起来大概有 1300 多台。


SOA over CDubbo 是将现有 SOA 框架的 HTTP 传输协议切换到 TCP 协议,能够解决长尾问题以及提供更好的稳定性。大概实现原理是,服务端通过 CDubbo 启动代理服务,客户端在服务发现后与服务端同步建立 TCP 长连接,请求也会在 TCP 通道传输。


但是,度假事业部的这个服务每次发布总是会有部分客户端报 connect timeout,触发大面积的应用报警。


com.alibaba.dubbo.rpc.RpcException: Fail to create remoting client for service(dubbo://ip:port/bridgeService)  failed to connect to server /ip:port, error message is:connection timed out:  /ip:portat  com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol.initClient(DubboProtocol.java:364)at  com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol.getSharedClient(DubboProtocol.java:329)at  com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol.getClients(DubboProtocol.java:306)at  com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol.refer(DubboProtocol.java:288)
复制代码


从日志分析,是 CDubbo 代理服务 TCP 连接失败,还好当初设计的时候考虑到降级机制,没有影响到用户流量。有同事提到既然没有影响,是否可以考虑把日志降级。这么诡异的问题,不知道是否会有其他层面的问题需要去优化的呢,作为执着的技术人员,我们决定排查到底。

二、服务的端口是否异步打开

调用方的每台机器都要跟 160 个服务端实例建立连接,但是客户端看到的报错量只有几个。所以,最开始怀疑客户端的连接发到服务端,但是端口没有来得及打开,导致少量的连接失败了。


翻了下 SOA 框架在处理实例注册的代码,启动 CDubbo 代理是在注册之前,而且是同步启动的,这样的话就否定了端口没打开的可能。


三、怀疑注册中心推送出现了问题

正常情况下的注册发现机制是在服务端健康检查通过后,再把实例推送到客户端。是否注册中心推送出了问题,服务没注册完就把实例推送到客户端了?或者,客户端实例缓存出现问题导致的呢?


这类问题还是要从日志入手,翻了下 Dubbo 的代码,如果 Netty 打开端口之后,是会记录端口打开时间的。



从日志系统可以看到端口是在 16:57:19 就已经被打开了。



客户端在 16:57:51 发起的连接居然失败了,这个时候端口肯定是已经被打开了。从这个层面推断注册中心或者缓存机制应该是没有问题的。



那么,是否端口打开后又被莫名其妙的关闭了呢?

四、怀疑端口打开后又被莫名其妙的关闭

不确定是否服务启动后,会有某些未知的场景触发端口被莫名其妙的关闭。于是,在本地模拟服务启动,启动过程中通过 shell 脚本不停的打印端口的状态。


通过以下这段脚本,每 1s 就会打印一次 20xxx 端口的状态。


for i in {1..1000}dolsof -nPi | grep 20xxxsleep 1done
复制代码


从结果中,可以看到 20xxx 端口一直处于 listen 状态,也就是正常情况下并不会被莫名其妙的关闭。


TCP *:20xxx (LISTEN)
复制代码

五、增加连接被 accept 的日志

Dubbo 已经打印了前面看到的端口打开的日志,如果再能够看到服务端连接被 accept 的情况就好了。


继续翻了 Dubbo 的代码,对 Netty3 的版本来说,连接被 accept 之后会执行 channelConnected 的。那么,只要在这里加点日志,就可以知道端口什么时候被打开,以及连接什么时候进来的了。


以下是基于 Dubbo 2.5.10 版本增加的日志。



业务同事帮忙升级了版本之后,服务端在 16:57:51:394 已经有连接被 accept 了,连接报错时间是 16:57:51:527,也就是 accept 连接过程中只有一部分被拒绝了。



那么,是没有收到这个连接的 syn,还是把 syn 给丢弃了呢,必须要抓包看看了。

六、服务端的 TCP 抓包

正常情况下,需要服务端和客户端同时抓包才有意义。但是,客户端数量实在太多,也不知道哪台机器会报超时,两端一起抓的难度有点打,所以决定先只抓服务端试试。


首先摘掉服务的流量,然后在 Tomcat 重启的过程中抓 TCPdump。从 TCP dump 的结果中可以看到,服务端有一阵子收到了 TCP 的 syn,但是全部没有回 ack。可是 HTTP 的 syn 却正常的回复了 syn+ack,难道是应用层把 syn 包给丢了?



没有回 syn+ack 是谁的问题呢,Netty 丢掉的吗?还是操作系统呢?为此,我们做了个小实验。


小实验


如果是应用层丢掉的,那么肯定要从 Netty 的入口处就调试代码。Netty3 的 NioServerBoss 收到请求,会在以下箭头 2 处对连接进行 accept,所以计划在 1 处打上断点。


启动服务端后,再启动客户端,连接进来的时候的确会被箭头 1 处 block 住。



通过 TCP 抓包发现在 accept 之前就已经回复 syn+ack 给客户端了。



这个时候,顺便看了下本机的 20xxx 端口情况,只有一个 listen 状态,没有任何客户端跟它连接。


$ lsof -nPi | grep 20xxxjava  24715  Tim  217u  TCP  *:20xxx (LISTEN)
复制代码


之后,继续执行代码,Netty 在 socket 的 accept 执行之后,就可以看到连接已经 ESTABLISHED 成功了。Netty 在 accept 连接之后会注册到 worker 线程进行 IO 处理。


$ lsof -nPi | grep 20xxxjava   24715  Tim  0t0  TCP  10.xx.xx.1:20xxx->10.xx.xx.139:12918 (ESTABLISHED)java   24715  Tim  0t0  TCP  *:20xxx (LISTEN)
复制代码


这就证明连接失败不是应用层丢掉的,肯定是操作系统层面的问题了,那么容器内的连接是否会成功呢?

七、从容器内发起的连接是否能成功

通过重启服务的时候,脚本不停的对服务端端口发起连接,看看是否会有失败的情况。


#!/bin/bashfor i in`seq 1 3600`dot=`timeout0.1 telnet localhost 20xxx  </dev/null 2>&1|grep -c 'Escapecharacter is'`echo$(date) "20xxx check result:" $tsleep0.005done
复制代码


从脚本的执行结果看到,容器内发起的连接有时也是会失败的,以下黄色高亮的 0 就是失败的连接。



同时,从服务端的抓包结果看到,也会有 syn 被丢弃的情况。


八、全连接队列满导致的 SYN 丢包

syn 包被操作系统丢弃,初步猜测是 syn queue 满了,通过 netstat -s 查看队列的情况。


$ netstat -s3220 times the listen queue of a socket  overflowed 3220 SYNs to LISTEN sockets dropped
复制代码


问题的原因基本找到了,可是导致 syn 被丢弃的原因还是不知道,这里我们先复习下三次握手的整个过程。



上图结合三次握手来说:


1、客户端使用 connect()向服务端发起连接请求(发送 syn 包),此时客户端的 TCP 的状态为 SYN_SENT;


2、服务端在收到 syn 包后,将 TCP 相关信息放到 syn queue 中,同时向客户端发送 syn+ack,服务端 TCP 的状态为 SYN_RCVD;


3、客户端收到服务端的 syn+ack 后,向服务端发送 ack,此时客户端的 TCP 的状态为 ESTABLISHED。服务端收到 ack 确认后,从 synqueue 里将 TCP 信息取出,并放到 accept queue 中,此时服务端的 TCP 的状态为 ESTABLISHED。


我们大概了解了 syn queue 和 accept queue 的过程,那再看上面的问题,overflowed 代表 accept queue 溢出,dropped 代表 syn queue 溢出,那么 3220 SYNs to LISTEN socketsdropped,这个就是代表 syn queue 溢出吗?


其实并不是,我们翻看了源码:



可以看到 overflow 的时候 TCP dropped 也会增加,也就是 dropped 一定大于等于 overflowed。但是结果显示 overflowed 和 dropped 是一样的(都是 3220),只能说明 accept queue 溢出了,而 syn queue 溢出为 0(3220-3220=0)。


从上图的 syn queue 和 accept queue 的设计,accept queue 满了应该不影响 syn 响应,即不影响三次握手。


带着这个疑问我们再次翻看了内核源码:



可以看到建连接的时候,会判断 accept queue,如果 acceptqueue 满了,就会 drop,即把这个 syn 包丢掉了。

九、全连接队列怎么调整?

我们再看下服务器的实际情况,通过 ss -lnt 查看当前 20xxx 端口的 accept queue 只有 50 个,这个 50 是哪里来的呢?


$ ss -lnt State Recv-Q Send-Q Local  Address:Port Peer Address:Port LISTEN 0 50 *:20xxx *:*
复制代码


然后看了下机器内核的 somaxconn 也远远超过 50,难道 50 是应用层设置的?


$ cat  /proc/sys/net/core/somaxconn 128
复制代码


Accept queue 的大小取决于:min(backlog, somaxconn),backlog 是在 socket 创建的时候传入的,somaxconn 是一个内核级别的系统参数。


Syn queue 的大小取决于:max(64,tcp_max_syn_backlog),不同版本的 os 会有些差异。


再研究下 Netty 的默认值,可以发现 Netty3 初始化的时候 backlog 只有 50 个,Netty4 已经默认升到 1024 了。



业务换了新的包,重新发布后发现 accept queue 变成了 128,服务端 syn 被丢弃的问题已经没有了,客户端连接也不再报错。


在应用启动时间,通过 shell 脚本打印队列大小,从图片中可以看到,最大 queue 已经到了 101,所以之前默认的 50 个肯定是要超了。



从这个截图,也可以知道为啥前面提到的 HTTP 请求没有 syn 丢包了。因为 Tomcat 已经设置了 backlog 为 128,而且 HTTP 的连接是 lazy 的。但是,我们对 TCP 连接的初始化并不是 lazy 的,所以在高并发的场景下会出现 accept queue 满的情况。

十、调整 backlog 到底有多大效果?

针对这个问题,我们还专门做了个试验了下,从实验结果看调整带来的效果还是比较明显的。


服务端:1 台 8C 的物理机器


客户端:10 台 4C 的 docker


Backlog每秒并发建连数SYN包被丢?
1283000
1285000少量丢包
10245000
102410000


可以看到,对 8C 的机器 backlog 为 128 的情况下,在每秒 5000 建连的时候就会出现 syn 丢包。在调整到 1024 之后,即使 10000 也没有任何问题。当然,这里提醒下,不要盲目的调整到很高的值,是否可以调整到这么高,还要结合各自服务器的配置以及业务场景。


作者介绍


顾海洋,携程框架架构研发部技术专家,负责携程分布式服务化领域的工作。目前主要负责 Dubbo 在携程的二次开发和推广工作。


李伟,携程云平台技术专家,2012 年加入携程,目前专注于云原生方向技术的研究和落地,先后参与负责过携程部署架构改造、接入层架构升级等项目的设计和推广。


本文转载自公众号携程技术(ID:ctriptech)。


原文链接


https://mp.weixin.qq.com/s/oKtu3AA9D3y--xMDQ8EARw


2020-02-19 20:311015

评论

发布
暂无评论
发现更多内容

2023我的前端面试小结

loveX001

JavaScript 前端

美团面试全流程详解:一面 + 二面

Java你猿哥

ssm Java 面试 面经 java

校招前端二面经典react面试题及答案

beifeng1996

前端 React

字节前端二面react面试题(边面边更)

beifeng1996

前端 React

​在行 | AI赋能,为行业发展创造全新可能

用友BIP

一文带你深度探析:软硬科技协同创新正当时

加入高科技仿生人

人工智能 科技 科技创新 ChatGPT

软件测试/测试开发 | 电商业务的性能测试(一): 必备基础知识

测试人

软件测试 自动化测试 测试开发

问:React的useState和setState到底是同步还是异步呢?

beifeng1996

React

从智能合约到机器学习:NFT 与 AI 技术的结合

NFT Research

大数据 AI NFT

两会共议北斗发展,华大北斗芯片领衔

江湖老铁

问:你是如何进行react状态管理方案选择的?

beifeng1996

前端 React

IDO代币合约质押流动性挖矿分红系统开发(开发逻辑及源码)

系统开发咨询1357O98O718

接近8000字的Spring/Spring常用注解总结!安排!

Java你猿哥

spring Spring Boot ssm java

为什么00后都不知道什么是报销?

用友BIP

差旅报销

好家伙!阿里最新版高并发系统设计涵盖了“三高”所有骚操作

Java你猿哥

java面试 面经 春招 java 八股文

卓越工作的4个特征

凌晞

项目管理 技术管理 构架

精心整理SpringBoot学习笔记,从Web入门到系统架构

Java你猿哥

Java 面试 面经 校招 春招 java

常见的ftp文件传输工具有哪些?推荐最佳ftp文件传输工具

镭速

80%的前端开发都答不上来的js异步面试题

loveX001

JavaScript 前端

flutter系列之:创建一个内嵌的navigation

程序那些事

flutter 架构 大前端 程序那些事

加速数据要素价值释放,用友打造高性能时序数据库

用友BIP

数据分析

算力网络智能选路新向导

鲸品堂

算力 电信运营商 算力网络 企业号 3 月 PK 榜

4万字,阿里云《大型体育赛事云上实战精选》电子书发布!

阿里云视频云

云计算 视频云

代币质押流动性挖矿分红开发案例丨代币质押流动性质押挖矿分红系统开发(案例及详细)

系统开发咨询1357O98O718

腾讯前端一面常考面试题

loveX001

JavaScript 前端

什么大模型?我是时尚产业“大模王”!

白洞计划

AI AI制衣

一场HttpClient调用未关闭流引发的问题

石臻臻的杂货铺

HttpClient

一文吃透什么是低代码开发?

这我可不懂

低代码 低代码平台 JNPF

企业数字化转型的核心——工作流

力软低代码开发平台

观测云产品更新|数据存储策略变更优化;新增支持创建重名的仪表板、笔记、自定义查看器;DQL 参数生效优先级调整等

观测云

数据存储 可观测 产品更新 观测云 可观测性用观测云

JS模块化—CJS&AMD&CMD&ES6-前端面试知识点查漏补缺

loveX001

JavaScript 前端

携程一次Dubbo连接超时问题的排查_文化 & 方法_顾海洋_InfoQ精选文章