ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Hyperf Retry 组件:面向分布式系统的可插拔重试策略深度指南

Hyperf Retry 组件:面向分布式系统的可插拔重试策略深度指南 后端微服务【免费下载链接】hyperf A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.项目地址https://gitcode.com/gh_mirrors/hy/hyperf点击查看免费下载本文基于 Hyperf 官方文档 docs/en/retry.md 并结合仓库源码编写。网络通信天然不稳定分布式系统必须做好容错设计而不加思考的盲目重试极为危险当通信出现问题时若每个请求都重试一次等同于系统 IO 负载翻倍极易诱发雪崩事故若被重试的接口不具备幂等性还可能造成数据不一致。Hyperf 的hyperf/retry组件通过策略组合的方式为不同业务场景提供了一套可插拔、可深度定制的重试机制。读完本文你将掌握如何使用#[Retry]注解快速为重试方法加上默认保护、如何通过组合策略打造专属重试注解、以及如何使用链式调用Fluent API对普通函数施加重试逻辑。安装通过 Composer 安装组件composer require hyperf/retry安装完成后组件会通过 ConfigProvider.php 自动注册注解、切面Aspect等相关配置无需额外手动注册即可使用。Hello World一行注解接入重试在需要重试的方法上添加#[Retry]注解即可/** * Retry the method on exception */ #[Retry] public function foo() { // make a remote call }默认的#[Retry]策略组合已经可以满足大多数日常重试需求最多尝试 10 次、出现Throwable即重试、带重试预算保护、失败后按策略休眠从而在重试能解决问题与过度重试引发雪崩之间取得平衡。从源码角度理解这条注解的生效过程Retry注解类继承自 AbstractRetry而AbstractRetry继承 Hyperf DI 组件的AbstractAnnotation通过collectMethod()将注解元数据收集到AnnotationCollector中随后 RetryAnnotationAspect 作为 AOP 切面拦截所有标注了AbstractRetry含其子类的方法将注解的policies属性逐一实例化并组装成 HybridRetryPolicy再驱动整个尝试 → 判断 → 重试循环。切面核心代码可见 RetryAnnotationAspect.php$annotation $this-getAnnotations($proceedingJoinPoint); $policy $this-makePolicy($annotation); $context $policy-start(); $context[proceedingJoinPoint] $proceedingJoinPoint; $context[pipe] $proceedingJoinPoint-pipe; if (! $policy-canRetry($context)) { goto end; } attempt: // Make an attempt to (re)try. $context[lastResult] $context[lastThrowable] null; try { $context[lastResult] $proceedingJoinPoint-process(); } catch (Throwable $throwable) { $context[lastThrowable] $throwable; } if ($policy-canRetry($context)) { $policy-beforeRetry($context); $proceedingJoinPoint-pipe $context[pipe]; goto attempt; } end: // Break out of retry $policy-end($context); if ($context[lastThrowable] ! null) { throw $context[lastThrowable]; } return $context[lastResult];其中makePolicy()会遍历注解中的$policies数组通过容器make($policy, $annotation-toArray())为每个策略注入注解属性最终以new HybridRetryPolicy(...$policies)组合成一个总策略——这正是本文后面策略组合思想的落地实现。策略组合把重试拆成可插拔的中间件本组件的核心设计是通过组合多个重试策略实现可插拔。每个策略只关注重试流程的一个方面重试判断是否应该重试、重试的触发条件是什么重试间隔两次尝试之间等待多久结果处理重试耗尽后的兜底行为。把策略想象成一摞中间件通过调整注解中使用的策略即可配置出适配任意场景的重试切面。HybridRetryPolicy正是这种中间件栈的编排者。从 HybridRetryPolicy.php 可以看到canRetry()要求所有策略都返回true才允许重试AND 语义即每个策略都拥有一票否决权start()将父上下文依次传入每个策略进行初始化reduce归约beforeRetry()依次执行每个策略的重试前准备如休眠、计数end()按顺序执行策略的收尾逻辑如兜底、熔断状态重置返回第一个返回true的策略结果。实战打造一个最多尝试 3 次的自定义注解官方建议根据具体业务需求构造自己的注解别名。下面演示如何制作一个最大尝试次数为 3 的新注解。提示在默认的#[Retry]注解中其实可以直接用#[Retry(maxAttempts3)]控制最大重试次数。这里为了演示策略组合的过程假设该参数不存在。首先创建一个注解类继承\Hyperf\Retry\Annotations\AbstractRetry?php declare(strict_types1); namespace App\Annotation; use Doctrine\Common\Annotations\Annotation\Target; #[Attribute(Attribute::TARGET_METHOD)] class MyRetry extends \Hyperf\Retry\Annotation\AbstractRetry { }注意文档原文中该继承写法为\Hyperf\Retry\Annotation\AbstractRetry实际源码中类的命名空间是Hyperf\Retry\Annotation见 AbstractRetry.php请以源码中的Hyperf\Retry\Annotation\AbstractRetry为准。同时建议为注解类补充use Hyperf\Retry\Annotation\AbstractRetry;等use语句以便阅读。接下来按需覆盖$policies属性。要限制重试次数使用MaxAttemptsRetryPolicy它还需要一个参数$maxAttempts作为最大尝试次数上限。把这两个属性加入上面的类?php declare(strict_types1); namespace App\Annotation; use Hyperf\Retry\Annotation\AbstractRetry; use Hyperf\Retry\Policy\MaxAttemptsRetryPolicy; #[Attribute(Attribute::TARGET_METHOD)] class MyRetry extends AbstractRetry { public $policies [ MaxAttemptsRetryPolicy::class, ]; public $maxAttempts 3; }这样#[MyRetry]注解会让任何方法最多被循环执行三次。但仅仅如此还不够还需要一个ClassifierRetryPolicy来规定什么样的错误可以被重试。加上它之后默认只在抛出Throwable时才重试?php declare(strict_types1); namespace App\Annotation; use Hyperf\Retry\Annotation\AbstractRetry; use Hyperf\Retry\Policy\ClassifierRetryPolicy; use Hyperf\Retry\Policy\MaxAttemptsRetryPolicy; #[Attribute(Attribute::TARGET_METHOD)] class MyRetry extends AbstractRetry { public $policies [ MaxAttemptsRetryPolicy::class, ClassifierRetryPolicy::class, ]; public $maxAttempts 3; }继续细化注解直到满足定制需求。例如只重试自定义的TimeoutException并使用至少 100ms 的变长间隔进行重试?php declare(strict_types1); namespace App\Annotation; use Hyperf\Retry\Annotation\Retry; use Hyperf\Retry\BackoffStrategy; use Hyperf\Retry\Policy\ClassifierRetryPolicy; use Hyperf\Retry\Policy\MaxAttemptsRetryPolicy; use Hyperf\Retry\Policy\SleepRetryPolicy; #[Attribute(Attribute::TARGET_METHOD)] class MyRetry extends Retry { public $policies [ MaxAttemptsRetryPolicy::class, ClassifierRetryPolicy::class, SleepRetryPolicy::class, ]; public $maxAttempts 3; public $base 100; public $strategy BackoffStrategy::class; public $retryThrowables [\App\Exception\TimeoutException::class]; }注文档原文中策略属性写作$strategy而源码 Retry.php 中该属性名实际为$sleepStrategyClass类型为string默认SleepStrategyInterface::class。两种写法在 Hyperf 注解属性注入机制下最终都会传给SleepRetryPolicy构造器但从源码一致性考虑推荐使用$sleepStrategyClass。SleepRetryPolicy的构造函数签名即为__construct(private int $base, private string $sleepStrategyClass)见 SleepRetryPolicy.php。只要保证该文件被 Hyperf 扫描到就可以在方法上使用#[MyRetry]注解来重试超时错误#[MyRetry] public function callRemote() { // 可能抛出 TimeoutException 的远程调用 }默认配置#[Retry]完整默认属性#[Retry]注解的完整默认属性如下与 Retry.php 中构造函数定义一致/** * Array of retry policies. Think of these as stacked middlewares. * var string[] */ public $policies [ FallbackRetryPolicy::class, ClassifierRetryPolicy::class, BudgetRetryPolicy::class, MaxAttemptsRetryPolicy::class, SleepRetryPolicy::class, ]; /** * The algorithm for retry intervals. */ public string $sleepStrategyClass SleepStrategyInterface::class; /** * Max Attampts. */ public int $maxAttempts 10; /** * 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|RetryBudgetInterface */ public $retryBudget [ ttl 10, minRetriesPerSec 1, 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. */ public int $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 arraystring|\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 arraystring|\Throwable */ public $ignoreThrowables []; /** * The fallback callable when all attempts exhausted. * * var callable|string */ public $fallback ;注意Retry的构造函数在最后还会执行$this-retryBudget make(RetryBudget::class, $this-retryBudget);将数组形式的预算配置实例化为RetryBudget对象。这意味着retryBudget既可以直接传数组也可以传入一个实现RetryBudgetInterface的对象。默认策略栈的执行顺序值得推敲FallbackRetryPolicy在最前负责收尾兜底、ClassifierRetryPolicy判定错误是否可重试、BudgetRetryPolicy控制重试预算、MaxAttemptsRetryPolicy限制最大尝试次数、SleepRetryPolicy负责尝试间隔。这五层策略叠加正好覆盖了能否重试、该不该重试、重试多少次、等多久、重试完怎么办的完整闭环。可选策略详解最大尝试次数策略MaxAttemptsRetryPolicy参数类型说明maxAttemptsint最大尝试次数源码实现MaxAttemptsRetryPolicy.php非常直观start()时将attempt初始化为 1每次beforeRetry()将attempt加一canRetry()判断attempt maxAttempts才允许继续否则将retryContext[retryExhausted] true并停止重试。这个retryExhausted标记正是后面FallbackRetryPolicy判断是否需要兜底的依据。错误分类策略ClassifierRetryPolicy通过分类器判断错误是否可重试。参数类型说明ignoreThrowablesarray要忽略的Throwable类名优先级高于retryThrowablesretryThrowablesarray要重试的Throwable类名优先级高于retryOnThrowablePredicateretryOnThrowablePredicatecallable传入函数判断Throwable是否可重试。返回 true 则可重试否则不可retryOnResultPredicatecallable用函数判断返回值是否可重试。返回 true 则可重试否则不可从源码看ClassifierRetryPolicy.php该策略在非首次尝试时若存在lastThrowable则调用isRetriable()判断若没有异常则只有当retryOnResultPredicate可调用且lastResult不为空时才会用谓词函数判断返回值。isRetriable()的判定顺序严格为先看ignoreThrowables命中即不可重试→ 再看retryThrowables命中即可重试→ 最后看retryOnThrowablePredicate谓词。换言之忽略列表的优先级最高。兜底策略FallbackRetryPolicy在重试资源耗尽后执行备用方法。参数类型说明fallbackcallable兜底方法fallback除了填写is_callable能识别的代码闭包、函数名字符串等之外还可以填写classmethod格式的字符串框架会从Container中取出对应的class实例然后执行它的method方法。例如fallback App\Service\OrderServicefallbackOrder。源码细节FallbackRetryPolicy.php该策略的end()方法只在retryContext[retryExhausted]存在即重试已耗尽时触发若fallback为包含的字符串则通过ApplicationContext::getContainer()-get($class)解析出对象执行时会把原方法的参数列表追加到兜底函数参数前面并在末尾追加最后一个异常对象即兜底方法签名形如fallback(...$originalArgs, ?Throwable $lastThrowable)。兜底执行成功则把返回值写入lastResult兜底自身抛出的异常则记录为新的lastThrowable继续向上抛出。休眠策略SleepRetryPolicy提供两种重试间隔策略固定间隔FlatStrategy与变长间隔BackoffStrategy。参数类型说明baseint基础休眠时间毫秒strategystring任意实现了Hyperf\Retry\SleepStrategyInterface的类名如Hyperf\Retry\BackoffStrategyFlatStrategy固定间隔每次重试前都固定休眠base毫秒实现见 FlatStrategy.phpBackoffStrategy指数退避第一次重试前休眠base毫秒之后间隔逐次翻倍实现基于Hyperf\Support\Backoff实现见 BackoffStrategy.php。从源码看SleepRetryPolicy.php该策略在start()时通过容器以[base $this-base]实例化休眠策略对象存入上下文beforeRetry()时调用$retryContext[strategy]-sleep()完成实际休眠。它在canRetry()中恒定返回true即休眠策略本身不决定能否重试只负责重试前等待多久。超时策略TimeoutRetryPolicy当总执行时间超过设定值后退出重试会话。参数类型说明timeoutfloat超时时间秒适合对整体重试窗口有严格要求的场景一旦从首次尝试开始累计耗时超过timeout秒无论当前处于第几次尝试都立即停止重试并将最后一次异常抛出。熔断策略CircuitBreakerRetryPolicy重试失败后将重试会话直接标记为一段时间内的熔断状态期间不再进行任何尝试。参数类型说明circuitBreakerState.resetTimeoutfloat恢复所需时间秒结合 CircuitBreakerState.php 理解熔断状态对象会记录熔断开启的时间resetTimeout过后状态自动复位后续调用重新获得重试机会。仓库中还提供了配套的#[CircuitBreaker]注解见 CircuitBreaker.php并在 CircuitBreakerAnotationAspectTest.php 与 CircuitBreakerStateTest.php 中有对应测试用例覆盖。预算策略BudgetRetryPolicy每个#[Retry]注解都会生成一个对应的令牌桶Token Bucket。每当注解方法被调用时就往令牌桶中放入一个带过期时间ttl的令牌。若发生可重试的错误必须消费掉对应数量的令牌percentCanRetry才能重试否则不重试错误继续向下传递。例如percentCanRetry0.2时每次重试要消耗 5 个令牌。这样当对端宕机时最多只会产生约 20% 的额外重试消耗对大多数系统而言都是可接受的范围。为了照顾一些低频调用的方法每秒还会额外生成一定数量的最低保障令牌minRetriesPerSec以保证系统稳定性。参数类型说明retryBudget.ttlint恢复令牌的过期时间秒retryBudget.minRetriesPerSecint最低保障每秒最小重试次数retryBudget.percentCanRetryfloat重试次数不超过总请求量的百分比重要限制重试组件的令牌桶不在 worker 之间共享因此最终的重试次数需要乘以 worker 数量来估算。也就是说实际生效的全局重试预算 单 worker 预算 × 常驻进程数。这在评估高并发下的额外负载时务必计入。预置注解别名由于重试注解配置较为复杂组件预置了若干开箱即用的别名便于快速书写#[RetryThrowable]只重试Throwable与默认的#[Retry]等价。源码见 RetryThrowable.php它继承Retry并仅将retryThrowables默认为[Throwable::class]。#[RetryFalsy]只重试返回值为弱等于false$result false的错误不重试异常。源码见 RetryFalsy.php其测试见 RetryFalsyTest.php。#[BackoffRetryThrowable]#[RetryThrowable]的变长重试间隔版本重试间隔至少 100ms。源码见 BackoffRetryThrowable.php。#[BackoffRetryFalsy]#[RetryFalsy]的变长重试间隔版本重试间隔至少 100ms。源码见 BackoffRetryFalsy.php。文档原文此处对#[BackoffRetryFalsy]的描述存在排版笔误#[【]RetryFalsy]实际指的就是#[RetryFalsy]的退避版本与源码目录中的实现一致。链式调用对普通函数使用重试除了通过注解作用于方法外组件还支持对普通 PHP 函数使用重试。\Hyperf\Retry\Retry是一个静态入口所有静态调用都会委托给FluentRetry见 Retry.php 的__callStatic与 FluentRetry.php。基础写法——显式传入策略对象?php $result \Hyperf\Retry\Retry::with( new \Hyperf\Retry\Policy\ClassifierRetryPolicy(), // Retry all Throwables by default new \Hyperf\Retry\Policy\MaxAttemptsRetryPolicy(5) //Retry up to 5 times )-call(function(){ if (rand(1, 100) 20){ return true; } throw new Exception; });为提高可读性还可以使用更流畅的链式写法?php $result \Hyperf\Retry\Retry::whenReturns(false) // Retry when false is returned -max(3) // up to 3 times -inSeconds(5) // up to 5 seconds -sleep(1) // 1ms interval -fallback(function(){return true;}) // fallback function -call(function(){ if (rand(1, 100) 20){ return true; } return false; });FluentRetry提供的全部方法均可在 FluentRetry.php 中确认方法作用对应策略with(RetryPolicyInterface ...$policies)显式传入策略对象列表直接组装when(callable $when)用自定义谓词判断是否重试ExpressionRetryPolicywhenReturns($when)返回值为指定值时重试如falseClassifierRetryPolicywhenThrows(string $when Throwable)抛出指定异常时重试ClassifierRetryPolicymax(int $times)最大尝试次数MaxAttemptsRetryPolicyinSeconds(float $seconds)整体重试窗口超时秒TimeoutRetryPolicyfallback(callable $fallback)兜底回调FallbackRetryPolicysleep(int $base)固定间隔休眠毫秒SleepRetryPolicyFlatStrategybackoff(int $base)变长退避休眠毫秒SleepRetryPolicyBackoffStrategycall(callable $callable)执行被重试的闭包组装HybridRetryPolicy驱动循环需要注意的是call()要求至少指定一个策略否则会抛出BadMethodCallException提示 Please specify at least one policy before call。其内部执行逻辑与注解切面完全一致创建HybridRetryPolicy→start()→ 循环调用闭包、捕获异常/结果、canRetry()判断、beforeRetry()准备→end()收尾 → 若有残留异常则抛出否则返回lastResult。唯一区别是链式调用下若存在ProceedingJoinPoint类会构造一个由闭包包装的伪连接点以保持与切面体系的上下文兼容。测试与验证仓库在 src/retry/tests 下提供了完整的测试用例可作为理解组件行为的补充依据RetryAnnotationAspectTest.php验证注解切面的重试循环行为RetryFalsyTest.php验证#[RetryFalsy]对弱等false返回值的重试逻辑RetryBudgetTest.php验证令牌桶预算的生效与消耗规则CircuitBreakerAnotationAspectTest.php 与 CircuitBreakerStateTest.php验证熔断状态与复位行为RetryTest.php通用重试行为测试。小结如何为你的业务选型重试策略最后把整条决策链路串起来最小侵入直接在方法上使用#[Retry]享受默认的五层策略保护兜底 分类 预算 次数 休眠按错误类型定制通过retryThrowables/ignoreThrowables/ 谓词函数明确只有可恢复的错误才值得重试避免对不可恢复错误浪费资源按负载与时长控制用BudgetRetryPolicy限制额外重试流量注意 worker 不共享令牌桶、用MaxAttemptsRetryPolicy与TimeoutRetryPolicy约束尝试次数与总耗时按间隔策略适配抖动敏感的服务优先BackoffStrategy变长退避容忍固定间隔的场景用FlatStrategy结果处理用FallbackRetryPolicy支持classmethod的容器解析写法保证重试耗尽后仍能返回可控的降级结果进程级保护对持续失败的下游用CircuitBreakerRetryPolicy或#[CircuitBreaker]注解实现短时间熔断避免雪崩非注解场景对普通函数使用\Hyperf\Retry\Retry::whenReturns(...)-max(...)-call(...)链式调用。重试不是无脑再来一次而是有策略地再来一次。借助 Hyperf 这套可组合的策略体系你可以在任何场景下精确控制重试的触发条件、频率、上限与兜底行为让容错设计既安全又高效。赞分享后端微服务【免费下载链接】hyperf A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.项目地址https://gitcode.com/gh_mirrors/hy/hyperf点击查看免费下载相关推荐Hyperf Retry 组件完全指南基于注解与可插拔策略的高可用重试机制Hyperf Retry 组件完全指南基于注解与可插拔策略的高可用重试机制 重试是分布式系统中抵御网络抖动与瞬时故障的第一道防线但盲目重试反而会放大系统负载后端Web框架微服务RPC框架异步编程Hyperf Retry 组件实战注解式重试、策略组合与熔断防雪崩指南Hyperf Retry 组件实战注解式重试、策略组合与熔断防雪崩指南 导读 Hyperf 是高性能的协程框架而网络通信天然不稳定微服务场景下调用失败在所后端微服务WinScript隐私保护详解阻止Windows数据收集的10个实用技巧WinScript隐私保护详解阻止Windows数据收集的10个实用技巧 WinScript是一款开源工具可帮助用户从零开始构建Windows 10/11脚上一篇Boto3 实战使用 AWS KMS 信封加密Envelope Encryption加密与解密文件下一篇手机号逆向查询QQ号3分钟快速上手完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表