hyperf/doc/zh/retry.md

6.4 KiB
Raw Blame History

令牌桶重试器

网络通讯天然是不稳定的因此在分布式系统中需要有良好的容错设计。失败重试人人都会但不是人人都能把重试做好。无差别重试是非常危险的。当通讯出现问题时每个请求都重试一次相当于系统负载增加了100%,容易诱发雪崩事故。重试还要考虑错误的原因,如果是无法通过重试解决的问题,那么重试只是浪费资源而已。除此之外,如果重试的接口不具备幂等性,还可能造成数据不一致等问题。

本组件受到Finagle启发,提供了令牌桶重试机制,避免雪崩事故发生,同时提供了丰富的重试过滤规则。

安装

composer require hyperf/retry

Hello World

在需要重试的方法上加入注解 @Retry

/**
 * 异常时重试该方法
 * @Retry
 */
public function foo()
{
    // 发起一次远程调用
}

原理

每一个 @Retry 注解处会生成一个对应的令牌桶,每当注解方法被调用时,就在令牌桶中放入一个具有过期时间(ttl)的令牌。如果发生可重试的错误,重试前要消耗掉对应的令牌数量(percentCanRetry)否则就不会重试错误继续向下传递。比如当percentCanRetry=0.2则每次重试要消耗5个令牌。如此遇到对端宕机时最多只会造成20%的额外重试消耗,对于大多数系统都应该可以接受了。

为了照顾某些使用频率较低的方法,每秒还会生成一定数量的“低保”令牌(minRetriesPerSec),确保系统稳定。

注解配置

Retry的完整注解默认值如下

class Retry extends AbstractAnnotation
{
    /**
     * The algorithm for retry intervals.
     * @var string
     */
    public $strategy = StrategyInterface::class;

    /**
     * Max Attampts.
     * @var float|int
     */
    public $maxAttempts = INF;

    /**
     * Retry Budget. 
     * ttl: Seconds of token lifetime.
     * minRetriesPerSec: Base retry token generation speed.
     * percentCanRetry: Generate new token at this ratio of the request volume.
     * 
     * @var array
     */
    public $retryBudget = [
        'ttl' => 10,
        'minRetriesPerSec' => 10,
        'percentCanRetry' => 0.2,
    ];

    /**
     * Base time inteval (ms) for each try. For backoff strategy this is the interval for the first try
     * while for flat strategy this is the interval for every try.
     * @var int
     */
    public $base = 0;

    /**
     * Configures a Predicate which evaluates if an exception should be retried.
     * The Predicate must return true if the exception should be retried, otherwise it must return false.
     *
     * @var callable|string
     */
    public $retryOnThrowablePredicate = '';

    /**
     * Configures a Predicate which evaluates if an result should be retried.
     * The Predicate must return true if the result should be retried, otherwise it must return false.
     *
     * @var callable|string
     */
    public $retryOnResultPredicate = '';

    /**
     * Configures a list of Throwable classes that are recorded as a failure and thus are retried.
     * Any Throwable matching or inheriting from one of the list will be retried, unless ignored via ignoreExceptions.
     *
     * Ignoring an Throwable has priority over retrying an exception.
     *
     * @var array<string|\Throwable>
     */
    public $retryThrowables = [\Throwable::class];

    /**
     * Configures a list of error classes that are ignored and thus are not retried.
     * Any exception matching or inheriting from one of the list will not be retried, even if marked via retryExceptions.
     *
     * @var array<string|\Throwable>
     */
    public $ignoreThrowables = [];

    public function collectMethod(string $className, ?string $target): void
    {
        AnnotationCollector::collectMethod($className, $target, self::class, $this);
    }
}

strategy

提供两种重试间歇策略。等长重试间歇FlatStrategy和变长重试间歇BackoffStrategy。默认为等长重试间歇。

retryBudget

public $retryBudget = [
    'ttl' => 10,
    'minRetriesPerSec' => 10,
    'percentCanRetry' => 0.2,
];
  • ttl 令牌过期时间。
  • minRetriesPerSec 每秒“低保”最少可以重试的次数。
  • percentCanRetry 重试次数不超过总请求数的百分比。

重试组件的令牌桶在worker之间不共享所以最终的重试次数要乘以worker数量。

base

等长重试间歇中的间歇时间,变长重试间歇中的第一次重试间歇时间。

maxAttempts

类型float|int。单次请求最大重试次数。

ignoreThrowables

类型array<string|\Throwable>。无视的 Throwable 。优先于 retryThrowables

retryThrowables

类型array<string|\Throwable>。需要重试的 Throwable 。优先于 retryOnThrowablePredicate

retryOnThrowablePredicate

类型callable|string。通过一个函数来判断 Throwable 是否可以重试。如果可以重试请返回true,反之必须返回false。

retryOnResultPredicate

类型callable|string。 通过一个函数来判断返回值是否可以重试。如果可以重试请返回true,反之必须返回false。

注解别名

因为重试注解配置较为复杂,这里提供了一些预设的别名便于书写。

  • @RetryThrowable 只重试 Throwable。和默认的Retry相同。

  • @RetryFalsy 只重试返回值弱等于false$result == false)的错误,不重试异常。

  • @BackoffRetryThrowable @RetryThrowable的变长重试间歇版本初次重试间歇100毫秒。

  • @BackoffRetryFalsy @RetryFalsy的变长重试间歇版本初次重试间歇100毫秒。

建议根据具体业务需要构造自己的注解别名。例如,配置只重试用户自定义的 TimeoutException , 并使用变长间歇, 方法如下:

<?php

declare(strict_types=1);

namespace App\Annotation;

use Doctrine\Common\Annotations\Annotation\Target;

/**
 * @Annotation
 * @Target({"METHOD"})
 */
class RetryTimeout extends Hyperf\Retry\Annotation\Retry
{
    public $base = 100;
    public $strategy = \Hyperf\Retry\BackoffStrategy::class;
    public $retryThrowables = [\App\Exception\TimeoutException::class];
}

只要确保该文件被Hyperf扫描就可以在方法中使用 @RetryTimeout 注解来重试超时错误了。