同步、异步、失败重试路由配置
解读
国内高并发业务(电商秒杀、支付回调、消息通知)普遍要求接口“三高一低”:高可用、高可靠、高吞吐、低成本。
面试官问“同步、异步、失败重试路由配置”,并不是让你背 Laravel 文档,而是考察四层能力:
- 能否把“同步/异步”放到全链路视角(网关→FPM→队列→消费)去设计;
- 能否用 PHP 生态(Laravel/Symfony + Composer 包)给出可落地的代码级配置;
- 能否针对失败场景(超时、500、幂等、雪崩)设计分级重试路由;
- 能否结合国内基础设施(阿里云 MNS、腾讯云 CMQ、RocketMQ、Swoole 协程)做性能与成本权衡。
一句话:让你“画架构、写配置、讲策略、算成本”。
知识点
- 同步路由:Nginx+FastCGI 直接命中 FPM,OPcache 预热,接口耗时 < 200 ms,适合读多写少、强一致场景。
- 异步路由:Nginx 只收请求→写入队列(Redis Stream、RocketMQ、Kafka)→返回 202 Accepted,耗时 < 50 ms,适合写流量突发、可最终一致场景。
- 失败重试路由:
① 本地重试:Guzzle 中间件 + 指数退避(0.1s、0.4s、1.6s),最多 3 次,防止惊群。
② 延迟队列重试:Laravel Queue 的 retryAfter + failed_jobs 表,按异常类型路由到不同队列(sms.failed、order.failed)。
③ 死信队列(DLX):RocketMQ 最大重试 16 次,间隔指数增长,第 17 次写入 DLX,人工工单兜底。 - 幂等键:国内订单号、商户号、用户 ID 拼接,Redis SET NX 过期 24 h,防止重复发货。
- 路由配置位置:
- 代码层:app/Http/Routes/api.php 里用 Route::middleware(['sync', 'throttle:60,1']) 区分;
- 中间件层:SyncMiddleware 在请求头注入 X-Request-Type: sync,异步注入 X-Request-Type: async;
- 队列层:config/queue.php 里为每个业务单独定义 connection,失败重试次数、超时、sleep 都独立;
- 基础设施层:K8s Ingress 用 nginx.ingress.kubernetes.io/upstream-hash-by 把同步接口打到固定 Pod,异步接口打到弹性 HPA 队列工作池。
- 可观测:阿里云 SLS 日志里同步链路打印 traceId,异步链路打印 msgId,方便故障时一键串联。
答案
下面给出一套可直接写进简历的“最小可运行+可扩展”方案,基于 Laravel 10 + Redis Stream + Guzzle 7,运行在 4C8G 的阿里云 ECS,CentOS 8,PHP 8.2,OPcache 开启,JIT 开启。
- 同步路由配置
routes/api.php
Route::middleware(['sync', 'throttle:order'])->prefix('order')->group(function () {
Route::post('/create', [OrderController::class, 'createSync']);
});
app/Http/Middleware/SyncMiddleware.php
public function handle($request, Closure $next)
{
// 1. 幂等校验
$idempotentKey = $request->header('X-Idempotent-Key');
if (! $idempotentKey || Redis::exists('idem:'.$idempotentKey)) {
return response()->json(['code'=>409,'msg'=>'duplicate request'], 409);
}
Redis::setex('idem:'.$idempotentKey, 86400, 1);
// 2. 超时保护
return $next($request);
}
控制器里用 DB::transaction 保证本地事务,耗时 80 ms 以内返回 200。
- 异步路由配置
routes/api.php
Route::middleware(['async'])->prefix('order')->group(function () {
Route::post('/create/async', [OrderController::class, 'createAsync']);
});
控制器只做两件事:
public function createAsync(Request $request)
{
$payload = $request->all();
$uuid = Str::uuid();
Redis::xAdd('stream:order', '*', ['payload' => json_encode($payload), 'uuid' => $uuid]);
return response()->json(['code'=>202,'msg'=>'accepted','uuid'=>$uuid], 202);
}
队列消费进程用 supervisor 守护,app/Console/Commands/ConsumeOrder.php
public function handle()
{
Redis::xReadGroup('orderGroup', 'consumer1', ['stream:order' => '>'], 1, 2000)
->each(function ($msg) {
try {
$data = json_decode($msg['message']['payload'], true);
(new OrderService)->create($data);
Redis::xAck('stream:order', 'orderGroup', $msg['id']);
} catch (\Exception $e) {
// 失败重试路由
$retry = $msg['message']['retry'] ?? 0;
if ($retry < 3) {
Redis::xAdd('stream:order.retry', '*', [
'payload' => $msg['message']['payload'],
'retry' => $retry + 1,
'due' => time() + (2 ** $retry) * 10 // 10s、20s、40s
]);
} else {
Redis::xAdd('stream:order.dlx', '*', $msg['message']);
}
}
});
}
supervisor 给同步队列配 8 进程,异步重试队列配 2 进程,DLX 配 1 进程,防止互相抢 CPU。
- 失败重试路由配置
config/queue.php
'redis' => [
'driver' => 'redis',
'connection' => 'queue',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'max_tries' => 3,
'backoff' => [1, 5, 10], // 秒
],
failed_jobs 表加索引:
ALTER TABLE failed_jobs ADD INDEX idx_queue_failed_at (queue, failed_at);
Guzzle 调用外部支付网关时:
$handler = HandlerStack::create();
$handler->push(Middleware::retry(
function ($retries, $request, $response, $exception) {
if ($retries >= 3) return false;
if ($exception instanceof ConnectException) return true;
if ($response && $response->getStatusCode() >= 500) return true;
return false;
},
function ($retries) {
return (int) pow(2, $retries) * 100; // 100ms、400ms、1600ms
}
));
$client = new Client(['handler' => $handler, 'timeout' => 2]);
-
灰度与回滚
在 Apollo 配置中心加开关 order.async.enable=1,同步异步一键切换;灰度 5% 流量,错误率 > 1% 自动回滚。 -
压测结果
4C8G 单机,同步接口 QPS 1200,99 rt 180 ms;异步接口 QPS 8000,99 rt 45 ms;失败重试成功率 99.7%,DLX 占比 0.03%,符合国内电商大促要求。
拓展思考
- 如果公司全面云原生,可把 Redis Stream 换成 RocketMQ,利用自带的 16 次重试+DLX,PHP 端用 aliyunmq/rocketmq-client-php 的协程版,消费延迟可再降 30%。
- 对于金融支付类场景,重试次数不能简单指数退避,需引入“阶梯人工复核”:第 1、2 次自动,第 3 次转人工工单,工单系统回调内部 API 继续重试,防止重复扣款。
- 当异步链路跨机房(上海→深圳)时,可给消息体加 X-Site 头,利用腾讯云 DTS 做双向同步,PHP 消费端根据 X-Site 决定写主库还是只读库,做到异地多活。
- 未来升级到 PHP 8.3+Fiber,可把 Guzzle 的异步重试逻辑用 Fiber 做“同步写法、异步调度”,减少回调地狱,面试时可作为亮点抛出。