资讯动态

Spring Cloud Gateway + Nacos 微服务全链路搭建实战

发布时间:2026/10/22 18:29:18 来源:尧图企业网站定制
说句实话微服务这套东西网上教程多得很但大部分都停在“给你一段配置、让你能启动”的程度。真正从零开始把网关、注册中心、服务发现、服务间调用全部串起来中间有多少版本坑、配置坑很少有人讲透。这篇文章我不打算复述官方文档而是把我自己搭建 Spring Cloud Gateway Nacos 全链路的过程完整记录下来包括为什么这么选型、每一步的配置意图、以及我在实测中踩过的坑。内容比较多建议先收藏再跟着一步步做。1. 为什么全链路要选 Gateway Nacos 这套组合1.1 微服务拆分后两个最核心的问题单体应用时代所有请求都走同一个入口方法之间直接调用根本不用考虑“服务在哪里”。一旦拆成微服务第一件麻烦事就是服务地址怎么管理。订单服务要调用用户服务用户服务可能部署了三台机器地址还是动态变化的总不能把IP写死在配置文件里。这时候就需要一个注册中心让每个服务启动时把自己的地址“报备”上去调用方再去注册中心查询。第二件麻烦事是统一入口。几十个服务各开各的端口客户端不可能一个个记也不安全。这时候需要一个网关把所有外部请求收口到一个入口再按某种规则转发到后面的服务。网关还能顺手做鉴权、限流、请求头处理这些事情。这俩问题不解决微服务就是“拆了但没完全拆”运行起来乱七八糟。Spring Cloud Gateway 解决入口问题Nacos 解决服务发现和服务配置问题两者配合恰好把微服务最基础的地基打牢。1.2 Gateway、Zuul、Nginx为什么我最终选了 Gateway很多刚开始接触微服务的人会纠结网关选型我当时也纠结过。Zuul 1.x 是 Servlet 阻塞模型每个请求占用一个线程网关这种高并发入口用阻塞模型很容易把线程池打满Nginx 本身很强大但它不是 Java 生态的做鉴权、动态路由都要写 Lua 脚本维护成本高而且和 Spring Cloud 的服务发现体系集成不如原生组件自然。Spring Cloud Gateway 走的是WebFlux Netty 非阻塞模型抗并发能力强并且它本身就是 Spring Cloud 生态里的亲儿子可以非常自然地通过lb://协议和注册中心联动实现从“路由”到“具体服务实例”的动态解析。对于以 Java 为主的技术团队这套链路最顺、最不容易出幺蛾子。1.3 Nacos 和 Eureka、Consul 的差异化定位注册中心这块老项目里 Eureka 存量很大但 Spring Cloud 2020 之后 Eureka 进入维护模式很多团队都在迁移。Consul 也很好但部署和运维比 Nacos 稍微重。Nacos 对中文社区友好控制台用起来直观而且它不只是注册中心还兼任配置中心一个组件解决两个问题。更关键的是 Nacos 提供了命名空间、分组、集群这些概念可以在一个 Nacos 集群里隔离多个环境比如测试环境、预发环境。这套能力在后续做环境隔离和灰度发布时非常有用前期可能感觉不到等团队规模上来之后会真香。2. 环境准备版本选型与基础工程骨架2.1 版本对应关系是第一个大坑如果你直接上网随便找一套版本就开始搭我基本可以断定你要卡在依赖冲突上。Spring Boot、Spring Cloud、Spring Cloud Alibaba 三者有严格的版本对应关系Nacos 服务端和客户端也有兼容要求。我用的这一组版本组合经过多次验证比较稳定组件版本Spring Boot2.6.13Spring Cloud2021.0.5Spring Cloud Alibaba2021.0.5.0Nacos Server2.2.3这里解释一下背景Spring Cloud Alibaba 本身不直接依赖 Spring Boot它要基于 Spring Cloud 的版本体系来工作。2021.0.5.0 这个版本适配 Spring Cloud 2021.0.5而 Spring Cloud 2021.0.5 又对应 Spring Boot 2.6.x。只要这层对应关系乱了比如用 Spring Boot 2.7 配这套某些组件可能看着能启动实际跑起来会有隐性问题。提示如果新建项目不想踩版本坑最稳妥的做法是在pom.xml里用spring-cloud-alibaba-dependencies作为 BOM 统一管控版本业务代码里不写框架版本号只写全局依赖版本。2.2 搭建多模块 Maven 工程我习惯把工程拆成父模块 子模块这样网关和业务服务每次只编译自己那块互不影响。大概结构如下gateway-nacos-demo/ ├── pom.xml # 父工程统一版本管理 ├── gateway-server/ # Spring Cloud Gateway 模块 ├── user-service/ # 用户服务模块 └── order-service/ # 订单服务模块父工程的核心逻辑是用dependencyManagement锁定版本子模块只声明依赖不写版本号。父 pom 里大概是这样dependencyManagement dependencies dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-dependencies/artifactId version2021.0.5/version typepom/type scopeimport/scope /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-alibaba-dependencies/artifactId version2021.0.5.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement别小看这个步骤很多项目启动后出现各种莫名其妙的 jar 包冲突就是因为父工程没有做版本收敛各个子模块自己引了不同版本的依赖。2.3 网关和业务服务的公共依赖网关模块的 pom 和普通 Web 服务很不一样这里有几个关键点网关不能引入spring-boot-starter-web因为 Gateway 基于 WebFlux 运行两者冲突启动会直接报错。网关必须引入spring-cloud-starter-gateway。因为要配合 Nacos 做服务发现需要加spring-cloud-starter-alibaba-nacos-discovery。Spring Cloud 2020 之后移除了 Ribbonlb://这种服务名方式的负载均衡需要单独引入spring-cloud-starter-loadbalancer。dependencies dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-gateway/artifactId /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-loadbalancer/artifactId /dependency /dependencies业务服务模块就简单多了正常引入spring-boot-starter-web加上 Nacos 发现依赖即可。如果是服务间要调用再加spring-cloud-starter-openfeign。这块建议在搭建开始前就理清楚后面跑通全链路会省很多时间。3. Nacos 注册中心的部署与服务接入3.1 单机部署 Nacos开发阶段用什么方式启动Nacos 部署方式很灵活开发环境没必要上集群单机模式足够。最简单的启动方式是直接下载服务端包解压后执行# Linux / Mac sh startup.sh -m standalone # Windows startup.cmd -m standalone如果希望环境干净一点用 Docker 更快。我一直用 Docker 方式一条命令起一个容器随时能丢。这里给一份我实际在用的 docker-compose 配置version: 3.8 services: nacos: image: nacos/nacos-server:v2.2.3 container_name: nacos-standalone environment: - MODEstandalone - JVM_XMS256m - JVM_XMX512m ports: - 8848:8848 - 9848:9848 - 9849:9849启动之后浏览器访问http://localhost:8848/nacos默认账号密码是nacos/nacos能看到控制台就说明服务端起来了。注意如果你用的是 Nacos 2.x客户端和服务端通信除了 8848还会用到 9848 和 9849 端口的 gRPC 通信。防火墙里这两个端口一定要放行否则服务能注册但实例列表刷新不出来排查起来非常隐蔽。3.2 业务服务如何把自己注册进去服务注册的本质就是每个服务启动时主动向 Nacos 上报自己的 IP、端口、服务名同时定期发送心跳。对开发者来说这些细节都被框架封装了我们需要做的只是配置。拿 user-service 举例application.yml里这样配server: port: 8081 spring: application: name: user-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 namespace: public group: DEFAULT_GROUP注意spring.application.name必须配置这是服务的唯一标识网关和 Feign 都要靠这个名字来找服务实例。启动 user-service 之后回到 Nacos 控制台在“服务管理 → 服务列表”里就能看到user-service已经出现在列表里状态为健康。3.3 注册中心背后的工作逻辑这里用一句大白话总结注册中心的原理Nacos 就是一本服务通讯录服务启动时在通讯录上登记名字和地址请求方要找人时去通讯录里翻地址而且翻到的地址是动态更新的。调用方通常不是直连某个实例而是通过服务名去发现列表再在本地用负载均衡算法选一个。比如网关收到一个lb://user-service的路由规则它会先从 Nacos 拿到 user-service 的所有实例地址然后选一个来转发。这正是注册中心和网关联动的基础也是我们接下来要配置的落点。4. Spring Cloud Gateway 的动态路由与服务发现联动4.1 静态 URI 与 lb:// 动态转发的区别刚开始搭网关的时候最容易写成这样spring: cloud: gateway: routes: - id: user-service-route uri: http://127.0.0.1:8081 predicates: - Path/api/user/**这种写法在联调阶段没问题但有一个很致命的缺陷服务地址写死了。一旦 user-service 被扩容成多实例或者某个实例挂了路由规则并不会感知依然把请求打向那个固定地址。正确做法是使用lb://前缀加服务名spring: cloud: gateway: routes: - id: user-service-route uri: lb://user-service predicates: - Path/api/user/** filters: - StripPrefix1lb://user-service的意思就是从负载均衡器中找到名为 user-service 的服务并从中选一个可用实例。这个写法是网关和服务发现之间的桥梁也是我说“全链路搭建”时最核心的一层联动。4.2 StripPrefix 过滤器到底在干什么这个过滤器对新手来说是著名的坑源。看配置外部请求/api/user/1到网关。网关按Path/api/user/**匹配到 user-service 路由。如果没配 StripPrefix网关会把完整路径/api/user/1转发给 user-service。但 user-service 的接口可能定义在/user/{id}于是路径对不上直接 404。StripPrefix1的意思是从原路径中截掉一层前缀把/api/user/1变成/user/1再转发。这里的前缀层级和 ?Path 里的层级必须想清楚多截一层少截一层都会导致转发失败。我一般会在网关转发前用日志把实际转发的 URL 打印出来确认路径对不对再放行。4.3 全局过滤器把鉴权做在入口网关适合处理横切逻辑比如统一鉴权、记录日志、生成 traceId。Spring Cloud Gateway 提供了GlobalFilter接口实现它就能在路由转发前后加逻辑。我给出一个非常简化的全局过滤器示例Component public class AuthGlobalFilter implements GlobalFilter, Ordered { Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request exchange.getRequest(); String token request.getHeaders().getFirst(Authorization); if (token null || token.isEmpty()) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } // 这里实际项目中会解析 token取到用户ID后放入请求头向下游传递 ServerHttpRequest newRequest request.mutate() .header(X-User-Id, parseUserId(token)) .build(); return chain.filter(exchange.mutate().request(newRequest).build()); } Override public int getOrder() { return -100; } private String parseUserId(String token) { // token 解析逻辑按各自业务实现 return 10001; } }这个过滤器有几个值得注意的点getOrder()返回的值越小优先级越高负数可以确保它在内置过滤器之前执行。不要在网关做太重的业务逻辑网关应该保持轻量像用户信息查询这种操作尽量后置到业务服务。把用户ID透传到下游请求头里这个习惯从第一天就该养成后面做数据权限、审计日志都会用得上。4.4 网关侧的超时与跨域设置网关默认的 HttpClient 超时配置比较保守生产环境经常要调大一点。看个人业务情况我常用的配置是这样的spring: cloud: gateway: httpclient: connect-timeout: 1000 response-timeout: 5sconnect-timeout 指的是“建立连接”的超时单位毫秒response-timeout 指的是“拿到完整响应”的超时需要带单位。如果下游服务慢比如做报表查询超过 5 秒就要把这个值调大否则网关会先超时断开下游其实还在算。前端跨域问题也是网关场景里大概率遇到的。Gateway 这种 WebFlux 应用不能用传统的CrossOrigin注解最省事的方式是在网关统一配 CORSspring: cloud: gateway: globalcors: cors-configurations: [/**]: allowedOriginPatterns: * allowedMethods: - GET - POST - PUT - DELETE allowedHeaders: * allowCredentials: true5. 全链路请求从网关进入业务服务的完整调用链5.1 第一次验证从网关访问用户服务前面配置都写完之后开始真正验证链路。我一般会按这个顺序启动服务先启动 Nacos再启动 user-service最后启动网关。然后执行一条最简单的请求curl http://localhost:8080/api/user/1这里假设网关端口是 8080。如果 user-service 的接口是/user/{id}并且网关配置了StripPrefix1那么我们传入/api/user/1后user-service 收到的是/user/1返回用户信息链路就通了。第一次验证如果失败优先看两个地方网关日志里是否有No servers available for user-service之类的报错如果有说明 Nacos 那边没拉到实例。下游服务日志里收到的路径是什么如果收到的是/api/user/1说明 StripPrefix 层级不对。链路通了之后我强烈建议在网关里随便加一条记录访问日志的 Filter把原始路径、转发目的地、耗时打出来。后面排查问题时这份日志能帮你快速定位是网关耗时还是下游耗时。5.2 服务间调用订单服务通过 Feign 访问用户服务微服务链路里最难缠的一部分其实是“服务调服务”。网关能转发还不够业务服务之间互相调用时要走的也是服务发现 负载均衡这套机制。在订单服务里调用用户服务我用的是 OpenFeign。先加依赖dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-openfeign/artifactId /dependency然后在启动类上加EnableFeignClients最后定义一个 Feign 接口FeignClient(name user-service, path /user) public interface UserFeignClient { GetMapping(/{id}) UserVO getUserById(PathVariable(id) Long id); }这里关键点是FeignClient里的name它不需要写 URL只需要写服务名。Feign 会从 Nacos 拿到 user-service 的实例列表再通过负载均衡选择实例发起请求。我在第一次测试 OpenFeign 调用的时候踩过一个经典的坑Feign 接口里写消息的时候需要给GetMapping加PathVariable(id)ID参数名必须明确写出来否则Spring编译时参数名丢失导致报错。这个小细节在真实项目中很容易被忽略。5.3 全链路日志打通一个 traceId 串起所有服务网关入口有了服务间调用通了接下来就该考虑“全链路”这三个字真正的含义——一个请求进来从头到尾经过的所有服务、所有日志必须是同一个请求ID。我在网关全局过滤器里生成一个traceId放进请求头往下传String traceId UUID.randomUUID().toString().replace(-, ); ServerHttpRequest newRequest request.mutate() .header(X-Trace-Id, traceId) .build();下游服务在 Controller 或拦截器里读取X-Trace-Id这个请求头把它放到日志里。在 Java 里最简单的做法是用一个拦截器把它放进MDCComponent public class TraceIdInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String traceId request.getHeader(X-Trace-Id); if (traceId null || traceId.isEmpty()) { traceId UUID.randomUUID().toString().replace(-, ); } MDC.put(traceId, traceId); return true; } }日志配置里加上%X{traceId}所有日志就会自动带上这个 ID。出问题时把 traceId 拿去各个服务日志里一搜整个调用链就出来了。如果想做得更专业可以接分布式链路追踪系统比如 SkyWalking 或者 Zipkin但底层的思路和我这个简化版是一样的给请求一个唯一标识并让它穿透整个调用链。6. 运维落地中最容易被忽视的配置坑6.1 bootstrap.yml 不生效的问题很多老教程会让把 Nacos 配置写在bootstrap.yml里但在 Spring Boot 2.4 之后默认不再加载bootstrap.yml导致服务启动时完全读不到 Nacos 配置注册也失败。解决办法有两个引入spring-cloud-starter-bootstrap依赖恢复旧的行为。或者别用bootstrap.yml直接把 Nacos 连接信息放在application.yml里。我后来的习惯是注册发现配置全放application.yml避免引入额外的 bootstrap 机制。少一个依赖少一个坑排查问题也更直接。6.2 Nacos 命名空间和分组不一致导致服务找不到现象是服务在 Nacos 控制台里明明能看到但网关转发时报 503 或Unable to find instance。这大概率是命名空间或分组对不上。比如网关配置里写在namespace: public业务服务却用了新建的namespace: dev两边各注册各的互相找不到对方。排查方法也不复杂打开 Nacos 控制台分别看网关服务和目标服务各自所在的命名空间和分组把这两项对齐。凡是涉及环境隔离的场景我建议前期就把命名空间命名规则定好比如 dev、sit、prod 每个环境一个命名空间服务配置里统一写好避免人肉对配置。6.3 缺少 loadbalancer 依赖导致路由转发失败Spring Cloud 2020 之后移除了内置 Ribbon如果网关里用了lb://user-service却没有引入spring-cloud-starter-loadbalancer网关启动不会报错但一转发就报错日志会提示找不到可用实例。这个错误很容易误判因为告警信息和“服务未注册”非常像。我的排查经验是三步走先看 Nacos 服务列表是不是有健康实例再看网关是否引入了 loadbalancer 依赖最后把日志级别调到 DEBUG看网关在服务选择时到底发生了什么。三步走完问题基本定位。6.4 网关超时和重试配置要小心网关超时配置过短会误杀慢请求但无限调大又可能导致线程被慢下游拖死。更危险的是如果有重试机制一个慢请求失败后自动重试下游服务的压力会成倍放大甚至引发雪崩。我的建议是早期阶段不要急着开重试。先让请求一次失败就暴露问题把下游服务的性能和稳定性优化好之后再考虑针对幂等接口开启有限重试而且要设置全局最大重试次数。6.5 快速验证路由是否生效的小技巧最后分享一个我常用的验证技巧。网关模块引入spring-boot-starter-actuator后打开端点management: endpoints: web: exposure: include: gateway,health,info然后访问curl http://localhost:8080/actuator/gateway/routes就能看到当前网关加载的所有路由规则包括每条规则对应的谓词、过滤器、目标 URI。这个端点在我调整路由、排查转发异常时帮了大忙。配合 Gateway 的日志控制把org.springframework.cloud.gateway的日志级别调到 DEBUG几乎可以看到每一步路由匹配的过程。整套链路我前后折腾了不止一次最深的感觉就是微服务架构本身并不复杂复杂的是版本和配置里那些“差一点就是不行”的隐性约定。但只要把注册中心、网关、服务调用这三层先后跑通后面再往上加限流、熔断、分布式事务都是在已有骨架上搭积木。希望这份实践笔记能帮你少走点弯路。

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

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

免费获取报价 →
↑