资讯动态

Spring Cloud Gateway从零搭建:核心概念、环境配置与路由实战

发布时间:2026/10/13 4:04:37 来源:尧图企业网站定制
1. 为什么网关是整个微服务体系的“咽喉要道”聊到微服务架构很多人第一反应是注册中心、配置中心、链路追踪这些组件但真正扛在流量最前面的其实是网关。你可以把整个后端集群想象成一栋写字楼里面每个微服务就是一家独立公司而网关就是这栋楼唯一的前台和安保。所有外部请求——不管是来自浏览器、App还是第三方回调——都得先经过这个前台由它决定你是去18楼的订单部还是去3楼的用户部顺便检查你的工牌、记录你的进出时间、限制你每分钟只能刷几次门禁。Spring Cloud Gateway就是这样一个前台角色。它是Spring Cloud生态里第二代网关组件用来替代早期的Zuul 1.x。和Zuul基于Servlet的阻塞式模型不同Gateway底层跑在Spring WebFlux和Project Reactor之上走的是响应式、非阻塞的路子。这个差异在低并发场景下几乎感觉不到但一旦QPS上去线程模型带来的差距就非常明显了。Zuul 1.x每个请求占一个线程线程池打满之后后续请求只能排队Gateway用事件循环处理少量线程就能撑起大量并发连接这是它最核心的架构优势。这个专题我打算分几篇来写这是第一篇主要解决两件事一是把Gateway的定位和核心概念讲清楚二是把开发环境从零搭起来跑通第一个路由转发。很多人卡在第一步不是因为不会写代码而是环境没搭对依赖冲突、版本不匹配、启动报错折腾半天热情就没了。所以我会把环境搭建这块写得细一点包括版本怎么选、依赖怎么引、配置文件怎么写、启动后怎么验证。这篇文章适合谁看如果你已经会写Spring Boot应用但对微服务网关还停留在“听说过”的阶段那正好。如果你已经在用Zuul想迁移到Gateway也能从里面找到对应的概念映射。如果你是完全的新手只要跟着步骤走也能把环境跑起来因为我会把每个操作背后的原因都讲明白而不是甩一堆配置让你抄。2. 先把Gateway的核心概念吃透再动手2.1 三大核心概念路由、断言、过滤器Gateway的整个模型可以用三个词概括Route路由、Predicate断言、Filter过滤器。这三个概念是理解一切配置的基础我逐个拆开讲。Route是网关的最小转发单元。一个Route包含一个目标URI、一组匹配条件、一组过滤器。当请求进来时Gateway会遍历所有Route找到第一个匹配的Route然后把请求转发到它指定的URI。你可以把它理解成一条“转发规则”。Predicate是Route的匹配条件。它决定了“什么样的请求算匹配这条路由”。Gateway内置了十几种断言工厂比如按路径匹配Path、按方法匹配Method、按请求头匹配Header、按时间匹配Before/After/Between、按Host匹配等等。多个断言之间是“与”的关系必须全部满足才算匹配。Filter是请求在转发前后要经过的处理链。它分两种Gateway Filter和Global Filter。Gateway Filter绑定在特定Route上只对该路由生效Global Filter对所有路由生效。过滤器的典型用途包括加请求头、改路径、限流、鉴权、记录日志、修改响应体等等。我用一个生活化的类比帮你记住这三者的关系Route是“一条快递配送线路”Predicate是“什么样的包裹走这条线路”比如同城件走A线、跨省件走B线Filter是“包裹在这条线路上要经过的分拣、贴标、称重环节”。三者组合起来就构成了一条完整的转发规则。2.2 和Zuul的核心差异到底在哪很多人问要不要从Zuul迁到Gateway我的建议是新项目直接用Gateway老项目如果Zuul跑得稳、没有性能瓶颈不急着迁。但如果你确实在评估下面这张表能帮你快速判断。对比维度Zuul 1.xSpring Cloud Gateway底层模型Servlet阻塞式WebFlux响应式非阻塞线程模型每请求一线程事件循环少量线程高并发表现线程池易打满连接数受限于内存而非线程过滤器类型pre/route/postGateway Filter Global Filter动态路由需配合外部存储支持Actuator端点动态刷新长连接支持较弱原生支持WebSocket学习曲线较低需要理解响应式编程最关键的一点是线程模型。Zuul 1.x在高并发下线程池是瓶颈你调大线程池又会带来上下文切换开销。Gateway基于Netty的事件循环一个线程可以处理成百上千个连接这是它在大流量场景下的根本优势。当然代价是你得接受响应式编程的思维不能再用ThreadLocal存上下文了这个后面讲过滤器的时候会细说。2.3 请求在Gateway内部的完整流转过程理解请求流转过程对你后面排查问题非常关键。一个请求进入Gateway后大致经历这几个阶段接收请求Netty Server接收到HTTP请求交给WebFlux的DispatcherHandler。路由匹配RoutePredicateHandlerMapping遍历所有Route用Predicate逐个匹配找到第一个命中的Route。过滤器链执行请求进入FilteringWebHandler先执行所有Global Filter和Route Filter的pre逻辑按order排序。转发请求NettyRoutingFilter把请求转发到目标URI。响应处理目标服务返回响应后执行过滤器的post逻辑顺序与pre相反。返回响应把最终响应写回客户端。这个流程里有个容易踩坑的点过滤器的执行顺序由order值决定值越小越先执行。pre阶段按order升序post阶段按order降序。如果你自定义了过滤器但没设order默认是0可能会和内置过滤器产生意料之外的顺序问题。我一般会给自定义过滤器显式设置order比如鉴权用-100尽早执行日志用100最后执行。3. 开发环境搭建从零到跑通第一个路由3.1 版本选型别让版本冲突毁掉你的热情环境搭建第一步是选版本。Spring Cloud Gateway的版本是跟着Spring Cloud走的而Spring Cloud又和Spring Boot有严格的兼容关系。选错版本启动就报NoSuchMethodError非常打击人。我整理了一份当前主流稳定版本的对应关系你可以直接参考Spring BootSpring CloudSpring Cloud GatewayJDK要求3.2.x2023.0.x4.1.x173.1.x2022.0.x4.0.x172.7.x2021.0.x3.1.x8我的建议是新项目直接上Spring Boot 3.2.x Spring Cloud 2023.0.x JDK 17。JDK 17是LTS版本Spring Boot 3.x全面拥抱了Jakarta EE虽然包名从javax变成jakarta会让一些老代码需要改但新项目没有历史包袱直接用新的就好。如果你所在的环境还在JDK 8那就用Spring Boot 2.7.x Spring Cloud 2021.0.x这套组合也很成熟只是后续维护周期要注意。注意千万不要混用版本。比如Spring Boot用3.2.x但Spring Cloud用2021.0.x启动时大概率报兼容性错误。版本对应关系以官方发布的兼容矩阵为准不要凭感觉搭配。3.2 用IDEA创建项目并引入依赖我用的是IDEA创建项目的方式有两种一种是通过Spring Initializr在线生成一种是手动建Maven工程再自己加依赖。我推荐后者因为手动加依赖你能清楚知道每个依赖是干什么的出了问题也好排查。先建一个普通的Maven工程然后在pom.xml里加这几块依赖parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent properties java.version17/java.version spring-cloud.version2023.0.1/spring-cloud.version /properties dependencies !-- Gateway核心依赖 -- dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-gateway/artifactId /dependency !-- Actuator用于查看路由和动态刷新 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency /dependencies dependencyManagement dependencies dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-dependencies/artifactId version${spring-cloud.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement这里有个非常关键的坑引入spring-cloud-starter-gateway之后绝对不能再引入spring-boot-starter-web。因为Gateway基于WebFlux而spring-boot-starter-web会引入Tomcat和Spring MVC两者同时存在会导致Gateway的自动配置失效启动时可能报错或者路由不生效。如果你发现网关启动后路由怎么都不匹配第一件事就是检查有没有误引入web依赖。实操心得如果你需要用到WebClient之外的HTTP客户端或者某些工具类依赖了spring-web注意区分spring-web和spring-boot-starter-web。前者是基础库Gateway本身也依赖它后者是完整的Web MVC启动器会引入Tomcat这个才是要避免的。3.3 写第一个路由配置并启动验证依赖加好之后写一个最简单的路由配置。在application.yml里server: port: 8080 spring: application: name: gateway-demo cloud: gateway: routes: - id: baidu_route uri: https://www.example.com predicates: - Path/example/** filters: - StripPrefix1 management: endpoints: web: exposure: include: gateway这段配置的意思是所有路径以/example/开头的请求转发到https://www.example.com并且在转发前去掉第一层路径StripPrefix1。比如你访问http://localhost:8080/example/abc实际会被转发到https://www.example.com/abc。启动类就是最普通的Spring Boot启动类SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }启动之后先看控制台有没有报错。如果看到Netty started on port 8080说明启动成功。然后访问http://localhost:8080/actuator/gateway/routes应该能看到你配置的路由信息包括id、uri、predicates等。这个端点非常有用后面调试路由匹配问题全靠它。再访问http://localhost:8080/example/abc如果配置正确你会看到目标站点的响应内容。如果返回404或者502往下看排查部分。3.4 用代码方式配置路由Java DSL除了yml配置Gateway也支持用Java代码配置路由。这种方式的好处是可以用编程逻辑动态生成路由比如从数据库读取路由配置。Configuration public class GatewayConfig { Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route(code_route, r - r .path(/code/**) .filters(f - f.stripPrefix(1) .addRequestHeader(X-Gateway, demo)) .uri(https://www.example.com)) .build(); } }这段代码和上面的yml配置效果一样但多了个加请求头的操作。代码方式适合路由数量少、需要动态逻辑的场景yml方式适合路由固定、配置集中的场景。实际项目里两种可以混用代码配置的路由和yml配置的路由会合并生效。注意代码方式配置的路由id必须唯一否则会覆盖。如果你同时用yml和代码配置了相同id的路由后加载的会覆盖先加载的这个行为要心里有数。4. 路由匹配与断言的实战细节4.1 常用断言工厂逐个拆解Gateway内置的断言工厂有十几种我挑最常用的几个讲透剩下的你用到时查文档即可。Path断言是最常用的格式是Path/api/**。它基于Spring的PathPattern匹配支持*匹配单层路径、**匹配多层路径、?匹配单个字符。注意/api/*只能匹配/api/abc不能匹配/api/abc/def要匹配多层必须用/api/**。Method断言按HTTP方法匹配比如MethodGET,POST。这个在需要区分读写接口时很有用比如GET请求走缓存服务POST请求走写入服务。Header断言按请求头匹配格式是HeaderX-Request-Id, \d第二个参数是正则。这个常用于灰度发布比如带特定header的请求走新版本服务。Query断言按查询参数匹配格式是Querytoken, .要求请求必须带token参数。After/Before/Between断言按时间匹配常用于限时活动或者定时切换。格式是After2024-01-01T00:00:0008:00[Asia/Shanghai]注意时区要写清楚。多个断言组合时它们是“与”关系。比如predicates: - Path/api/** - MethodGET - HeaderX-App-Version, v2这个路由只匹配“路径以/api/开头 且 方法是GET 且 请求头X-App-Version是v2”的请求。三个条件缺一不可。4.2 断言匹配的优先级与顺序陷阱这里有个很多人踩过的坑当多个路由都能匹配同一个请求时Gateway按路由的order值决定优先级order小的优先。如果你没设order默认是0那么多个order相同的路由之间匹配顺序是不确定的取决于加载顺序。我遇到过这样一个场景配置了两条路由一条Path/api/**转发到服务A一条Path/api/user/**转发到服务B。本意是用户相关接口走B其他走A。但因为两条路由order都是0实际请求/api/user/1时可能匹配到A导致404。解决办法是给更具体的路由设更小的orderroutes: - id: user_route uri: http://service-b order: -1 predicates: - Path/api/user/** - id: api_route uri: http://service-a order: 0 predicates: - Path/api/**这样/api/user/**会优先匹配user_route其他/api/**才落到api_route。原则就是越具体的路由order越小。4.3 路径重写与StripPrefix的正确用法StripPrefix过滤器用来去掉路径的前N层。比如前端请求/api/order/list后端服务实际接口是/order/list那就需要StripPrefix1去掉/api这一层。但StripPrefix有个容易搞混的地方它数的是“路径段”而不是“字符”。/api/order/list按/分割是[api, order, list]StripPrefix1去掉第一段api剩下/order/list。如果路径是/api/v1/order/listStripPrefix1去掉api得到/v1/order/listStripPrefix2去掉api和v1得到/order/list。除了StripPrefix还有RewritePath过滤器可以做更灵活的重写它支持正则替换filters: - RewritePath/api/(?segment.*), /$\{segment}这个效果和StripPrefix1类似但用正则更灵活比如你可以把/api/v1/xxx重写成/xxx把版本号也去掉。实操心得路径重写是排查“404但目标服务明明有这个接口”问题的第一怀疑对象。我建议在开发阶段打开Gateway的debug日志把logging.level.org.springframework.cloud.gatewayDEBUG加上这样每次请求匹配了哪条路由、经过哪些过滤器、最终转发到什么URI日志里都清清楚楚。5. 过滤器机制与自定义过滤器入门5.1 Gateway Filter和Global Filter的区别Gateway Filter是绑定在特定路由上的只对该路由生效。比如你在路由A上配了AddRequestHeader那只有走路由A的请求会加这个头走路由B的不会。Global Filter是全局的所有路由的请求都会经过它典型的内置Global Filter包括NettyRoutingFilter负责实际转发、NettyWriteResponseFilter负责写响应等。自定义过滤器时选哪种取决于你的需求。如果是某个业务特有的处理逻辑用Gateway Filter如果是全站通用的鉴权、日志、限流用Global Filter。5.2 手写一个Global Filter记录请求耗时我写一个最实用的Global Filter记录每个请求的耗时并打印日志。这个在排查慢请求时非常有用。Component public class RequestTimeFilter implements GlobalFilter, Ordered { private static final Logger log LoggerFactory.getLogger(RequestTimeFilter.class); Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { long start System.currentTimeMillis(); String path exchange.getRequest().getURI().getPath(); return chain.filter(exchange).then(Mono.fromRunnable(() - { long cost System.currentTimeMillis() - start; log.info(请求路径: {}, 耗时: {}ms, path, cost); })); } Override public int getOrder() { return 100; } }这段代码有几个关键点要理解。第一chain.filter(exchange)返回的是MonoVoid表示异步的过滤链执行。第二.then(Mono.fromRunnable(...))表示在过滤链执行完成之后也就是响应返回之后再执行日志逻辑这就是post阶段。第三getOrder()返回100值越小越先执行我设100是为了让它尽量靠后这样统计的耗时包含了前面所有过滤器的处理时间。注意在响应式编程里不能用ThreadLocal存上下文因为一个请求可能在不同线程上执行。如果需要在过滤器之间传递数据用exchange.getAttributes()它底层是个ConcurrentHashMap线程安全。5.3 过滤器的执行顺序与常见误区过滤器的执行顺序由order决定但这里有个容易误解的地方pre阶段按order升序执行post阶段按order降序执行。也就是说order最小的过滤器pre最先执行、post最后执行形成一个“洋葱模型”。这个模型的好处是外层过滤器可以在内层过滤器处理完之后再做收尾工作。比如鉴权过滤器order-100日志过滤器order100那么执行顺序是鉴权pre → 日志pre → 转发 → 日志post → 鉴权post。鉴权过滤器可以在post阶段检查响应状态码如果发现异常可以统一处理。常见误区是有人以为order只影响pre阶段post阶段还是按order升序结果发现日志顺序不对。记住“洋葱模型”这个比喻就不会错。6. 常见问题与排查技巧实录6.1 启动报错与依赖冲突速查环境搭建阶段最容易遇到的就是启动报错。我整理了一份常见错误和对应原因报错信息大概率原因解决办法NoSuchMethodError版本不兼容检查Spring Boot/Cloud/Gateway版本矩阵Port already in use端口被占用换端口或杀掉占用进程Failed to configure a DataSource误引入了数据源依赖排除spring-boot-starter-jdbc等Spring MVC found on classpath误引入spring-boot-starter-web移除web依赖Unable to find RoutePredicateFactory断言工厂名拼写错误检查yml里断言名称大小写其中“Spring MVC found on classpath”这个错误最典型。Gateway启动时会检查classpath里有没有Spring MVC如果有就会报这个错并拒绝启动。这是Gateway的自我保护机制因为WebFlux和Spring MVC不能共存。解决办法就是找到哪个依赖传递引入了spring-boot-starter-web用mvn dependency:tree排查然后exclude掉。6.2 路由不生效的排查思路路由配了但访问404这是新手最常遇到的问题。我一般按这个顺序排查看actuator端点访问/actuator/gateway/routes确认路由是否加载成功。如果这里没有你的路由说明配置没被读取检查yml缩进和配置前缀。看断言是否匹配在actuator端点里能看到每条路由的predicates对照你的请求路径、方法、header看是否满足。看order是否被抢占如果有多条路由检查是不是被order更小的路由先匹配走了。看路径重写如果匹配上了但目标服务返回404检查StripPrefix或RewritePath是否把路径改错了。开debug日志加logging.level.org.springframework.cloud.gatewayDEBUG日志会打印每次请求的路由匹配过程。我踩过最坑的一次是yml里predicates写成了predicate少了个s配置没生效但也不报错排查了半天。所以配置写完一定要对着actuator端点确认一遍。6.3 跨域配置不生效的处理前后端分离项目里跨域是绕不开的。Gateway作为入口跨域配置应该放在网关层统一处理而不是每个微服务各自配。spring: cloud: gateway: globalcors: cors-configurations: [/**]: allowedOriginPatterns: * allowedMethods: * allowedHeaders: * allowCredentials: true maxAge: 3600这里有个坑allowedOrigins设为*时allowCredentials不能为true否则浏览器会拒绝。要用allowedOriginPatterns代替allowedOrigins这样即使allowCredentials为true也能用通配符。另一个坑是如果目标服务自己也配了CORS可能会出现响应头重复的问题浏览器同样会报错。解决办法是在Gateway的CORS配置里加上DedupeResponseHeaderAccess-Control-Allow-Origin Access-Control-Allow-Credentials过滤器把重复的头去重。实操心得跨域问题排查时先看浏览器Network面板里的响应头确认Access-Control-Allow-Origin是否存在、值是否正确。如果是预检请求OPTIONS失败检查Gateway有没有放行OPTIONS方法。我一般会在CORS配置里显式加上OPTIONS避免预检被拦截。7. 环境搭建完成后的下一步环境跑通、第一个路由转发成功之后其实Gateway的大门才刚刚打开。接下来可以深入的方向有几个一是动态路由把路由配置放到数据库或配置中心通过actuator端点刷新不用重启就能改路由二是限流用RequestRateLimiter过滤器配合Redis做令牌桶限流三是鉴权集成JWT在网关层统一校验token四是灰度发布用Header断言或权重断言把部分流量导到新版本。我个人建议的进阶顺序是先把动态路由搞定因为这是生产环境的基本要求总不能每次改路由都重启网关。然后是限流和鉴权这两个是安全相关的优先级也高。灰度发布可以放到最后等业务真的有这个需求再做。最后分享一个我自己的习惯每配一条新路由我都会先在actuator端点确认它加载成功然后用curl发一个测试请求验证转发路径最后再看目标服务的日志确认请求真的到了。这三步走下来基本不会出现“配置了但没生效”的情况。环境搭建这件事慢就是快把基础打牢后面遇到问题才不会慌。

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

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

免费获取报价 →
↑