我叫你不要重试,你非得重试。这下玩坏了吧? (中)

简介: 我叫你不要重试,你非得重试。这下玩坏了吧? (中)

FailoverCluster源码


源码位于org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker中:

image.png

等等,不对啊,前面刚刚说的是 3 次,怎么一转眼就是 2 次了呢?

你别急啊

image.png

你看第 61 行的最后还有一个 "+1" 呢?

你想一想。我们想要在接口调用失败后,重试 n 次,这个 n 就是 DEFAULT_RETRIES ,默认为 2 。那么我们总的调用次数就是 n+1 次了。

所以这个 "+1" 是这样来的,很小的一个知识点,送给大家。

另外图中标记了红色五角星★的地方,第62到64行。也是很关键的地方。对于 retries 参数,在官网上的描述是这样的:

image.png

不需要重试,请设为 0 。我们前面分析了,当设置为 0 的时候,只会调用一次。

但是我也看见过retries配置为 -1 的。-1+1=0。调用0次明显是一个错误的含义。但是程序也正常运行,且只调用一次。

这就是标记了红色五角星的地方的功劳了。

防御性编程。哪怕你设置为 -10000 也只会调用一次。

下面这个图片是我对 doInvoke 方法进行一个全面的解读,基本上每一行主要的代码都加了注释,可以点开大图查看:

微信图片_20220427210659.png


如上所示,FailoverClusterInvoker 的 doInvoke 方法主要的工作流程是:

  • 首先是获取重试次数,然后根据重试次数进行循环调用,在循环体内,如果失败,则进行重试。
  • 在循环体内,首先是调用父类 AbstractClusterInvoker 的 select 方法,通过负载均衡组件选择一个 Invoker,然后再通过这个 Invoker 的 invoke 方法进行远程调用。
  • 如果失败了,记录下异常,并进行重试。

注意一个细节:在进行重试前,重新获取最新的 invoker 集合,这样做的好处是,如果在重试的过程中某个服务挂了,可以通过调用 list 方法保证 copyInvokers 是最新的可用的 invoker 列表。

整个流程大致如此,不是很难理解。


HttpClient 使用样例


接下来,我们看看 apache 的 HttpClients 中的重试是怎么回事。

也就是这个类:org.apache.http.impl.client.HttpClients

首先,废话少说,弄个 Demo 跑一下。

先看 Controller 的逻辑:

@RestController
public class TestController {
    @PostMapping(value = "/testRetry")
    public void testRetry() {
        try {
            System.out.println("时间:" + new Date() + ",数据库插入成功");
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

同样是睡眠 5s,模拟超时的情况。

HttpUtils 封装如下:

public class HttpPostUtils {
    public static String retryPostJson(String uri) throws Exception {
        HttpPost post = new HttpPost(uri);
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(1000)
                .setConnectionRequestTimeout(1000)
                .setSocketTimeout(1000).build();
        post.setConfig(config);
        String responseContent = null;
        CloseableHttpResponse response = null;
        CloseableHttpClient client = null;
        try {
            client = HttpClients.custom().build();
            response = client.execute(post, HttpClientContext.create());
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                responseContent = EntityUtils.toString(response.getEntity(), Consts.UTF_8.name());
            }
        } finally {
            if (response != null) {
                response.close();
            }
            if (client != null){
                client.close();
            }
        }
        return responseContent;
    }
}

先解释一下其中的三个设置为 1000ms 的参数:

connectTimeout:客户端和服务器建立连接的timeout

connectionRequestTimeout:从连接池获取连接的timeout

socketTimeout:客户端从服务器读取数据的timeout

大家都知道一次http请求,抽象来看,必定会有三个阶段

  • 一:建立连接
  • 二:数据传送
  • 三:断开连接

当建立连接的操作,在规定的时间内(ConnectionTimeOut )没有完成,那么此次连接就宣告失败,抛出 ConnectTimeoutException。

后续的 SocketTimeOutException 就一定不会发生。

当连接建立起来后,才会开始进行数据传输,如果数据在规定的时间内(SocketTimeOut)沒有传输完成,则抛出 SocketTimeOutException。如果传输完成,则断开连接。

测试 Main 方法代码如下:


public class MainTest {
    public static void main(String[] args) {
        try {
            String returnStr = HttpPostUtils.retryPostJson("http://127.0.0.1:8080/testRetry/");
            System.out.println("returnStr = " + returnStr);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


首先我们不启动服务,那么根据刚刚的分析,客户端和服务器建立连接会超时,则抛出 ConnectTimeoutException 异常。

直接执行 main 方法,结果如下:


image.png

符合我们的预期。

现在我们把 Controller 接口启动起来。

由于我们的 socketTimeout 设置的时间是 1000ms,而接口里面进行了 5s 的睡眠。

根据刚刚的分析,客户端从服务器读取数据肯定会超时,则抛出 SocketTimeOutException 异常。

Controller 接口启动起来后,我们运行 main 方法输出如下:

image.png

这个时候,其实接口是调用成功了,只是客户端没有拿到返回。

image.png

这个情况和我们前面说的 Dubbo 的情况一样,超时是针对客户端的。

即使客户端超时了,服务端的逻辑还是会继续执行,把此次请求处理完成。

执行结果确实抛出了 SocketTimeOutException 异常,符合预期。

但是,说好的重试呢?

目录
相关文章
|
前端开发 安全 程序员
测试大姐趁我下班点又提了个bug!前端你咋多个options请求?
测试大姐趁我下班点又提了个bug!前端你咋多个options请求?
88 0
|
Cloud Native Go
如何处理面试拒绝:失败并不是终点
如何处理面试拒绝:失败并不是终点
87 0
|
数据采集 Python
python爬虫怎么解决超时timeou错误
python爬虫怎么解决超时timeou错误
|
测试技术 内存技术
|
设计模式 Kubernetes 算法
leader说用下httpclient的重试,但我没用,因为我有更好的方案。
leader说用下httpclient的重试,但我没用,因为我有更好的方案。
195 0
|
SQL 运维 监控
一个诡异的MySQL查询超时问题,居然隐藏着存在了两年的BUG
一个诡异的MySQL查询超时问题,居然隐藏着存在了两年的BUG
195 0
|
JSON Java 程序员
写了这么久的业务连异常都不知道怎么处理吗
前言 文本已收录至我的GitHub仓库,欢迎Star:github.com/bin39232820… 种一棵树最好的时间是十年前,其次是现在
220 0
|
负载均衡 Dubbo 应用服务中间件
我叫你不要重试,你非得重试。这下玩坏了吧? (下)
我叫你不要重试,你非得重试。这下玩坏了吧? (下)
353 0
我叫你不要重试,你非得重试。这下玩坏了吧? (下)
|
XML Dubbo 应用服务中间件
我叫你不要重试,你非得重试。这下玩坏了吧? (上)
我叫你不要重试,你非得重试。这下玩坏了吧? (上)
120 0
我叫你不要重试,你非得重试。这下玩坏了吧? (上)
|
Java Spring 容器
从0到1带你手撸一个请求重试组件,不信你学不会!
从0到1带你手撸一个请求重试组件,不信你学不会!
从0到1带你手撸一个请求重试组件,不信你学不会!