Apollo Federation API网关无限循环问题排查求助
问题原因分析及解决方案
核心问题拆解
你的场景中出现的多次空domain/token调用和整体耗时过长是关联问题,主要源于以下几点:
1. 网关内部请求触发不必要的认证逻辑
Apollo Gateway在运行过程中会发起内部请求,比如服务健康检查、SDL同步(即使使用静态supergraphSdl,部分场景下仍会有内部校验请求),这些请求不会携带客户端的authorization和domain头,但会进入你定义的context函数执行getUserRole,导致多次空参数调用,日志中req url: undefined也印证了这一点——这类内部请求的路径属性并非你错误获取的req.headers.originalUrl。
2. getUserRole无缓存导致重复耗时
如果getUserRole是远程调用(比如查询认证服务、数据库),每个请求(包括内部无效请求)都会发起一次远程调用,没有缓存的情况下,重复请求会累积大量耗时,拖慢整体响应。
3. 请求路径属性获取错误
你尝试从req.headers.originalUrl获取请求路径,但originalUrl是Express等框架扩展的请求对象属性,并非HTTP标准请求头,正确的获取方式应为req.url(标准属性)或req.originalUrl(Express框架下),这也导致日志中请求路径显示为undefined,无法准确区分请求类型。
针对性解决方案
1. 区分内部请求与客户端请求,跳过无效认证
在context函数中识别网关内部请求,跳过getUserRole执行:
context: async({ req }) => { // 识别网关内部请求(健康检查、SDL同步等) const isInternalRequest = req.path === '/.well-known/apollo/server-health' || req.headers['apollo-internal'] === 'true'; if (isInternalRequest) { return {}; } const token = req.headers['authorization'] || ''; const domain = req.headers['domain'] || ''; // 修正请求路径获取方式 console.log('req url: ', req.originalUrl || req.url); const user = await getUserRole(token, domain); return { ...user }; }
2. 为getUserRole添加缓存,减少重复调用
对相同token+domain的请求结果进行缓存,避免重复远程调用:
// 内存缓存(生产环境建议替换为Redis等分布式缓存) const roleCache = new Map(); const CACHE_TTL = 5 * 60 * 1000; // 5分钟过期 async function getUserRole(token, domain) { const cacheKey = `${token}:${domain}`; // 命中缓存直接返回 if (roleCache.has(cacheKey)) { return roleCache.get(cacheKey); } // 原有的角色获取逻辑 const user = await fetchAuthInfoFromService(token, domain); // 存入缓存并设置过期 roleCache.set(cacheKey, user); setTimeout(() => roleCache.delete(cacheKey), CACHE_TTL); return user; }
3. 优化数据源的头设置逻辑
避免向子服务传递空值头,减少无效数据传输:
class AuthenticatedDataSource extends RemoteGraphQLDataSource { willSendRequest({request, context }) { if (context.requiredAuth !== undefined) { request.http.headers.set('required-auth', context.requiredAuth); } if (context.authenticated !== undefined) { request.http.headers.set('authenticated', context.authenticated); } if (context.userRole !== undefined) { request.http.headers.set('user-role', context.userRole); } } }
内容的提问来源于stack exchange,提问作者Danis
相关产品推荐
相关产品推荐

