ARTICLE DETAIL

资讯详情

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

SpringCloud Gateway实战:路由断言过滤器与502排查全解析

SpringCloud Gateway实战:路由断言过滤器与502排查全解析 1. 先泼一盆冷水Gateway不是转发工具是流量关卡我在面试候选人的时候几乎每次问到SpringCloud Gateway都会得到一个标准答案“网关就是统一入口做路由转发、过滤、鉴权。”这个回答没错但实在太空了。空到让人怀疑他是不是只背了概念没跑过项目。先说说我自己的经历。之前有个项目团队为了快速上线内部服务之间直接调用几十个服务互相耦合得跟意大利面一样。后来要接外部渠道需要一个统一出口处理签名、限流、日志有人提了一嘴“上Gateway吧”然后花了两天时间把路由配上转发是通了但上线第三周就被渠道方投诉——我们的请求签名算法变了三次每次都要改所有调用方。那一刻才真正理解网关的价值从来不在“转发”本身而在于它把横切关注点收拢到了一个入口。SpringCloud Gateway说白了就是基于Spring WebFlux的响应式网关底层跑在Netty上。它不是一个Servlet应用跟传统的Spring MVC项目不一样这意味着你在处理IO密集型流量时用一个线程就能扛住大量并发连接而不是每个请求占一个线程。很多第一次接触Gateway的人会在这里踩坑——把原来的Filter、拦截器、ThreadLocal那套思路直接往Gateway上搬结果发现完全不是那么回事。这篇内容适合这些人看正在学SpringCloud、准备面试、或者项目里已经上了Gateway但感觉只用了它十分之一能力的人。我会把路由、断言、过滤器这三个核心组件一个个拆开讲清楚再补上配置、集群、排错这些实战里绕不开的内容。2. 三大核心组件拆解路由、断言、过滤器到底谁管谁Gateway的核心组件官方文档里写得很清楚Route路由、Predicate断言、Filter过滤器。这三者的关系我用一个生活场景来解释路由是地图断言是门卫过滤器是流水线上的工人。2.1 Route路由网关的地图一个路由由三部分组成ID、目标URI、一组断言和过滤器。它的作用就是告诉网关“什么请求该往哪儿去”。ID是唯一标识URI是转发目标地址断言决定了这个路由在什么条件下生效过滤器则会在请求转发前后做加工。spring: cloud: gateway: routes: - id: order-service uri: lb://order-service predicates: - Path/api/order/** filters: - StripPrefix2这段配置就是最典型的用法。id叫order-serviceuri用的是lb://前缀意思是通过注册中心的服务名做负载均衡。Path匹配/api/order/**开头的请求StripPrefix2会把路径前两级去掉再转发。也就是说客户端请求/api/order/v1/detail网关处理后转发给order-service的路径是/v1/detail。这里强烈建议所有路由ID都和服务名保持一致。你问我为什么因为排障的时候日志里看到route id等于服务名你不用再去翻配置确认这条路由到底是给哪个服务用的。这个习惯在路由数量超过20条之后尤其重要。2.2 Predicate断言这个请求该走哪条路断言就是一堆条件判断全部满足才会命中当前路由。Gateway内置了一堆常用的断言工厂比如Path、Query、Header、Method、Cookie、Host等等也支持自定义。实际项目中用得最多的是Path其次是Header和Query。predicates: - Path/api/payment/** - HeaderX-Client-Version, 2.* - Querychannel, h5上面这段要求请求路径以/api/payment/开头同时请求头X-Client-Version要以2.开头并且带了channel参数且值为h5三个条件同时满足才路由到支付服务。这就是一个很典型的场景渠道方的老版本客户端签名逻辑不同我不想让它们打到新版服务上那就用Header和Query把它们分流到不同路由。还有一个经常被忽视的断言是After、Before和Between配合时间条件做灰度发布。比如之前做过一次活动凌晨0点到6点之间请求走新服务其他时间走老服务。一开始打算在代码里写时间判断后来发现一个Between断言就能搞定改配置重启就生效连代码都不用动。2.3 Filter过滤器请求的加工流水线过滤器是Gateway里最灵活的部分也是面试时最容易聊出深度的点。它分两类Global Filter全局过滤器和Gateway Filter路由级过滤器。全局过滤器对所有路由生效。常见的比如XForwarded Remote Address过滤器会重写请求头把真实的客户端IP透传给下游。路由级过滤器只对当前路由生效比如StripPrefix、AddRequestHeader、Retry、RequestRateLimiter这些。Component public class AuthGlobalFilter implements GlobalFilter, Ordered { Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token exchange.getRequest().getQueryParams().getFirst(token); if (StringUtils.isBlank(token)) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } Override public int getOrder() { return -100; } }这是一个最原始的鉴权过滤器。getOrder()返回的是执行顺序数字越小优先级越高负数意味着比内置过滤器的默认顺序更靠前。为什么不直接在业务服务里做鉴权一开始我们也是每个服务自己校验后来发现一套鉴权逻辑复制了几十份某个服务版本升级漏改了线上就出了一个权限漏洞。把鉴权收敛到网关之后至少保证所有外部流量必须过同一道闸门。需要注意的是Gateway的过滤器是基于WebFlux的接口签名里大量出现Mono和Flux这是响应式编程的返回值类型。习惯了Spring MVC的人第一次写会觉得别扭但别慌大多数情况下你只需要照猫画虎写chain.filter(exchange)理解这是个异步调用链就够了。2.4 三者的协作顺序一次请求进入Gateway后先被路由的断言条件判断命中之后进入过滤器链过滤器链执行完才把请求转发到目标URI。响应回来时过滤器链还会反向再走一遍这个过程叫post-filter。很多人只关注请求方向的过滤忽视了响应方向也可以在过滤器中统一加工比如给响应统一加时间戳、压缩、加密。整个链路是客户端请求 - GatewayHandlerMapping找到匹配路由 - FilteringWebHandler组装过滤器链 - 转发到下游服务 - 下游响应原路返回 - 客户端收到结果。3. 路由配置实操从固定地址转发到Nacos动态路由配置层面的问题在网上被问得最多的就是“怎么把请求转发到一个固定的链接地址”。说实话这个需求很常见尤其是对接外部老系统的HTTP接口、内部工具平台、或者转发到指定环境时。3.1 转发到固定地址spring: cloud: gateway: routes: - id: external-api uri: http://192.168.1.100:8080 predicates: - Path/proxy/external/** filters: - RewritePath/proxy/external/(?segment.*), /$\{segment}这里的逻辑是请求/proxy/external/health网关RewritePath把它重写为/health再转发到http://192.168.1.100:8080/health。对端服务不需要感知网关的存在。特别注意一个坑YAML里写RewritePath时一定要用单引号包裹因为正则表达式里的$符号在YAML解析时会被处理。很多人在这个上面卡了半天日志里看到的路径始终不对其实就少了一对单引号。3.2 结合注册中心的动态路由固定地址适合小项目服务多了以后还是得走注册中心。Gateway对Nacos的支持非常友好只要uri改成lb://服务名即可。spring: cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true开了discovery locator之后Gateway会自动扫描注册中心里的服务生成一条默认路由/服务名/**转发到对应服务。这个功能开发环境非常方便但生产不建议依赖它——因为自动生成的路由没有StripPrefix也没有细粒度断言路径会带着服务名后缀容易把下游接口搞乱。我的做法是开发环境开启discovery locator方便调试生产环境关掉全部用显式路由配置。显式路由虽然看起来重复但每一条路由都能精确控制过滤器和断言排查问题时思路清晰得多。3.3 负载均衡与超时配置lb://自带负载均衡默认是轮询策略激活条件是项目里引入了spring-cloud-starter-loadbalancer。这里的坑是很多人以为SpringCloud Gateway默认接的是Ribbon但从2020年之后的版本开始Ribbon被移除了官方推荐用Spring Cloud LoadBalancer。如果你用的Nacos版本比较老可能会出现服务名解析不了的情况多半是负载均衡组件没引入或者版本不匹配。超时配置是另一个必踩的坑。Gateway默认连接超时和响应超时都不大对接慢接口时很容易把自己这边先搞挂。spring: cloud: gateway: httpclient: connect-timeout: 3000 response-timeout: 10s pool: max-connections: 500 max-idle-time: 30sconnect-timeout是建立连接的超时response-timeout是等待响应的超时。这两项一定要根据下游服务的真实响应时间来定配合压测数据一起调整。曾经有个项目没配response-timeout下游一个报表接口要跑40秒结果网关侧默认超时后直接断开客户端看到的是一堆502和连接重置。4. 集群部署与高可用Gateway不是单机玩具Gateway本身是无状态的应用无状态意味着它可以随意横向扩容。但也正因为无状态它不会帮你保存任何会话信息所有需要状态的能力比如分布式限流的计数器都得靠外部存储解决。4.1 集群的基本形态一个标准的Gateway集群部署是客户端 - Nginx负责负载均衡和SSL卸载 - Gateway集群两到三个节点 - 下游微服务。Nginx层可以用HTTP健康检查来摘除故障节点。Gateway暴露一个/actuator/health端点Nginx定期探测发现不健康就停止往这个节点转发。这里必须引入Spring Boot Actuator否则Nginx的健康检查只能靠TCP端口存活判断Gateway如果出现内存溢出或线程池耗尽端口不一定立刻关闭Nginx会继续往里打流量。4.2 无状态设计与共享存储Gateway集群模式下如果用了限流过滤器RequestRateLimiter默认的限流数据是存在本机内存里的。单机没问题两台机器就会出问题——流量被Nginx分散到两个节点每个节点各自统计各自的配额总体的QPS限制等于实际变成了两倍。解决办法是使用Redis Rate Limiter把令牌桶数据放到Redis里共享。配置方式很简单spring: cloud: gateway: routes: - id: order-service uri: lb://order-service predicates: - Path/api/order/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 100 redis-rate-limiter.burstCapacity: 200 key-resolver: #{ipKeyResolver}replenishRate是每秒填充的令牌数burstCapacity是桶容量key-resolver决定了限流粒度这里用了SpEL表达式引用一个自定义Bean按客户端IP限流。集群规模还有个容易忽略的点Gateway节点越多路由配置的同步成本越高。如果配置全写在application.yml里改一次配置就得逐个节点更新。正经做法是把路由配置放到Nacos配置中心用spring-cloud-starter-alibaba-nacos-config管理配置变更后动态刷新所有节点自动同步。4.3 容量预估关于Gateway集群配几个节点没有标准答案但有个大概估算方式。先压测单节点能扛多少QPS假设你的业务平均请求耗时50ms单核CPU的Netty网关大致能处理几百到上千QPS。再根据业务高峰期流量反推节点数记得多留30%到50%的余量应对突发流量。我们的经验是Gateway是IO密集型应用CPU核数比内存重要。配置服务器时优先选高主频多核的机型JVM内存给2到4G足够因为响应式模型下请求不占线程栈内存消耗大头是连接缓冲区和业务对象。5. 502 Bad Gateway的完整排查链路在热搜词里看到502 bad gateway被搜了那么多次我一点都不意外。SpringCloud Gateway的502是运维排障里的老朋友了问题点可多可少我讲一条完整的排查链路照这个顺序走大部分问题都能定位到。5.1 502发生的环节拆解502的全称是Bad Gateway意思是网关从上游收到了无效响应。一个请求经过Gateway502可能发生在两个阶段一是Gateway连接下游服务失败二是Gateway已经连上但下游返回超时或异常响应。网上搜502 bad gateway会出来大量AI网关、代理工具的内容跟SpringCloud Gateway完全无关。排障之前先明确你自己的网关类型别被搜索引擎带偏。5.2 从日志开始的排查顺序第一步看Gateway日志。Gateway的日志里一般会有转发失败的异常堆栈重点搜ConnectTimeoutException、ReadTimeoutException、Connection refused这些关键字。ConnectTimeoutException说明下游地址不通Connection refused说明端口没监听ReadTimeoutException说明TCP连接是通的但服务处理时间超过了response-timeout配置。第二步确认下游服务状态。如果你用的lb://方式先确认Nacos上服务实例是否在线。Nacos控制台 服务列表看实例的健康状态如果显示不健康基本上就是下游实例挂了或内存被打满了。第三步验证网络链路。如果是固定IP地址转发直接在Gateway所在机器上telnet目标服务的IP和端口。网络不通就排查防火墙和安全组注意有些云环境的安全组只放通了业务端口对网关到服务的内部端口没有开通这个在环境刚搭建的时候出问题最多。5.3 一次真实的502排查复盘之前遇到过一起诡异的生产502。下午三点开始大量请求时报502持续了大概40分钟之后自动恢复。查Gateway日志发现是ReadTimeoutException下游服务日志显示一切正常没有报错也没有慢SQL。后来看监控才发现问题出在下游服务连接池兜底策略高峰期连接数打到了数据库连接池上限新请求拿不到连接导致请求在等待获取连接的阶段超过了10秒触发了Gateway的响应超时。下游服务自身的响应时间监控看起来很漂亮因为慢的都是等待连接而不是执行SQL。那次之后把连接池最大连接数调大并加了连接等待超时告警问题就没再出现过。这个案例告诉我们502不一定是网关或者下游代码的问题中间件的连接池耗尽、线程池打满、GC停顿都可能造成上游超时。排查时不能只看一端的日志最好把Gateway、下游应用、中间件的监控时间线对齐找出真正的时间差。5.4 502的预防与监控经验之谈与其每次靠人肉排查不如提前把监控做好。我在Gateway上加了几项基础监控每个路由维度的请求量、成功率、耗时分布用Micrometer暴露到Prometheus下游转发失败的告警阈值可以设为连续10个请求转发失败就触发连接池的活跃连接数和等待队列长度response-timeout配置不能拍脑袋要根据下游的真实TP99响应时间去微调还有一点很实用于生产给Gateway配一个兜底错误处理。默认的502页面是空白的对排查和客户端提示都不友好。可以定义一个全局异常处理器把502和504对应的提示信息统一转换成JSON结构返回给调用方。6. 面试常问问题清单与几个文档里不会写的实践教训看到热搜里有springcloud面试题我顺手把Gateway相关的经典问题整理一下再附带几个我在实际项目里的经验教训。6.1 高频面试题整理Gateway和Zuul的区别是什么最核心的差异是底层模型。Gateway基于WebFlux和Netty响应式非阻塞Zuul 1.x基于Servlet每个请求占一个线程。在线程模型上Gateway处理高并发连接时资源占用更低。另外Gateway原生支持长连接WebSocketZuul 1.x对WebSocket的支持没有这么直接。面试官通常希望听到的不只是“Gateway是异步的”还得能说出异步带来的实际效果——比如在IO密集场景下用少量线程承载海量连接。Gateway的过滤器有哪几种执行顺序怎么控制分Global Filter和Gateway Filter。执行顺序通过Ordered接口的getOrder()控制数字越小越靠前。全局过滤器对所有路由生效路由级过滤器只对指定路由生效。路由、断言、过滤器的工作流程是怎样的请求进来先被GatewayHandlerMapping匹配路由匹配过程就是执行断言条件匹配成功后由FilteringWebHandler组装过滤器链过滤器链处理完请求再转发到目标服务响应返回时过滤器链逆序执行。Gateway如何实现限流内置的RequestRateLimiter配合Redis基于令牌桶算法。关键配置是replenishRate和burstCapacity限流粒度通过key-resolver控制可按IP、按用户ID或按接口维度。Gateway集群部署需要注意什么无状态部署限流数据需要共享存储Redis路由配置建议放配置中心统一管理负载均衡可以用Nginx做流量分发。6.2 几个实践教训第一个教训不要把业务逻辑写进过滤器。开始的时候图方便把价格计算这种业务规则写到了网关的过滤器中后来业务一变就要改网关并重启风险极高。Gateway的定位是横切关注点凡是业务逻辑都应该下沉到微服务网关只做通用的技术处理。第二个教训测试环境一定要沿用生产环境的路由配置风格。我们曾为了开发方便测试环境开了自动路由发现所有服务名直接暴露成URL。结果有一次测试环境的服务列表里混进了一个本地冒烟测试的实例导致一批测试请求打到错误的服务上浪费了整整一个下午来排查。后来测试环境也改成显式路由这类问题再也没出现过。第三个教训版本升级别拍脑袋。SpringCloud是个全家桶版本彼此有强依赖关系升级Gateway版本时必须连Spring Boot和Spring Cloud的版本一起校验。吃过一次亏只把Gateway依赖升级了结果Spring Boot版本太旧启动时报了一堆Bean冲突。推荐直接看官方推荐的版本对应关系或者用Spring Initializr生成的版本组合作为基线。这三个教训都是我踩过坑之后总结出来的。技术文档只会告诉你组件能做什么但实战里真正决定成败的往往是这些细节路由ID是否规范、配置是否有冗余、版本是否匹配、监控是否到位。把这些细节管好了Gateway才能成为微服务架构里真正可靠的流量关卡。
返回列表