美文网首页
弹力设计之重试设计

弹力设计之重试设计

作者: 匠丶 | 来源:发表于2021-09-24 10:08 被阅读0次

关于重试,这个模式应该是一个很普遍的设计模式了。当我们把单体应用服务化,尤其是微服务化,本来在一个进程内的函数调用就成了远程调用,这样就会涉及到网络上的问题。

网络上有很多的各式各样的组件,如 DNS 服务、网卡、交换机、路由器、负载均衡等设备,这些设备都不一定是稳定的。在数据传输的整个过程中,只要任何一个环节出了问题,最后都会影响系统的稳定性。

重试的场景

所以,我们需要一个重试的机制。但是,我们需要明白的是,“重试”的语义是我们认为这个故障是暂时的,而不是永久的,所以,我们会去重试。

设计重试时,我们需要定义出什么情况下需要重试,例如,调用超时、被调用端返回了某种可以重试的错误(如繁忙中、流控中、维护中、资源不足等)。

而对于一些别的错误,则最好不要重试,比如:业务级的错误(如没有权限、或是非法数据等错误),技术上的错误(如:HTTP 的 503 等,这种原因可能是触发了代码的 bug,重试下去没有意义)。

重试的策略

关于重试的设计,一般来说,都需要有个重试的最大值,经过一段时间不断的重试后,就没有必要再重试了,应该报故障了。在重试过程中,每一次重试失败时都应该休息一会儿再重试,这样可以避免因为重试过快而导致网络上的负担加重。

在重试的设计中,我们一般都会引入,Exponential Backoff 的策略,也就是所谓的 " 指数级退避 "。在这种情况下,每一次重试所需要的休息时间都会成倍增加。这种机制主要是用来让被调用方能够有更多的时间来从容处理我们的请求。这其实和 TCP 的拥塞控制有点像。

如果我们写成代码应该是下面这个样子。

首先,我们定义一个调用返回的枚举类型,其中包括了 5 种返回错误——成功 SUCCESS、维护中 NOT_READY、流控中 TOO_BUSY、没有资源 NO_RESOURCE、系统错误 SERVER_ERROR。

public enum Results { SUCCESS, NOT_READY, TOO_BUSY, NO_RESOURCE, SERVER_ERROR}

接下来,我们定义一个 Exponential Backoff 的函数,其返回 2 的指数。这样,每多一次重试就需要多等一段时间。如:第一次等 200ms,第二次要 400ms,第三次要等 800ms……

public static long getWaitTimeExp(int retryCount) { 
long waitTime = ((long) Math.pow(2, retryCount) ); 
return waitTime;
}

下面是真正的重试逻辑。我们可以看到,在成功的情况下,以及不属于我们定义的错误下,我们是不需要重试的,而两次重试间需要等的时间是以指数上升的。


public static void doOperationAndWaitForResult() {
    
    // Do some asynchronous operation.
long token = asyncOperation();

    int retries = 0;
    boolean retry = false;

    do {
        // Get the result of the asynchronous operation.
        Results result = getAsyncOperationResult(token);

        if (Results.SUCCESS == result) {
            retry = false;
        } else if ( (Results.NOT_READY == result) ||
                      (Results.TOO_BUSY == result) ||
                      (Results.NO_RESOURCE == result) ||
                      (Results.SERVER_ERROR == result) ) {
            retry = true;
        } else {
            retry = false;
        }
        if (retry) {
            long waitTime = Math.min(getWaitTimeExp(retries), MAX_WAIT_INTERVAL);
            // Wait for the next Retry.
            Thread.sleep(waitTime);
        }
    } while (retry && (retries++ < MAX_RETRIES));
}

重试设计的重点

  • 要确定什么样的错误下需要重试;
  • 重试的时间和重试的次数。这种在不同的情况下要有不同的考量。有时候,而对一些不是很重要的问题时,我们应该更快失败而不是重试一段时间若干次。比如一个前端的交互需要用到后端的服务。这种情况下,在面对错误的时候,应该快速失败报错(比如:网络错误请重试)。而面对其它的一些错误,比如流控,那么应该使用指数退避的方式,以避免造成更多的流量。
  • 如果超过重试次数,或是一段时间,那么重试就没有意义了。这个时候,说明这个错误不是一个短暂的错误,那么我们对于新来的请求,就没有必要再进行重试了,这个时候对新的请求直接返回错误就好了。但是,这样一来,如果后端恢复了,我们怎么知道呢,此时需要使用我们的熔断设计了。
  • 重试还需要考虑被调用方是否有幂等的设计。如果没有,那么重试是不安全的,可能会导致一个相同的操作被执行多次。
  • 对于有事务相关的操作。我们可能会希望能重试成功,而不至于走业务补偿那样的复杂的回退流程。对此,我们可能需要一个比较长的时间来做重试,但是我们需要保存请求的上下文,这可能对程序的运行有比较大的开销,因此,有一些设计会先把这样的上下文暂存在本机或是数据库中,然后腾出资源来做别的事,过一会再回来把之前的请求从存储中捞出来重试。

相关文章

  • 弹力设计之重试设计

    关于重试,这个模式应该是一个很普遍的设计模式了。当我们把单体应用服务化,尤其是微服务化,本来在一个进程内的函数调用...

  • 弹力设计之熔断设计

    熔断机制这个词肯定不陌生,它的灵感来源于我们电闸上的“保险丝”,当电压有问题时(比如短路),自动跳闸,此时电路就会...

  • 弹力设计之限流设计

    保护系统不会在过载的情况下出现问题,我们就需要限流。 我们在一些系统中都可以看到这样的设计,比如,我们的数据库访问...

  • 弹力设计之降级设计

    所谓的降级设计(Degradation),本质是为了解决资源不足和访问量过大的问题。当资源和访问量出现矛盾的时候,...

  • 分布式弹力设计之重试机制

    重试机制的使用场景 重试的前提是认为故障是暂时的,不是永久的,所以重试才有意义。 使用重试机制是必须要明确哪些错误...

  • 弹力设计之异步通讯设计

    前面所说的隔离设计通常都需要对系统做解耦设计,而把一个单体系统解耦,不单单是把业务功能拆分出来,正如上面所说,拆分...

  • 弹力设计-“隔离设计”

    隔离方式 服务的种类 用户 按服务种类分离 存在的问题 一个查询功能需要调用多个服务,降低性能(响应时间) 大数据...

  • (2)弹力设计篇之“隔离设计”

    概要:系统的分离有两种方式,以服务、以用户来做分离;隔离设计的重点: 一、按服务的种类来做分离 系统分成了用户、商...

  • (9)弹力设计篇之“限流设计”

    1、限流的策略 2、限流的算法:计数器、队列、漏斗和令牌桶。 3、如何基于响应时间来限流。 4、限流设计的要点 限...

  • 弹力设计之幂等性设计

    所谓幂等性设计,就是说,一次和多次请求某一个资源应该具有同样的副作用。用数学的语言来表达就是:f(x) = f(f...

网友评论

      本文标题:弹力设计之重试设计

      本文链接:https://www.haomeiwen.com/subject/ychggltx.html