写点什么

携程一次 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:311384

评论

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

2023年国内AI Agent下项目大盘点,科技大厂与创业公司齐头并进

王吉伟频道

创业 融资 大语言模型 AI Agent AI智能体

AE脚本-快速创建微风摇曳摆动波浪动画 Breeze

Rose

做CAE分析用哪个显卡?CAE咨询

思茂信息

仿真 CAE CAE软件

AudFree Tidable Music Converter for Mac(Tidal音乐转换器)

Rose

Tidal音乐转换器 苹果电脑音频转换器 AudFree Tidal Music

程序员都必须知道的Vue 开发技巧

不在线第一只蜗牛

vue.js Vue 开发技巧

PostgreSQL数据库开发工具Navicat for PostgreSQL中文版

Rose

数据库设计 Navicat for PostgreSQL PostgreSQL数据库开发

关于 IntelliJ IDEA 中 Schedule for Addition 的问题

Rose

IntelliJ IDEA

2024年,苟住求活,才是长期主义

老张

第二曲线 职场发展 互联网裁员

矢量图形转换工具vector magic for Mac 兼容macos14系统

Rose

mac软件下载 Vector Magic破解版 矢量图像转换工具

如何查看崩溃日志

Avdshare Audio Converter for Mac(性能超强的音频格式转换器)

Rose

苹果软件下载 Avdshare Audio Converter Mac音频格式转换器

实时获取建材网商品数据:API实现详解与代码示例

Noah

MongoDB 数据库管理和开发Navicat for MongoDB【Mac/win】

Rose

MongoDB数据库 Navicat数据库下载 Navicat for MongoDB中文

5分钟使用Hologres实时湖仓加速分析挑战赛来袭

阿里云大数据AI技术

NFTScan | 01.01~01.07 NFT 市场热点汇总

NFT Research

NFT NFT\ NFTScan

LED屏租赁需求激增,为何LED显示屏租赁如此受欢迎?

Dylan

综艺节目 应用 LED显示屏 户外LED显示屏 led显示屏厂家

草料荣获第五届中国工业互联网大赛新锐组二等奖

草料二维码

二维码 草料二维码

强大的原型设计:Kite Compositor最新激活版

胖墩儿不胖y

Mac软件 原型设计工具 Mac动画软件

万界星空科技云MES,助力客户快速构建数字工厂

万界星空科技

mes 万界星空科技 低代码云MES

macos强大的界面设计和原型制作工具:Kite

Rose

Mac动画原型设计 Kite for Mac破解 Kite Mac下载 苹果软件下载站

首个云原生、分布式、全栈国产化银行核心业务系统投产上线丨TiDB × 杭州银行

编程猫

【年后跳槽必看篇-非广告】Spring Bean的生命周期

派大星

spring 跳槽季 Java 面试题

OpenAI 也在 996?一位离职员工自白:代码贡献第四,经常工作 6 天丨 RTE 开发者日报 Vol.121

声网

TikTok海外直播专线:优化你的海外直播体验

Ogcloud

直播 直播优化 TikTok

AE脚本-图层分布路径形状高级控制 Tweaks

Rose

[Mac/win数据库最新版]Navicat for MySQL 永久激活教程

Rose

Navicat for MySQL 数据库管理开发 Navicat数据库软件下载

软件测试开发/全日制/测试管理丨接口功能测试

测试人

软件测试 自动化测试 接口测试 测试开发 测试管理

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