ARTICLE DETAIL

资讯详情

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

Hyperf Retry 组件全解析:注解驱动的容错重试机制与自定义策略实战

Hyperf Retry 组件全解析:注解驱动的容错重试机制与自定义策略实战 后端Web框架微服务RPC框架异步编程【免费下载链接】hyperf A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.项目地址https://gitcode.com/hyperf/hyperf点击查看免费下载导读在分布式系统与微服务架构中网络通信本质上是不稳定的任何一个远端调用都可能因超时、抖动或下游故障而失败。Hyperf 的hyperf/retry组件提供了一套基于注解Annotation与策略Policy组合的可插拔重试机制覆盖重试判定、重试间隔、结果处理、熔断与预算控制等完整环节。读完本文你将掌握#[Retry]注解的完整配置项、如何按业务场景定制属于自己的重试注解以及如何使用链式 API 在普通 PHP 代码中嵌入重试逻辑从而在不引发雪崩的前提下优雅地提升系统容错能力。为什么需要「克制」的重试重试不是简单地把失败的请求再打一遍无脑重试反而会放大故障。文档中明确指出几个关键风险放大系统负载当通信出现问题时如果每个请求都重试一次等价于系统 IO 负载提升 100%极易触发雪崩avalanche。重试无意义如果错误本身无法通过重试解决重试只是在浪费资源。破坏一致性如果被重试的接口不具备幂等性重复执行可能造成数据不一致等问题。因此一个合格的重试组件必须同时回答三个问题什么情况下该重试、重试多少次、两次重试之间间隔多久并且需要提供预算Budget与熔断Circuit Breaker等保护机制防止重试本身成为新的故障源。这正是hyperf/retry的设计出发点——它通过组合多个职责单一的重试策略来覆盖上述所有维度。安装在 Hyperf 项目中通过 Composer 安装composer require hyperf/retry安装完成后组件会通过 ConfigProvider.php 自动完成注解扫描与 AOP 切面Aspect注册无需额外手动配置即可使用。Hello World一行注解开启重试在需要重试的方法上添加#[Retry]注解即可/** * 发生异常时重试该方法 */ #[Retry] public function foo() { // 发起远程调用 }默认的Retry策略组合已经能够满足大多数日常重试需求并且由于内置了预算控制BudgetRetryPolicy不会因为过度重试而引发雪崩。从源码看#[Retry]是一个#[Attribute(Attribute::TARGET_METHOD)]属性注解Retry.php其实际生效依赖 Hyperf 的 AOP 机制切面 RetryAnnotationAspect.php 监听所有继承自AbstractRetry的注解在方法执行时根据注解配置构造策略并驱动重试循环。深度定制创建你自己的重试注解组件通过组合多个重试策略实现可插拔性。每个策略专注于重试流程的一个方面——重试判定、重试间隔、结果处理等。通过调整注解中使用的策略列表你可以为任何场景定制重试行为。官方强烈建议根据具体业务需求构建自己的别名注解alias annotation。下面以「最大尝试次数为 3 次」为例演示如何创建新注解。注默认的Retry注解本身就能通过#[Retry(maxAttempts3)]控制最大重试次数此处仅出于演示目的假设它不存在。第一步继承 AbstractRetry创建一个新的注解类并继承\Hyperf\Retry\Annotation\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 { }从源码结构看AbstractRetry.php 本身继承自Hyperf\Di\Annotation\AbstractAnnotation并实现了collectMethod()将注解元数据收集到AnnotationCollector中——这是 Hyperf 注解生效的基础。其$policies属性默认是空数组因此你的自定义注解需要自己声明策略组合。第二步用 MaxAttemptsRetryPolicy 限制次数按需覆写$policies属性。要限制重试次数需要加入MaxAttemptsRetryPolicy它需要一个参数——最大尝试次数$maxAttempts?php declare(strict_types1); namespace App\Annotation; use Doctrine\Common\Annotations\Annotation\Target; #[Attribute(Attribute::TARGET_METHOD)] class MyRetry extends \Hyperf\Retry\Annotation\AbstractRetry { public $policies [ MaxAttemptsRetryPolicy::class, ]; public $maxAttempts 3; }此时#[MyRetry]会让任意方法最多循环执行 3 次。第三步用 ClassifierRetryPolicy 控制重试对象还需要加入ClassifierRetryPolicy来指定哪些错误可以被重试。加入后默认它只会在抛出Throwable时进行重试?php declare(strict_types1); namespace App\Annotation; use Doctrine\Common\Annotations\Annotation\Target; #[Attribute(Attribute::TARGET_METHOD)] class MyRetry extends \Hyperf\Retry\Annotation\AbstractRetry { public $policies [ MaxAttemptsRetryPolicy::class, ClassifierRetryPolicy::class, ]; public $maxAttempts 3; }第四步组合更多策略定制重试细节你可以持续打磨这个注解直到满足你的定制需求。例如只重试自定义的TimeoutException并使用可变间隔——每次重试至少休眠 100 毫秒?php declare(strict_types1); namespace App\Annotation; use Doctrine\Common\Annotations\Annotation\Target; #[Attribute(Attribute::TARGET_METHOD)] class MyRetry extends \Hyperf\Retry\Annotation\Retry { public $policies [ MaxAttemptsRetryPolicy::class, ClassifierRetryPolicy::class, SleepRetryPolicy::class, ]; public $maxAttempts 3; public $base 100; public $strategy \Hyperf\Retry\BackoffStrategy::class; public $retryThrowables [\App\Exception\TimeoutException::class]; }只要该文件被 Hyperf 扫描到你就可以在方法内使用#[MyRetry]注解来重试超时错误了。需要注意$policies是「堆叠的中间件」式的策略数组。切面 RetryAnnotationAspect.php 会遍历注解的policies通过make($policy, $annotation-toArray())将注解上的其他属性如$maxAttempts、$base等注入到每个策略构造器中再组装成 HybridRetryPolicy。因此注解属性名需要与各策略构造器的参数名保持一致才能正确注入。默认配置全览#[Retry]注解的完整默认属性如下/** * 重试策略数组可将其视为堆叠的中间件。 * var string[] */ public $policies [ FallbackRetryPolicy::class, ClassifierRetryPolicy::class, BudgetRetryPolicy::class, MaxAttemptsRetryPolicy::class, SleepRetryPolicy::class, ]; /** * 重试间隔算法。 */ public string $sleepStrategyClass SleepStrategyInterface::class; /** * 最大尝试次数。 */ public int $maxAttempts 10; /** * Retry Budget。 * ttl: token 有效期秒。 * minRetriesPerSec: retry token 的基础生成速率。 * percentCanRetry: 以请求量的这个比例生成新 token。 * * var array|RetryBudgetInterface */ public $retryBudget [ ttl 10, minRetriesPerSec 1, percentCanRetry 0.2, ]; /** * 每次尝试的基础时间间隔毫秒。对于 backoff 策略它是第一次尝试的间隔 * 对于 flat 策略它是每次尝试的间隔。 */ public int $base 0; /** * 配置一个 Predicate用于评估某个异常是否应该重试。 * 若该异常应该重试Predicate 必须返回 true否则返回 false。 * * var callable|string */ public $retryOnThrowablePredicate ; /** * 配置一个 Predicate用于评估某个结果是否应该重试。 * 若该结果应该重试Predicate 必须返回 true否则返回 false。 * * var callable|string */ public $retryOnResultPredicate ; /** * 配置被记录为失败、因而需要重试的 Throwable 类列表。 * 任何匹配或继承自列表中某个类的 Throwable 都会被重试 * 除非被 ignoreThrowables 忽略。忽略的优先级高于重试。 * * var arraystring|\Throwable */ public $retryThrowables [\Throwable::class]; /** * 配置被忽略、因而不重试的错误类列表。 * 任何匹配或继承自列表中某个类的异常都不会被重试 * 即使它被标记在 retryThrowables 中。 * * var arraystring|\Throwable */ public $ignoreThrowables []; /** * 所有尝试都耗尽时的 fallback 回调。 * * var callable|string */ public $fallback ;这些属性与 Retry.php 中构造函数参数一一对应。需要特别说明的一点构造时$retryBudget数组会被make(RetryBudget::class, $this-retryBudget)实例化为真正的 RetryBudget 对象因此直接传数组即可组件内部会自动完成装配。可选策略详解MaxAttemptsRetryPolicy最大尝试次数参数类型说明maxAttemptsint最大尝试次数ClassifierRetryPolicy错误分类器通过分类器判断某个错误是否可以重试。参数类型说明ignoreThrowablesarray被忽略的Throwable类名。优先级高于retryThrowablesretryThrowablesarray需要重试的Throwable类名。优先级高于retryOnThrowablePredicateretryOnThrowablePredicatecallable通过一个函数判断Throwable是否可重试。可重试返回 true否则返回 falseretryOnResultPredicatecallable通过一个函数判断返回值是否可重试。可重试返回 true否则返回 false从 ClassifierRetryPolicy.php 的实现可以印证其判定逻辑先检查ignoreThrowables是否命中命中即不重试再检查retryThrowables是否命中命中即重试最后才回退到retryOnThrowablePredicate回调而结果判定retryOnResultPredicate只在没有抛出异常且结果为非 null 时生效。注意其中对 Throwable 的匹配使用的是instanceof语义因此子类异常同样会被匹配。FallbackRetryPolicy兜底策略重试资源耗尽后执行备选方法。参数类型说明fallbackcallable兜底方法除了能被is_callable识别的代码形式外fallback还可以填写classmethod格式。框架会从Container中获取对应的class实例然后执行其method方法——这非常适合注入依赖的 Service 方法作为兜底处理。SleepRetryPolicy休眠策略提供两种重试间隔策略固定间隔FlatStrategy与可变间隔BackoffStrategy。参数类型说明baseint基础休眠时间毫秒strategystring任何实现了Hyperf\Retry\SleepStrategyInterface的类名如Hyperf\Retry\BackoffStrategy对应的具体实现为 FlatStrategy.php 与 BackoffStrategy.php。文档中「#[Retry]默认的sleepStrategyClass是SleepStrategyInterface::class」其实是一种空实现表示默认不做额外休眠真正要启用间隔控制时应显式指定FlatStrategy或BackoffStrategy。TimeoutRetryPolicy超时策略当总执行时间超过指定时间后退出重试会话。参数类型说明timeoutfloat超时时间秒CircuitBreakerRetryPolicy熔断策略当重试失败并退出重试会话后在一段时间内直接被标记为熔断状态不再进行任何尝试。参数类型说明circuitBreakerState.resetTimeoutfloat恢复所需时间秒熔断状态由 CircuitBreakerState.php 维护。仓库中还提供了独立的#[CircuitBreaker]注解CircuitBreaker.php并有对应的测试 CircuitBreakerAnotationAspectTest.php 与 CircuitBreakerStateTest.php 验证其行为。BudgetRetryPolicy预算策略每个#[Retry]注解会生成一个对应的令牌桶token bucket。每次调用被注解的方法时会向桶中放入一个带有过期时间ttl的 token。当发生可重试的错误时需要消耗一定数量由percentCanRetry决定的 token 才会真正重试若 token 不足则不再重试错误继续向下传递。例如percentCanRetry0.2时每次重试消耗 5 个 token。这样一来当某个对端崩溃时最多只会带来 20% 的额外重试消耗对大多数系统而言是可接受的。参数类型说明retryBudget.ttlinttoken 过期时间秒retryBudget.minRetriesPerSecint每秒保证的最小重试次数retryBudget.percentCanRetryfloat重试次数不超过总请求量的比例注意retry 组件的 token bucket不会在多个 worker 之间共享因此实际的重试总量需要乘以 worker 数量来估算。RetryBudgetTest.php 中包含了预算消耗与补充逻辑的单元测试可供参考。内置别名注解由于重试注解的配置相对复杂组件内置了几个预设别名注解方便快速书写#[RetryThrowable]只重试Throwable与默认的#[Retry]等价。#[RetryFalsy]只重试「返回值松散等于 false$result false」的错误不重试异常。其实现见 RetryFalsy.php内部通过静态方法isFalsy()作为retryOnResultPredicate完成判定。#[BackoffRetryThrowable]#[RetryThrowable]的可变间隔版本重试间隔至少 100 毫秒。#[BackoffRetryFalsy]#[RetryFalsy]的可变间隔版本重试间隔至少 100 毫秒。以 BackoffRetryThrowable.php 为例它继承自RetryThrowable默认设置base 100与sleepStrategyClass BackoffStrategy::class——这正是「间隔至少 100 毫秒、指数退避」的由来。测试文件 RetryFalsyTest.php 与 RetryTest.php 覆盖了这些别名注解的核心行为。Fluent 链式调用除了注解方式你还可以通过普通 PHP 函数调用方式使用该组件?php $result \Hyperf\Retry\Retry::with( new \Hyperf\Retry\Policy\ClassifierRetryPolicy(), // 默认重试所有 Throwable new \Hyperf\Retry\Policy\MaxAttemptsRetryPolicy(5) // 最多重试 5 次 )-call(function(){ if (rand(1, 100) 20){ return true; } throw new Exception; });为了提升可读性还可以使用如下 fluent 语法?php $result \Hyperf\Retry\Retry::whenReturns(false) // 返回 false 时重试 -max(3) // 最多 3 次 -inSeconds(5) // 最多 5 秒 -sleep(1) // 间隔 1 毫秒 -fallback(function(){return true;}) // 兜底函数 -call(function(){ if (rand(1, 100) 20){ return true; } return false; });从实现上看Retry.php 通过__callStatic将静态调用委托给由容器创建make(FluentRetry::class)的 FluentRetry.php 实例。FluentRetry内部把每一步链式方法翻译成对应的策略对象例如whenReturns(false)→ 构造ClassifierRetryPolicy([], [], null, fn ($r) $r $when)max(3)→MaxAttemptsRetryPolicy(3)inSeconds(5)→TimeoutRetryPolicy(5)sleep(1)→SleepRetryPolicy(1, FlatStrategy::class)固定间隔backoff(100)→SleepRetryPolicy(100, BackoffStrategy::class)可变间隔。最终所有策略被组装为HybridRetryPolicy并以与注解切面相同的方式驱动canRetry → attempt → beforeRetry → end的重试循环FluentRetry.php。若未指定任何策略就调用call()会抛出BadMethodCallException提示至少先声明一个策略。重试循环的底层机制无论是注解方式还是链式方式重试的核心循环都在策略层面统一实现注解路径见 RetryAnnotationAspect.phpstart()初始化RetryContext各策略按顺序在此注入初始状态如预算桶、熔断状态、开始时间等。canRetry()判定当前是否还能继续尝试。HybridRetryPolicy 会同时询问所有策略只有全部返回 true 才继续重试——任何一个策略如超时、预算耗尽、次数用尽说「不」重试即终止。执行目标方法注解方式为$proceedingJoinPoint-process()捕获结果或异常写入RetryContext。canRetry()再次判定若可重试则调用beforeRetry()各策略在此执行休眠、扣减预算等动作然后回到第 3 步。循环退出后调用end()若有遗留异常则重新抛出否则返回最后一次执行结果。RetryContext是贯穿整个重试会话的载体承载lastResult、lastThrowable、proceedingJoinPoint等关键状态RetryContext.php。理解了这一循环就能明白为什么policies数组的顺序会影响行为例如SleepRetryPolicy是否在MaxAttemptsRetryPolicy之前决定了「次数耗尽」与「最后一段休眠」之间的先后关系实践中通常保持默认顺序即可。总结hyperf/retry以「策略组合 注解/链式双入口」的方式把重试这一高风险的容错手段变得可控、可配、可插拔通过#[Retry]一行注解即可获得带预算保护的安全重试通过继承AbstractRetry组合MaxAttemptsRetryPolicy、ClassifierRetryPolicy、SleepRetryPolicy等策略可以精确刻画「重试什么、重试几次、间隔多久」BudgetRetryPolicy与CircuitBreakerRetryPolicy提供了雪崩防护与熔断兜底别名注解与Retry::whenReturns(...)-max(...)-call(...)链式 API 则让高频场景的书写成本降到最低。在把任何远程调用、数据库写入或第三方接口调用接入重试之前请先确认接口的幂等性并结合业务对延迟与成功率的容忍度选择合适的策略组合与预算参数。赞分享后端Web框架微服务RPC框架异步编程【免费下载链接】hyperf A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.项目地址https://gitcode.com/hyperf/hyperf点击查看免费下载相关推荐Hyperf Retry 组件实战注解式重试、策略组合与熔断防雪崩指南Hyperf Retry 组件实战注解式重试、策略组合与熔断防雪崩指南 导读 Hyperf 是高性能的协程框架而网络通信天然不稳定微服务场景下调用失败在所后端微服务Hyperf Retry 组件完全指南基于注解与可插拔策略的高可用重试机制Hyperf Retry 组件完全指南基于注解与可插拔策略的高可用重试机制 重试是分布式系统中抵御网络抖动与瞬时故障的第一道防线但盲目重试反而会放大系统负载后端Web框架微服务RPC框架异步编程Hyperf 熔断器Circuit Breaker组件实战指南注解驱动、状态机与降级策略Hyperf 熔断器Circuit Breaker组件实战指南注解驱动、状态机与降级策略 导读 在微服务与分布式系统中一个基础服务不可用往往会导致调用链后端Web框架微服务RPC框架异步编程上一篇StreamCap终极指南免费开源的多平台直播录制神器下一篇终极解决指南PCL2启动器游戏启动失败的3个核心原因与高效修复方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表