我是靠谱客的博主 自觉夕阳,最近开发中收集的这篇文章主要介绍dubbo调用超时回滚_又是大干货!dubbo启用服务瞬间出现超时调用,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

简单介绍下:

dubbo是阿里开源出来的一款高性能远程调用框架,可以使开发者像使用本地服务一样调用远程服务,目前已经毕业为apache的顶级项目。

背景

目前生产环境发版可以简化为如下三个步骤: 假设服务A有10台机器A1~A10在提供服务:先针对机器A1~A5操作服务禁用,使调用请求不会发到即将部署的机器上。

对机器A1~A5进行部署,检查应用启动情况。

对机器A1~A5操作服务启用。

对A6~A10重复以上3步。

问题来了:基本上每次进行发布的时候,都会有调用方反馈出现调用超时。通过查检查上下游日志总结出以下规律:超时调用发生在上线过程的第三步,也就是操作服务启用瞬间。

调用方超时的请求在服务方没有对应日志。

猜想或许是因为应用刚启动完成,代码还没有进行jit编译,导致调用超时;

与dubbo的lazy连接有关?阅读源码可知,在开启lazy连接的情况下,消费端并不会着急与服务端建立tcp连接,而是在有调用发生时在去建立;

DubboProtocol.java

------------------

private ExchangeClient initClient(URL url) {

...

ExchangeClient client;

try {

// connection should be lazy

if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {

client = new LazyConnectExchangeClient(url, requestHandler);

} else {

client = Exchangers.connect(url, requestHandler);

}

} catch (RemotingException e) {

throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);

}

return client;

}启用服务瞬间有大量消费者来建立tcp连接,造成服务端来不及响应;

tcp三次握手

在这里回顾下tcp的三次握手过程:

作为必考题之一的tcp三次握手,他的过程肯定已经再熟悉不过了:客户端发生syn到服务端,客户端状态转为syn_send状态。

服务端接受到客户端发生的syn握手包,回复syn+ack,进入syn_rcvd状态。此时,服务端为半连接。

客户端收到服务端回传的syn+ack,并回传ack。客户端服务端进入established状态,变为全连接。那么,问题来了

大家都知道网络是天然的并发环境,server端在收到第三次握手的ack后如何去校验传入的ack值是否合法呢?

答案是系统维护了两个队列,分别称为半连接队列,和全连接队列。

第一步server收到client的syn后,把相关信息放到半连接队列中,同时回复syn+ack给client

第三步server收到client的ack,从半连接队列拿出相关信息放入到全连接队列中。

所以,完整的tcp三次握手过程应该是下面这样:

所以,既然有队列存在,那么就一定会有长度限制。

dubbo连接模型

再回到dubbo上。这里只介绍两个与启用服务超时相关的点。

tcp连接数

阿里这样介绍dubbo:以少量提供者支持大量的消费者调用(原话我不记得了,反正是这个意思)。

我认为其原因之一是dubbo使用了共享连接:

简单来说,共享连接意思就是在同一组消费者,提供者机器上,只维护一个tcp长连接,即使该消费者需要调用提供者提供的多个服务。

当然,目前生产环境都是集群部署,针对单台提供者来看的话,他所建立的tcp连接应该是下图这样:

如果所示,如果应用customer1有3台机器,customer2有4台机器,且他们都需要调用provider的服务,那么prodiver就需要维护最多3+4=7条tcp连接。

(为什么是最多,因为dubbo后台有一个线程去关闭有段时间不用的tcp连接。如果qps效高,且负载均衡策略会落在每一台机器上的话,连接可能被关闭的不会很多)。

lazy 连接

当客户端与服务端创建代理时,暂不建立 tcp长连接,当有数据请求时再做连接初始化。

超时问题

有了上面这些背景知识,再看最初的超时问题就比较容易理解了:

首先,dubbo服务端需要和每一天客户端建立一个tcp连接,如果调用方部署非常多,那么每个服务端需要建立的连接也是非常多的。 tcp连接的个数可以用这个命令来估算一下:

netstat -ant | wc -l

其次,由于我们开启了lazy连接,那么在启用服务后,消费者收到zk事件,开始分配调用到新的提供者上,在qps较高的情况下,服务端受到的压力情况可以用下面这段伪代码来描述:

CountDownLatch latch = new CountDownLatch(NUMBER_OF_TCP_CONN);

int i = 0;

while (i++< NUMBER_OF_TCP_CONN){

new Thread(()->{

latch.countDown();

latch.await();

//发送syn握手包

send_syn();

}).start();

}

可以想到,大家都在几乎同一时间来建立tcp连接,这种情况是不是让你想起了一种古老的拒绝服务攻击?

没错,就是syn flood攻击。简单来说就是通过发送大量的syn握手包,使服务端半连接队列溢出,从而无法提供服务。

如果怀疑tcp连接队列溢出,可以使用以下命令确认:

netstat -s | egrep "listen|LISTEN"

解决方案

知道了原因,那么问题就解决了一半。

最直观的做法就是调整tcp的半连接队列和全连接队列。

全连接队列的大小取决于:min(backlog, somaxconn) 。 backlog是在socket创建的时候传入的,somaxconn是一个os级别的系统参数。

半连接队列的大小取决于:max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)。

其中值得注意的是,在jdk中,backlog默认的设置的为50。

java.net.ServerSocket

---------------------

public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {

setImpl();

if (port < 0 || port > 0xFFFF)

throw new IllegalArgumentException(

"Port value out of range: " + port);

if (backlog < 1)

backlog = 50;

try {

bind(new InetSocketAddress(bindAddr, port), backlog);

} catch(SecurityException e) {

close();

throw e;

} catch(IOException e) {

close();

throw e;

}

}

public ServerSocket(int port) throws IOException {

this(port, 50, null);

}

所以,对于backlog参数,仅修改系统参数是不起作用的,需要将创建ServerSocket是传入的backlog参数一同修改。

最后

以上就是自觉夕阳为你收集整理的dubbo调用超时回滚_又是大干货!dubbo启用服务瞬间出现超时调用的全部内容,希望文章能够帮你解决dubbo调用超时回滚_又是大干货!dubbo启用服务瞬间出现超时调用所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(41)

评论列表共有 0 条评论

立即
投稿
返回
顶部