资讯动态

Spring Cloud Gateway微服务网关路由、限流与线程模型实践

发布时间:2026/10/27 7:22:08 来源:尧图企业网站定制
1. 从一次凌晨的接口雪崩说起网关在架构里到底站什么位置微服务网关这个话题很多人是在项目拆到七八个服务、前端开始抱怨“接口地址一天变三次”的时候才被迫认真研究的。我印象最深的一次是某项目的订单服务做了一次扩容实例从 3 个变成 8 个结果客户端那边硬编码的 IP 列表没同步半夜一半请求打到了已经下线的旧节点上监控面板上错误率曲线直接拉成一条竖线。第二天复盘问题根本不在业务代码而在于没有任何一层东西替客户端做统一的入口收敛和实例寻址。Gateway 服务网关要解决的恰恰是这类“架构层面的搬运工作”。先把话说清楚Gateway 在这里指的是微服务体系里的 API 网关层典型实现是基于响应式编程模型的 Spring Cloud Gateway也可以是其他同类框架。它的位置很明确——所有外部流量浏览器、App、第三方回调先进网关再由网关按规则转发到后端的各个微服务。它解决的是三件事入口统一、横切逻辑收敛、后端拓扑对客户端透明。适合正在做服务拆分、或者已经被“前端要维护十几个域名”折磨过的团队参考如果你只有一个单体应用、日请求量几千那老老实实把 Nginx 配好就行上网关属于给自己加作业。我写这篇的出发点是把网关这个东西从“配置几行 yml 就能跑”的表象里拽出来。真正让网关在生产上出问题的从来不是路由写不出来而是路由写得出来但顺序不对、过滤器写得出来但把线程堵死了、限流配得出来但阈值是拍脑袋定的。下面按我实际踩过的顺序,一层一层往下拆。1.1 客户端直连服务暴露出的三个具体麻烦在没有网关的阶段客户端直连微服务通常靠注册中心 客户端负载均衡或者更粗暴一点直接配一堆域名。这两种做法在服务数量少于五个、团队只有一个前端组的时候勉强能忍一旦规模上去麻烦会以三种形态集中爆发。第一种是地址治理失控。每个微服务都有自己的域名或 IP 端口客户端需要维护一张越来越长的映射表。服务改个端口、加个灰度环境、迁一次机房客户端就得跟着发版。移动端更惨用户不升级就永远拿着旧地址。第二种是横切逻辑重复实现。鉴权、限流、日志埋点、跨域处理、请求 ID 生成这些东西每个服务都要写一遍写十遍就有十种实现出问题的时候排查口径都不一样。第三种是后端拓扑直接暴露。客户端能感知到你有哪些服务、每个服务几个实例这在安全和演进灵活性上都是负担——你想把订单服务拆成两个客户端那边就炸了。网关的价值就是把这三种麻烦一次性收口。客户端只认一个域名后面的服务怎么拆、怎么扩、怎么灰度全在网关内部完成。这个收益是架构级的不是省几行代码的问题。1.2 网关的职责边界该收的收上来不该管的一律别碰这一点我必须重点讲因为见过太多项目把网关做成了一个“什么都往里塞”的怪物。有个团队的网关里塞了业务校验、订单状态机判断、甚至还有一段查询用户等级的数据库逻辑最后网关的接口响应时间比业务服务还长一次数据库慢查询直接把整个站点的入口堵死。判断一个逻辑该不该放网关我的标准很简单它是否对所有或大部分路由都成立且不依赖具体业务语义。按这个标准划一下该放在网关不该放在网关统一鉴权与令牌校验具体业务参数校验限流、熔断、黑白名单订单状态流转判断跨域头处理查询数据库拼装业务数据请求 ID 注入与日志透传复杂的响应体业务字段改写路径改写与服务寻址业务级的聚合编排那是 BFF 的活协议转换、灰度分流缓存业务数据网关是基础设施层不是业务层。它的健康标准是“稳定、低延迟、无状态”任何会让它变慢或变复杂的东西都应该往下沉。我后来给团队定了一条硬规矩网关代码库里不允许出现任何Service命名的业务类只允许 Filter 和配置。这条规矩救过我们不止一次。1.3 选型对比现成网关框架、Nginx 层、还是自研选型这块不用纠结太久但得知道各自的边界。Nginx或 OpenResty做接入层转发性能极好配置成熟适合做最外层的 TLS 卸载和静态资源但它做动态路由、服务发现集成、按 Header 做灰度这类事情配置维护成本会陡增而且团队里能写 Lua 的人往往就一两个。基于响应式模型的网关框架优势在于和后端服务共用一套注册中心与配置中心路由能动态刷新Filter 用主语言写团队上手快。自研网关这件事我的态度是除非你的流量规模或者特殊协议需求已经到了现成方案扛不住的量级否则不要碰。网关看着简单实际上是连接管理、背压、超时、重试、内存这几件事的高难度组合自己写一遍大概率会在某个深夜被 direct buffer 泄漏教做人。提示如果你的团队里没人能说清楚“响应式线程模型下为什么不能做阻塞调用”那就先别急着把网关推到核心链路先在非核心业务上跑一两个月。2. 路由配置的坑几乎全在匹配顺序和路径改写上路由配置是所有人接触网关的第一课也是最容易被低估的一课。表面上看就是id uri predicates filters四个字段实际上线后遇到最多的问题都集中在这里明明配了路由却报 404、请求转发了但下游收到的路径少了一段、两个路由互相抢流量。这些问题有一个共同点——配置本身语法没错错在对匹配机制的理解。我一般要求团队在写路由前先明确两件事这条路由的唯一标识语义是什么按业务域还是按服务名以及它的路径改写规则由谁负责网关剥前缀还是下游自己带前缀。这两件事定不下来后面一定会返工。2.1 断言工厂的常用组合与真实写法断言Predicate决定一条路由在什么条件下生效。常用的大致分四类路径类Path、方法类Method、请求头/参数类Header、Query、Cookie、时间与权重类After、Before、Between、Weight。实际项目里 90% 的路由是 Path Method 的组合剩下 10% 用在灰度和特殊场景。一个能直接抄的配置骨架长这样spring: cloud: gateway: routes: - id: order-service uri: lb://order-service predicates: - Path/api/order/** - MethodGET,POST filters: - StripPrefix2 - AddRequestHeaderX-Gateway-Source, edge - id: user-service uri: lb://user-service predicates: - Path/api/user/** filters: - StripPrefix2逐行解释几个容易出错的点。uri: lb://order-service里的lb是负载均衡前缀它要求类路径上存在负载均衡组件否则启动时会提示找不到对应实例如果写成http://127.0.0.1:8081就是写死地址失去动态扩缩容能力只建议在本地调试时用。StripPrefix2表示剥掉路径的前两段/api/order/123转发后变成/123这里的数字必须和实际路径段数严格对应多剥一段少剥一段都会让下游 404。Weight断言是做灰度最省事的方式同一个路径配两条路由分别给group和weight权重加起来是 100- id: order-service-v1 uri: lb://order-service predicates: - Path/api/order/** - Weightorder-group, 90 - id: order-service-v2 uri: lb://order-service-v2 predicates: - Path/api/order/** - Weightorder-group, 10注意group名字必须一致才会被算进同一组权重名字写错就变成两条独立路由各走各的表现出来就是“灰度没生效”。2.2 路由优先级为什么你的规则总是被另一条抢先命中这是踩过最疼的一个坑。网关的路由匹配是按顺序遍历、命中第一条即返回不是“最精确匹配优先”。也就是说如果你先声明了Path/api/**后面再声明Path/api/order/**那么订单请求永远走第一条第二条形同虚设。我们当时的现场是这样的为了兼容老客户端加了一条/api/**的兜底路由转发到旧服务后来新增订单服务路由加在配置文件末尾。测试环境订单接口一切正常上线后订单请求全部打到了旧服务上返回的都是过期的数据结构前端解析直接报错。排查了快两个小时最后把路由列表打印出来一看顺序问题一目了然。解决办法有两个。一是用order字段显式排序数字越小优先级越高- id: order-service uri: lb://order-service order: -1 predicates: - Path/api/order/**二是从设计上避免通配兜底路径规划时就用不同前缀区分比如内部接口统一/internal/**、开放接口统一/open/**从源头上消灭“谁先谁后”的歧义。我更推荐第二种因为它不依赖任何人记得加order。注意路由既可能来自配置文件也可能来自注册中心或数据库动态推送两批路由合并后的顺序往往不由你控制。生产环境务必通过网关自带的路由查询端点把最终生效列表拉出来核对一遍别只看自己写的那份。2.3 StripPrefix 与 RewritePath 的取舍路径改写有两个常用手段StripPrefix按段数剥RewritePath按正则替换。前者简单但僵硬后者灵活但正则容易写错。StripPrefix适合“网关前缀固定、下游路径干净”的场景比如外部统一/api/{service}/**内部服务不感知api和{service}两段。RewritePath适合路径结构不一致的迁移期比如老接口/v1/getUserInfo要映射到新服务/user/infofilters: - RewritePath/v1/getUserInfo, /user/info正则替换的坑在于分组引用写错不会报错只会静默转发到错误路径。比如RewritePath/api/(?segment.*), /$\{segment}里的大括号在某些 YAML 解析器下需要转义写漏了就会把字面量传给下游。我的习惯是改完路径后先用一条测试请求打到回显接口上把下游收到的requestURI打印出来确认再往下推进。还有一个隐蔽问题下游服务的 context-path 和网关改写叠加。如果下游自己配了/order前缀网关又剥了段最终路径可能变成/order/order/123。这类问题排查成本极高所以在项目初期就要约定清楚“前缀归谁管”并且写进接口规范里。3. 全局过滤器鉴权、上下文透传和请求体只能读一次过滤器是网关真正干活的地方。断言负责“选路”过滤器负责“加工”。框架自带的过滤器工厂能覆盖大部分需求但统一鉴权、用户上下文透传、统一响应包装这三件事基本每个项目都得自己写全局过滤器。写全局过滤器有个前提认知它运行在响应式线程上任何一个阻塞操作都会拖垮整个事件循环。这一点我在第 6 节会展开这里先记住结论——过滤器里不要出现同步的数据库查询、同步的远程调用、Thread.sleep、以及任何返回非响应式类型的客户端。3.1 统一鉴权放在网关做的理由与实现骨架鉴权放网关最直接的理由是避免每个服务重复实现更重要的是能形成一道统一防线新服务上线时只要在网关注册路由就自动获得了鉴权保护不会因为某个开发者忘了加注解而裸奔。实现上一个鉴权过滤器需要做四步取令牌、验令牌、解析出用户身份、把身份塞进请求头传给下游。骨架大致是Component public class AuthGlobalFilter implements GlobalFilter, Ordered { private final TokenVerifier tokenVerifier; public AuthGlobalFilter(TokenVerifier tokenVerifier) { this.tokenVerifier tokenVerifier; } Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { String path exchange.getRequest().getURI().getPath(); if (path.startsWith(/open/) || path.startsWith(/actuator/)) { return chain.filter(exchange); } String token exchange.getRequest().getHeaders().getFirst(Authorization); Claims claims; try { claims tokenVerifier.verify(token); } catch (Exception e) { return reject(exchange, 401, invalid token); } ServerHttpRequest mutated exchange.getRequest().mutate() .header(X-User-Id, claims.getUserId()) .header(X-Tenant-Id, claims.getTenantId()) .build(); return chain.filter(exchange.mutate().request(mutated).build()); } Override public int getOrder() { return -100; } }几个细节值得说。白名单判断必须放在验令牌之前否则健康检查接口也会被拦。getOrder()返回负数保证它在业务过滤器之前执行。reject方法要自己写完整响应包括状态码、Content-Type和响应体否则客户端可能收到一个空响应前端无法定位问题。这里有个安全上的坑下游服务不能无条件信任X-User-Id这类请求头。如果攻击者绕过网关直连服务端口伪造这个头就能冒充任意用户。所以内网服务要么只监听内网地址要么在网关侧对这类头做覆盖而非追加——用header()而不是headers(h - h.add())确保客户端传来的同名头被冲掉。3.2 用户上下文怎么传给下游服务传递上下文有三条路请求头、URL 参数、请求体。请求头是唯一合理的选择参数会污染日志和缓存键改请求体会引入一串麻烦。传递时要注意字符编码和头长度。用户身份信息如果塞了完整权限列表头可能超长某些中间件对单个头有长度上限超了直接 400。我的做法是只传三个必要字段用户 ID、租户 ID、令牌过期时间戳权限校验交给下游缓存或者再查一次。还有一点是上下文在异步链路里的丢失。响应式编程里没有 ThreadLocal 的天然支持如果你在下游服务里用ThreadLocal存用户上下文跨线程池切换时会拿到空值。要么用响应式上下文传递要么在入口处把上下文显式作为方法参数往下传。这个问题在引入异步之后才会暴露属于典型的“上线才发现”系列。3.3 请求体重复读取的三种处理方式请求体在响应式模型里是一个FluxDataBuffer只能被消费一次。如果你的过滤器读了一遍做签名校验转发时下游收到的就是空 body表现为下游报“参数缺失”而你的过滤器日志一切正常。处理方式有三种按复杂度递增第一种是能不读就不读把需要 body 的校验下沉到下游网关只做基于头的判断。这是最省事的很多时候签名校验其实可以用头里的时间戳 令牌完成未必需要 body。第二种是缓存后重写把 body 读出来存成字节数组然后重建请求Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { return DataBufferUtils.join(exchange.getRequest().getBody()) .flatMap(dataBuffer - { byte[] bytes new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bytes); DataBufferUtils.release(dataBuffer); String body new String(bytes, StandardCharsets.UTF_8); ServerHttpRequest request exchange.getRequest().mutate() .header(X-Body-Length, String.valueOf(bytes.length)) .build(); ServerHttpRequestDecorator decorator new ServerHttpRequestDecorator(request) { Override public FluxDataBuffer getBody() { return Flux.defer(() - Mono.just( exchange.getResponse().bufferFactory().wrap(bytes))); } }; return chain.filter(exchange.mutate().request(decorator).build()); }); }第三种是用框架提供的请求体改写过滤器工厂配置化完成代价是灵活性差一些。第二种方案的风险在于内存大文件上传时把整个 body 读进内存几个并发就能把堆打满。所以必须配合大小限制超过阈值直接拒绝并且把这条路由明确排除在 body 缓存之外。4. 限流参数的推导过程不要凭感觉填 replenishRate限流是最容易“配了个数字但不知道为什么”的地方。我看过太多配置里replenishRate: 100、burstCapacity: 100问配置的人依据是什么回答是“抄的”。这种配置在流量平稳时看不出问题一旦遇到突发就会要么误杀正常用户要么完全不起作用。4.1 令牌桶两个参数的业务含义基于 Redis 的令牌桶限流有两个核心参数replenishRate令牌生成速率单位是个/秒等价于你允许的长期平均 QPS。burstCapacity桶容量等价于允许的瞬时突发量。还有一个requestedTokens表示一次请求消耗几个令牌默认 1。对于普通接口填 1 就行但如果是批量接口一次请求处理 100 条数据把它设成和批量规模成正比的数值更合理否则限流就形同虚设。关键关系是一个请求需要requestedTokens个令牌桶里不够就会等待或拒绝而桶每 1/replenishRate秒补充一个令牌。这里有个非常容易踩的坑——当burstCapacity小于replenishRate时补充速率会被桶容量卡住实际表现是“每隔若干秒才放行一个请求”。举个具体例子replenishRate100、burstCapacity10那么桶里最多存 10 个令牌而补充速度是每秒 100 个——看起来补充很快但因为桶太小一瞬间就被打空随后的请求只能等下一个令牌实际通过的瞬时并发只有 10。这个配置在压测时特别具有欺骗性低并发下一切正常一上量就大量 429。4.2 从容量规划倒推参数的完整算法我的推法是三步。第一步确定单实例的目标 QPS。从压测数据来不要从理论来。假设压测显示网关单实例在 P99 小于 100ms 的前提下能稳定扛住 800 QPS留 30% 余量目标定为 550 QPS。第二步把总目标按路由拆分。假设订单接口占整体流量的 40%那订单路由的目标就是 220 QPS。replenishRate取 220。第三步按业务容忍的突发窗口定桶容量。如果业务允许 3 秒的突发缓冲burstCapacity replenishRate × 3 660。这样设计的效果是正常情况下按 220 QPS 匀速放行遇到秒杀这种瞬时尖峰时桶里最多有 660 个令牌可以一次性消耗撑过约 3 秒之后回落到匀速。简化成一张对照表业务场景replenishRateburstCapacity说明查询类接口单实例目标 QPSreplenishRate × 2容忍短时突发用户体验优先写入类接口单实例目标 QPS × 0.6replenishRate × 1.5保护下游数据库第三方回调按对方约定速率略大于速率不小于速率桶容量绝不能小于速率登录/短信类严格按业务规则等于速率防刷优先不留突发空间还有一条硬约束必须记住burstCapacity必须大于等于replenishRate否则限流器的工作方式会和你预期完全相反。这条我在代码评审里见过至少三次被违反。限流后的行为也要定义清楚是直接返回 429 让客户端重试还是排队等待。默认是拒绝这通常是对的——排队会占住连接把网关变成瓶颈。响应体里最好带上Retry-After头告诉客户端多久后再来。4.3 限流之外的熔断与降级边界限流保护的是网关自己熔断保护的是下游服务。两者经常被混为一谈。限流解决的是“进来的请求太多”熔断解决的是“下游已经不行了别再打了”。当一个下游服务的错误率超过阈值熔断器打开后续请求直接走降级逻辑不再发往下游给下游留出恢复时间。典型配置是错误率超过 50%、且在 10 秒统计窗口内请求数超过 20 才触发避免低流量时误判。熔断的坑在于降级返回值的设计。很多项目降级时返回一个空对象或空列表前端拿到后渲染出“暂无数据”用户以为是真没数据实际上服务已经挂了。降级响应应该带上明确的状态标识让前端能区分“真的没有”和“暂时不可用”。另外熔断是有状态的要确认所用的实现在多实例部署下是每实例独立统计还是全局统计这直接影响到阈值该设多少。5. 上线后才暴露的故障502、504、404 的排查链路网关的故障排查有个特点现象在网关根因大多不在网关。所以排查的第一步不是看网关日志而是先把状态码归类快速定位问题在哪一层。5.1 从状态码反推故障层的判断表状态码常见根因排查入口404路由未命中或路径被剥错段数查最终生效路由列表打印下游收到的 URI502下游实例不可达或下游返回了非法响应查注册中心实例健康状态、下游端口是否监听503无可用实例负载均衡选不出节点查服务发现是否正常、实例是否全部被摘除504下游响应超时查网关超时配置、下游耗时分布429触发限流查限流参数与实际 QPS 是否匹配431请求头过大查是否透传了过大的 Cookie 或鉴权信息这张表贴在我们值班文档的第一页能挡掉七成的无效沟通。举两个真实案例。一个是 404 的。某次发布后部分客户端报 404但用 Postman 请求同样的路径却正常。差异在于客户端带了一个额外的路径段比如/api/order/v2/list而路由写的是/api/order/**配StripPrefix2剥完变成/v2/list下游只认/list于是 404。解决方式是把改写规则改成RewritePath精确处理版本段。一个是 504 的。下游某个接口偶发超时网关默认没有配置响应超时导致连接一直挂着连接池耗尽后整个网关开始拒绝新请求表现是“所有接口都慢”看起来像网关崩了实际上是单个慢接口拖垮了连接池。这件事之后我们把响应超时设成了明确值并且给每个路由单独配。5.2 日志与链路追踪怎么开网关日志默认级别信息量不够排查时临时开 debuglogging: level: org.springframework.cloud.gateway: DEBUG reactor.netty.http.client: DEBUGreactor.netty.http.client这个包名打出来能看到实际的连接建立、请求发送、响应接收过程定位 502/504 特别有效。但它日志量很大只能临时开别留在生产默认配置里。链路追踪这块网关是整条链路的起点必须在这里生成或透传请求 ID。做法是在全局过滤器里检查请求头里有没有X-Request-Id没有就生成一个 UUID 塞进去并在响应头里回写一份。这样客户端报障时能直接给出 ID后端所有服务的日志都能按这个 ID 串起来。这个动作成本极低收益极高我建议所有项目无脑加上。还有一点网关自己的日志要能区分“转发前”和“转发后”。至少记录三行——收到请求路径、方法、来源 IP、转发目标实例地址、耗时、响应结果状态码、耗时。有了这三行绝大多数问题不需要复现就能定位。5.3 一份踩过的坑清单整理几条不那么常见但很折磨人的跨域头重复。网关配了跨域处理下游服务也配了一份浏览器收到两个Access-Control-Allow-Origin直接判定跨域失败。规则是跨域要么全在网关做要么全在服务做绝不能两边都做。推荐在网关统一做并且用DedupeResponseHeader过滤器去重。响应体压缩与内容改写冲突。如果开了响应压缩又用过滤器改写响应体可能拿到的是压缩后的字节流改写直接失败。要么关压缩要么在改写前解压。重试放大了写操作。Retry过滤器配置在写接口上下游超时但实际上已经处理成功重试导致重复下单。重试只能用于幂等的 GET或者下游接口本身有幂等键保护。上传接口的 body 缓存。前面提到的 body 缓存方案如果没有排除文件上传路由几十兆的文件会直接把内存打爆。健康检查路径被鉴权拦截。注册中心探活请求没有令牌被鉴权过滤器拦成 401实例被判定不健康反复摘除表现是“实例明明活着但流量进不来”。6. 线程模型与部署参数为什么阻塞代码会把网关拖死前面反复提到阻塞调用的问题这里系统说一下因为这是网关性能问题里最有代表性的一类。6.1 Reactor Netty 的线程模型到底长什么样网关底层的运行模型是少量事件循环线程处理大量连接。默认情况下事件循环线程数等于 CPU 核心数可以调整每个线程负责一批连接的读写事件。一个请求进来从解析、匹配路由、执行过滤器链到转发整个过程都在同一个事件循环线程上以非阻塞方式推进只有在真正等待网络 IO 时才让出线程。这个模型的好处是线程数少、内存占用低、上下文切换开销小能扛住很高的并发连接数。代价是任何一个线程被阻塞它负责的所有连接都会卡住。假设 8 个事件循环线程其中一个因为同步调用数据库卡了 3 秒那么这段时间里分配到它上面的所有请求全部排队表现为“部分请求莫名很慢”而且随机的、不可复现的因为它取决于请求被分配到哪个线程。这种故障最难排查的地方在于CPU 不高、内存不高、下游也不慢就是有零星请求超时。很多人会怀疑网络抖动其实是自己代码里埋了一个阻塞点。6.2 常见的伪异步写法以下几种写法看着像异步实际上都是阻塞在过滤器里调用同步 HTTP 客户端。用同步的 HTTP 工具去调鉴权中心即使加了超时调用期间线程也是被占住的。正确做法是用响应式 HTTP 客户端返回Mono让线程在等待期间去处理别的连接。在过滤器里操作关系型数据库。JDBC 是阻塞的没有例外。网关里如果需要查配置数据要么启动时一次性加载到内存并监听变更要么用响应式的缓存客户端。用同步的 Redis 客户端。这是最常见的因为很多人对 Redis 的印象是“很快”觉得阻塞一下没关系。但 Redis 抖动的时候一次操作可能卡几百毫秒足够把事件循环拖垮。用响应式客户端或者干脆把限流逻辑交给框架内置的实现。在过滤器里写日志文件。同步的文件 IO 也是阻塞的高并发下日志写入会成为瓶颈。用异步日志框架并且注意队列满时的丢弃策略。一个自检方法在代码里搜Thread.sleep、同步的RestTemplate、JdbcTemplate、同步 Redis 客户端在网关模块里出现任何一个都要在评审时打回去。6.3 上线前的压测与关键配置压测这块我的建议是不要只压网关本身要用真实的链路压——网关 至少一个真实下游服务因为网关的很多性能问题来自于和下游的交互方式比如连接复用没开、超时配置不合理。压测时重点看三组指标网关的 P99 响应时间、事件循环线程的忙碌程度、以及连接池的活跃连接数。如果 P99 随着并发上升出现台阶式跳变通常是某个资源池被打满了。几个值得显式配置的参数spring: cloud: gateway: httpclient: connect-timeout: 1000 response-timeout: 5s pool: max-connections: 500 max-idle-time: 30s acquire-timeout: 2000connect-timeout不建议设太大连接建立都超时的下游基本可以判定不可用了快速失败比慢慢等更有价值。response-timeout必须设不设就是无限等待这是连接池耗尽的头号原因。pool.max-connections要根据下游实例数和单实例承受能力算配太大反而会把下游打挂。acquire-timeout是从连接池取连接的等待时间设短一点能快速暴露连接池不够用的问题而不是让它变成一个隐藏的慢查询。另外还有 JVM 层面的网关处理大量连接会用到堆外内存-XX:MaxDirectMemorySize要显式设置不能依赖默认值。如果看到内存缓慢增长且不回落、Full GC 后也不释放优先怀疑堆外内存泄漏排查方向通常是响应体没有正确释放。提示网关是无状态组件实例数至少给两个并且要保证它们在不同的物理故障域上。网关是单点入口它挂了就是整站不可用这一点比任何参数调优都重要。最后分享一个我在实际项目里坚持的小习惯给网关单独做一套接口只返回当前生效的路由列表、限流配置和过滤器顺序不做任何业务逻辑仅供值班时人工核对。这套东西在三次线上故障里帮我省掉了大量猜测时间——很多时候问题不在代码而在于线上跑的那份配置和你以为的那份不是同一份。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑