概述
简单介绍下:
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启用服务瞬间出现超时调用所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复