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

标签: dev | 发表时间:2020-01-02 00:00 | 作者:
出处:http://itindex.net/relian

作者简介

 

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

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


工作中,常常会遇到连接超时的问题,一般都是先检查端口状态,然后再检查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:port

at  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}

do

lsof -nPi | grep 20xxx

sleep 1

done

 

从结果中,可以看到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 20xxx

java  24715  Tim  217u  TCP  *:20xxx (LISTEN)

 

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


$ lsof -nPi | grep 20xxx

java   24715  Tim  0t0  TCP  10.xx.xx.1:20xxx->10.xx.xx.139:12918 (ESTABLISHED)

java   24715  Tim  0t0  TCP  *:20xxx (LISTEN)

 

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

 

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


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


   #!/bin/bash   foriin`seq 1 3600`   do   t=`timeout0.1 telnet localhost 20xxx  </dev/null 2>&1|grep -c'Escapecharacter is'`   echo$(date)"20xxx check result:"$t   sleep0.005   done


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


 

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


 

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


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


$ netstat -s

3220 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包被丢?

128

3000

128

5000

少量丢包

1024

5000

1024

10000


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


【推荐阅读】




 “携程技术”公众号

  分享,交流,成长



相关 [携程 dubbo 问题] 推荐:

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

- - IT瘾-dev
  顾海洋,携程框架架构研发部技术专家,负责携程分布式服务化领域的工作. 目前主要负责 Dubbo 在携程的二次开发和推广工作. 李伟,携程云平台技术专家,2012年加入携程,目前专注于云原生方向技术的研究和落地,先后参与负责过携程部署架构改造、接入层架构升级等项目的设计和推广. 工作中,常常会遇到连接超时的问题,一般都是先检查端口状态,然后再检查CPU、Memory、GC、Connection等机器指标是否正常.

dubbo 问题整理

- - 行业应用 - ITeye博客
1 面试题:Dubbo中zookeeper做注册中心,如果注册中心集群都挂掉,发布者和订阅者之间还能通信么. 可以的,启动dubbo时,消费者会从zk拉取注册的生产者的地址接口等数据,缓存在本地. 每次调用时,按照本地存储的地址进行调用. 注册中心对等集群,任意一台宕掉后,会自动切换到另一台 . 注册中心全部宕掉,服务提供者和消费者仍可以通过本地缓存通讯 .

Dubbo之connet timeout问题排查

- - 开源软件 - ITeye博客
    购物车依赖推荐的dubbo接口,推荐服务每天凌晨3点会批量下线推荐dubbo服务,全量更新商品,更新完以后在执行上线操作,每天凌晨3点10分左右,购物车工程都会出现5000左右的connection timeout error. 正常依赖的dubbo服务工程在启动的时候,消费端会经常出现connection timeout error.

[原]Dubbo实例

- -
Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案. Remoting: 网络通信框架,实现了sync-over-async 和 request-response 消息机制. RPC: 一个远程过程调用的抽象,支持负载均衡、容灾和集群功能. Registry: 服务目录框架用于服务的注册和服务事件发布和订阅.

被事务代理的spring service 不能使用注解方式发布dubbo服务的问题解决

- - 企业架构 - ITeye博客
       使用 @com.alibaba.dubbo.config.annotation.Service 发布dubbo服务的时候,当服务类没有加入@Transactional的时候没有问题..        但是当加入事务后,spring bean 事务代理, dubbo的 AnnotationBean 扫描 类执行下面的代码的时候就获取不到对应的注解,也就发布不了服务:.

项目中单元测试容易出现的普遍问题归纳(Junit/Spring/Spring-test/Dubbo/RocketMQ/JAVA)

- - 编程语言 - ITeye博客
   最近公司要求项目在使用maven构建的时候不能跳过test的生命周期,也就是通过mvn test命令需要将整个项目运行起来. 因为之前项目组的成员都是在eclipse中去执行的unit test,在maven对所有模块构建的都是直接-Dmaven.test.skip=true的方式直接跳过UT的.

DUBBO用户指南

- - 开源软件 - ITeye博客
随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进. 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本. 此时,用于简化增删改查工作量的 数据访问框架(ORM) 是关键. 当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率.

dubbo服务降级实现dubbo-plus/circuitbreaker at master · dubboclub/dubbo-plus · GitHub

- -
向注册中心写入动态配置覆盖规则:(通过由监控中心或治理中心的页面完成). 表示消费方对该服务的方法调用都直接返回null值,不发起远程调用. 屏蔽不重要服务不可用时对调用方的影响. 表示消费方对该服务的方法调用在失败后,再返回null值,不抛异常. 容忍不重要服务不稳定时对调用方的影响. Dubbo支持服务降级,并且支持当服务出现异常的时候进行服务降级处理,但是存在一下几个缺陷.

学习dubbo源代码

- - Java - 编程语言 - ITeye博客
1、Dubbo与Spring的整合. Dubbo在使用上可以做到非常简单,不管是Provider还是Consumer都可以通过Spring的配置文件进行配置,配置完之后,就可以像使用springbean一样进行服务暴露和调用了,完全看不到dubboapi的存在. 这是因为dubbo使用了spring提供的可扩展Schema自定义配置支持.

dubbo的安装和使用

- - CSDN博客云计算推荐文章
随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进. 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本. 此时,用于简化增删改查工作量的 . 数据访问框架(ORM) 是关键.