☰
TongWeb上部署Spring Cloud微服务:Nacos注册与Gateway路由避坑指南
2026/9/29 17:59:30 网站建设 项目流程

1. 项目缘起与整体架构拆解

TongWeb 上跑微服务,这事我在几个项目里都碰过。第一次接触的人往往会觉得别扭——TongWeb 是国产应用服务器,Spring Cloud 那一套是围绕 Tomcat、Netty 这些组件长出来的生态,两者凑一块,注册和路由这两块最容易出问题。这篇就把我在 TongWeb 上部署 Spring Cloud 微服务、接 Nacos 做注册配置、用 Spring Cloud Gateway 做网关路由的完整过程拆开讲,重点放在那些文档里不写、但实际一跑就报错的地方。

先说清楚这套组合到底解决什么问题。TongWeb 作为应用服务器,负责承载每个微服务实例的 Servlet 容器运行环境;Nacos 同时承担注册中心和配置中心的角色,服务实例启动后把自己的地址注册进去,网关从 Nacos 拉取服务列表做动态路由;Spring Cloud Gateway 作为统一入口,把外部请求按规则转发到后端各个微服务。这套架构的价值在于:服务上下线对网关透明,配置变更不用重启,运维侧只需要关注 Nacos 和网关两个点。

适合谁来参考?如果你手上是标准的 Tomcat 加 Nacos 加 Gateway 组合,这篇里大部分内容你也能用,但 TongWeb 特有的类加载机制、端口管理、部署包结构会带来额外坑点,我会单独标出来。如果你完全没接触过 Nacos 和 Gateway,建议先把 Nacos 装起来跑通单机模式,再回来看 TongWeb 集成的部分,不然问题会叠在一起,排查起来很痛苦。

整体设计上,我采用的方案是:Nacos 独立部署(不用内嵌),TongWeb 以独立实例方式部署每个微服务,Gateway 也跑在 TongWeb 上。为什么不把 Nacos 内嵌到应用里?因为内嵌模式在 TongWeb 这种非标准 Spring Boot 内嵌容器的环境里,生命周期管理会很乱,Nacos 的启动和 TongWeb 的启动顺序、类加载器隔离都会互相干扰。独立部署虽然多了一个进程,但边界清晰,出问题好定位。

提示:TongWeb 的版本差异对类加载影响很大,建议先确认你用的是哪个大版本,不同版本对spring-boot-loader和ClassLoader的处理策略不一样,后面配置会提到具体差异。

2. Nacos 注册中心在 TongWeb 环境下的接入要点

2.1 为什么 TongWeb 上注册 Nacos 容易失败

标准 Spring Boot 应用打成的可执行 jar 里,Spring Boot 有自己的LaunchedURLClassLoader,Nacos 客户端在启动时通过这个类加载器加载配置和注册逻辑,一切顺理成章。但 TongWeb 部署微服务时,通常是把应用打成 war 包或者以 exploded 目录形式部署,类加载器变成了 TongWeb 自己的WebAppClassLoader。Nacos 客户端里有些类(比如com.alibaba.nacos.client.naming下的部分实现)依赖线程上下文类加载器(TCCL)去加载 SPI 实现,如果 TCCL 指向的不是应用类加载器,就会报ClassNotFoundException或者 SPI 找不到实现。

我实测下来,最常见的报错是启动时日志里出现NacosException: failed to req API后面跟着一长串类加载相关的堆栈,或者干脆注册不上但也不报错,Nacos 控制台里看不到实例。这时候不要急着去改 Nacos 服务端配置,先确认客户端这边的类加载器问题。

2.2 注册配置的关键参数与实操

在 TongWeb 上接入 Nacos,bootstrap.yml或者application.yml里的配置和标准 Spring Boot 基本一致,但有几个参数必须显式指定,不能靠默认值。

spring: application: name: order-service cloud: nacos: discovery: server-addr: 192.168.1.100:8848 namespace: dev group: DEFAULT_GROUP register-enabled: true heart-beat-interval: 5000 heart-beat-timeout: 15000 ip-delete-timeout: 30000

这里heart-beat-interval和heart-beat-timeout我特意调过。默认值在 TongWeb 环境下有时候会因为容器线程池调度延迟导致心跳超时,实例被误判下线。把心跳间隔从默认的 5 秒保持,但超时从 15 秒放宽到 15 秒以上(我用的 15000 毫秒),给 TongWeb 的线程调度留出余量。ip-delete-timeout设成 30000,意思是实例下线后 30 秒才从 Nacos 列表里彻底删除,避免网关侧还在转发已经停掉的实例。

namespace这个参数特别容易踩坑。Nacos 的 namespace 用的是命名空间 ID,不是命名空间名称。你在控制台创建命名空间时,它会生成一个 UUID 作为 ID,配置里必须填这个 UUID,填名称是无效的,而且不会报错,只是注册到默认的 public 命名空间去了。我见过有人排查了半天为什么实例不在预期命名空间,最后发现就是填了名称而不是 ID。

2.3 TongWeb 类加载器隔离的绕行方案

如果确认是类加载器导致 Nacos 客户端初始化失败,有两个处理方向。第一个方向是在 TongWeb 的部署描述里调整类加载策略,把 Nacos 相关的包设置为父类加载器优先或者应用类加载器优先,具体在 TongWeb 控制台的类加载配置里,把com.alibaba.nacos和com.alibaba.spring这两个包路径加到应用优先加载的列表里。第二个方向是在应用启动时手动设置 TCCL,在main方法或者ServletContextListener的contextInitialized里加一行:

Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());

这行代码的作用是确保 Nacos 客户端在初始化时,TCCL 指向应用自己的类加载器,SPI 能正确找到实现类。我一般两个方向都做,双保险。实测下来,只做其中一个有时候能跑通,但换个 TongWeb 小版本又不行了,两个都做稳定性明显提升。

注意:改类加载策略后一定要清 TongWeb 的临时目录和缓存,TongWeb 会缓存已加载的类,不清缓存改了也不生效。这个坑我踩过,改完配置重启没效果,清缓存后才正常。

3. Spring Cloud Gateway 路由配置与 TongWeb 适配

3.1 网关路由的核心配置逻辑

Gateway 跑在 TongWeb 上,路由配置本身和标准环境没区别,核心是spring.cloud.gateway.routes这一段。但 TongWeb 环境下,Gateway 的RoutePredicateHandlerMapping依赖的DispatcherHandler在初始化时,如果 TongWeb 的 Servlet 容器对异步请求支持不完整,会导致路由匹配上了但转发不出去,表现为请求卡住然后超时。

我的路由配置是这样的:

spring: cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true routes: - id: order-service-route uri: lb://order-service predicates: - Path=/api/order/** filters: - StripPrefix=1 - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 100 redis-rate-limiter.burstCapacity: 200

lb://order-service这个写法表示从 Nacos 注册中心按服务名做负载均衡,StripPrefix=1去掉路径的第一段前缀,这样/api/order/create转发到 order-service 时就变成/order/create。lower-case-service-id设成 true 是因为 Nacos 里服务名默认是大写,Gateway 做服务发现时如果大小写不匹配会找不到实例,这个参数在 TongWeb 环境下尤其要注意,因为 TongWeb 对 URL 大小写的处理有时候和标准容器不一致。

3.2 动态路由刷新的实现方式

Gateway 从 Nacos 拉取服务列表做动态路由,靠的是NacosDiscoveryClient定时拉取加事件通知。默认情况下,Gateway 启动时拉一次服务列表,之后每隔 30 秒(spring.cloud.nacos.discovery.watch-delay控制)刷新一次。但如果你希望服务上下线后网关立刻感知,可以把这个值调小,比如 5000 毫秒。

不过调小有个副作用:TongWeb 环境下,频繁的服务列表刷新会触发 Gateway 的RefreshRoutesEvent,每次刷新都会重建路由定义。如果 TongWeb 的线程池配置不够,刷新期间新请求进来可能会短暂路由不到。我的做法是保持 30 秒默认值,但在 Nacos 服务端把实例的健康检查间隔调短,让不健康的实例更快被剔除,这样网关侧即使 30 秒才刷新一次,拉到的列表也是相对准确的。

3.3 TongWeb 上 Gateway 的异步请求处理

Gateway 基于 Reactor Netty 做底层通信,但跑在 TongWeb 上时,TongWeb 的 Servlet 容器会接管请求的接收和响应。这里有个关键点:TongWeb 对 Servlet 3.1 异步请求的支持程度决定了 Gateway 能不能正常工作。如果 TongWeb 版本较老,异步支持不完整,Gateway 的NettyRoutingFilter在转发请求时会阻塞 TongWeb 的工作线程,并发一上来就卡死。

判断方法很简单:压测一下,看 TongWeb 的线程池活跃线程数是不是随着并发上升一直涨,涨到最大值后请求开始排队。如果是,说明异步没生效。解决办法是在 TongWeb 的配置里开启异步支持,并且把 Gateway 的spring.cloud.gateway.httpclient相关超时参数调大:

spring: cloud: gateway: httpclient: connect-timeout: 5000 response-timeout: 30s pool: max-connections: 500 max-idle-time: 60s

max-connections设成 500 是根据后端微服务的实例数和每个实例能承受的并发估算的。假设 order-service 有 5 个实例,每个实例能扛 100 并发,那网关到后端的连接池至少要有 500 个连接才够用。这个数不是拍脑袋来的,是压测时观察连接等待时间调出来的。

4. 配置中心动态刷新在 TongWeb 上的落地

4.1 Nacos 配置动态刷新的触发机制

Nacos 配置中心动态刷新的原理是:客户端启动时向 Nacos 服务端注册一个长轮询请求,服务端配置有变更时,长轮询返回变更的配置项,客户端收到后触发RefreshEvent,Spring Cloud 的ContextRefresher重新加载@RefreshScope标注的 Bean。在 TongWeb 环境下,这个链路能不能走通,取决于 TongWeb 对长轮询连接的处理。

TongWeb 默认对长连接有超时限制,如果长轮询请求被 TongWeb 提前断掉,客户端会不断重连长轮询,配置变更通知就会延迟甚至丢失。我遇到过一次,改了 Nacos 里的配置,等了五分钟应用都没刷新,查日志发现长轮询请求每隔 30 秒就被 TongWeb 断一次,客户端重连后重新发起长轮询,但变更通知在断连期间丢了。

处理办法是在 TongWeb 的连接器配置里,把connectionTimeout和keepAliveTimeout调大,至少大于 Nacos 长轮询的超时时间(默认 30 秒)。我一般设成 65 秒,给长轮询留出足够余量。

4.2 @RefreshScope 在 TongWeb 上的使用禁忌

@RefreshScope这个注解在 TongWeb 上有个坑:它依赖 CGLIB 做代理,而 TongWeb 的类加载器对 CGLIB 生成的代理类加载有时候会出问题,表现为配置刷新后,Bean 里的值没变,但日志显示RefreshEvent已经触发了。排查下来是代理类没被正确替换。

我的经验是,在 TongWeb 上尽量把需要动态刷新的配置集中到一个@ConfigurationProperties类里,而不是散落在各个@Value注解上。@ConfigurationProperties的刷新机制不依赖 CGLIB 代理,走的是ConfigurationPropertiesRebinder,在 TongWeb 上稳定性好很多。

@Component @ConfigurationProperties(prefix = "order.config") @RefreshScope public class OrderConfig { private int timeout; private String endpoint; // getter setter }

这样配置刷新时,OrderConfig这个 Bean 会被重新绑定,所有注入它的地方拿到的都是新值。比在每个@Value上加@RefreshScope要可靠。

4.3 配置刷新的验证与排查

配置刷新有没有生效,不能只看日志。我的验证方法是:在应用里加一个/config/check接口,返回当前生效的配置值,改完 Nacos 配置后调这个接口看返回值变没变。如果没变,按这个顺序排查:先看 Nacos 控制台的配置有没有推送成功(看配置的 MD5 和客户端拉取的 MD5 是否一致),再看应用日志里有没有RefreshEvent触发记录,最后看 TongWeb 的长连接有没有被断。

提示:Nacos 配置的dataId和group必须和客户端配置完全一致,包括大小写。TongWeb 环境下,如果dataId里带了文件扩展名(比如order-service.yaml),而客户端配置的是order-service,是拉不到配置的,而且不报错。

5. 常见问题排查与避坑经验实录

5.1 注册不上 Nacos 的排查路径

注册失败是最常见的问题,表现是应用启动日志里没有报错,但 Nacos 控制台的服务列表里看不到实例。排查按这个顺序走:

排查项检查方法常见原因
网络连通性telnet Nacos地址 8848防火墙、安全组拦截
命名空间对比配置里的 namespace 和 Nacos 控制台的 ID填了名称而非 ID
分组对比 group 配置默认 DEFAULT_GROUP 被改过
类加载器看启动日志有无 SPI 相关异常TongWeb 类加载隔离
心跳看 Nacos 控制台实例的健康状态心跳超时被剔除

我遇到最多的是命名空间填错和类加载器问题。命名空间那个坑前面说了,类加载器问题有个快速验证方法:在应用启动后,手动调一下 Nacos 客户端的注册接口,如果手动调能注册上,说明是自动注册的类加载器问题,如果手动调也注册不上,那就是网络或配置问题。

5.2 网关路由 404 与 503 的区分处理

Gateway 报 404 和 503 是两回事。404 通常是路由没匹配上,检查predicates里的Path表达式和实际请求路径是否匹配,注意StripPrefix的数值对不对。503 是路由匹配上了但后端没有可用实例,检查 Nacos 里服务实例是否健康、服务名大小写是否一致。

TongWeb 环境下有个特殊情况:如果 TongWeb 的上下文路径(context path)不是根路径,Gateway 收到的请求路径会带上上下文路径,导致Path表达式匹配不上。比如 TongWeb 里应用部署在/gateway下,实际请求是/gateway/api/order/create,但路由配置里写的是/api/order/**,就匹配不上。解决办法是在 TongWeb 里把 Gateway 应用的上下文路径设为根路径,或者在路由Path里加上上下文路径前缀。

5.3 配置刷新不生效的速查表

现象可能原因处理
改配置后无任何反应长轮询被断调大 TongWeb 连接超时
日志有 RefreshEvent 但值没变CGLIB 代理问题改用 @ConfigurationProperties
部分配置刷新部分不刷新@RefreshScope 作用域问题统一用 @ConfigurationProperties
刷新后应用报错新配置值不合法加配置校验逻辑

这张表是我踩坑踩出来的,基本上按这个顺序查,90% 的配置刷新问题都能定位。

5.4 实操心得与独家技巧

第一个心得:TongWeb 上部署微服务,日志级别一定要调细。Nacos 客户端和 Gateway 的日志默认是 INFO,很多类加载和路由匹配的细节看不到。把com.alibaba.nacos和org.springframework.cloud.gateway的日志级别调到 DEBUG,启动和运行时的关键路径都能看到,排查效率翻倍。

第二个心得:TongWeb 的临时目录和缓存目录要定期清理。TongWeb 会缓存已加载的类和 JSP 编译结果,微服务频繁更新部署时,旧缓存不清会导致新代码不生效或者类冲突。我一般在部署脚本里加一步清缓存的操作,部署前先停应用、清缓存、再启动。

第三个心得:Nacos 的命名空间规划要提前做。开发、测试、生产用不同的命名空间,不要混在 public 里。TongWeb 环境下,不同环境的微服务如果注册到同一个命名空间,网关路由会串,测试环境的请求可能被转发到生产实例上。这个不是技术问题,是管理问题,但一旦出问题就是大问题。

6. 部署流程的完整复现步骤

6.1 环境准备与 Nacos 安装

Nacos 的安装我推荐用单机模式先跑通,生产环境再考虑集群。下载 Nacos 的压缩包,解压后进bin目录,Linux 下执行sh startup.sh -m standalone,Windows 下执行startup.cmd -m standalone。启动后访问http://localhost:8848/nacos,默认账号密码都是 nacos。

Nacos 的数据库配置,默认用的是内嵌的 Derby,生产环境建议换成 MySQL 或者达梦。换数据库要改conf/application.properties里的spring.datasource相关配置,并且把conf目录下的mysql-schema.sql或者对应数据库的建表脚本执行一遍。达梦的话,Nacos 官方没有直接提供建表脚本,需要根据 MySQL 脚本做语法转换,主要是AUTO_INCREMENT改成达梦的IDENTITY,ENGINE=InnoDB去掉。

6.2 TongWeb 上部署微服务的包结构

TongWeb 部署微服务,我建议打成 war 包,而不是可执行 jar。war 包的结构里,WEB-INF/classes放应用的类和配置文件,WEB-INF/lib放依赖 jar。Nacos 客户端和 Spring Cloud 相关的 jar 都放在WEB-INF/lib下,这样 TongWeb 的类加载器能正确加载。

打包时注意排除 Spring Boot 的内嵌容器依赖,因为 TongWeb 已经提供了 Servlet 容器。在pom.xml里把spring-boot-starter-tomcat的 scope 设为provided,同时加上spring-boot-starter-web但排除内嵌 Tomcat。

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency>

启动类要继承SpringBootServletInitializer并重写configure方法,这样 TongWeb 启动时才能正确初始化 Spring 上下文。

6.3 网关路由的联调验证

部署完微服务和网关后,联调验证按这个顺序来:先确认每个微服务在 Nacos 控制台里是健康状态,再确认网关能拉到服务列表(看网关日志里有没有NacosDiscoveryClient拉取记录),最后用 curl 或 Postman 调网关的接口,看能不能转发到后端。

调网关接口时,如果返回 503,先直接调后端微服务的接口,确认后端本身是通的,再查网关到后端的路由。如果返回 404,检查请求路径和路由Path表达式。如果返回 200 但响应内容不对,检查StripPrefix和RewritePath过滤器配置。

注意:TongWeb 环境下,网关的server.port配置可能被 TongWeb 的端口配置覆盖。如果网关启动后监听的端口和你配置的不一样,去 TongWeb 的应用配置里看端口设置,TongWeb 的端口优先级高于 Spring Boot 的server.port。

7. 性能调优与稳定性保障

7.1 TongWeb 线程池与 Gateway 并发匹配

TongWeb 的线程池配置直接影响 Gateway 的并发处理能力。TongWeb 默认的线程池最大线程数通常在 200 左右,如果 Gateway 的并发请求数超过这个值,请求会排队。我的做法是把 TongWeb 的最大线程数调到 500,同时把 Gateway 的httpclient.pool.max-connections也调到 500,两边匹配。

但线程数不是越大越好。线程数过大,上下文切换开销增加,反而降低吞吐。我一般通过压测找拐点:逐步增加并发数,观察吞吐量和响应时间,吞吐量不再上升而响应时间开始明显增加的那个点,就是合适的线程数。

7.2 Nacos 客户端心跳与 TongWeb 线程调度的协调

Nacos 客户端的心跳是定时任务,默认用ScheduledExecutorService调度。TongWeb 环境下,如果 TongWeb 的线程池被业务请求占满,心跳任务的线程可能被延迟调度,导致心跳超时。解决办法是给 Nacos 客户端的心跳任务单独配一个线程池,不跟业务线程池共用。

在application.yml里可以配:

spring: cloud: nacos: discovery: heart-beat-interval: 5000 heart-beat-timeout: 15000

同时在代码里初始化 Nacos 客户端时,传入自定义的ExecutorService。这个在 Spring Cloud Alibaba 的 Nacos 自动配置里可以覆盖,具体是定义一个NacosDiscoveryProperties的 Bean,设置executor属性。

7.3 配置刷新的性能影响

配置刷新会触发 Spring 上下文的RefreshEvent,重新加载@RefreshScope的 Bean。如果刷新的配置项很多,或者@RefreshScope的 Bean 很多,刷新过程会占用较多 CPU 和内存。TongWeb 环境下,刷新期间如果有大量请求进来,可能会因为上下文重建导致短暂的服务不可用。

我的优化做法是:把配置刷新集中在低峰期做,或者用 Nacos 的灰度发布功能,先推送到部分实例验证,再全量推送。另外,@RefreshScope的 Bean 尽量少,只把真正需要动态刷新的配置放进去,其他配置用@ConfigurationProperties但不加@RefreshScope,这样刷新时只重建必要的 Bean。

8. 个人实操体会与后续扩展方向

这套 TongWeb 加 Nacos 加 Gateway 的组合,我在三个项目里落地过,每次都会遇到新问题,但核心的坑就那几个:类加载器、长连接超时、命名空间配置、路由路径匹配。把这几个点吃透,剩下的就是熟练度问题。

我个人在实际操作中的体会是,TongWeb 环境下排查问题,日志是第一手资料,但日志级别要调对。Nacos 和 Gateway 的 DEBUG 日志能告诉你客户端在做什么、路由匹配到了哪条规则、配置刷新有没有触发。没有这些日志,排查就是盲人摸象。

最后再分享一个小技巧:TongWeb 的类加载器问题,如果实在搞不定,可以试试把 Nacos 客户端和 Spring Cloud 相关的 jar 包放到 TongWeb 的公共类加载器路径下(通常是lib目录),让 TongWeb 用父类加载器加载这些包。这样能绕过大部分类加载隔离问题,但代价是这些包不能随应用单独升级,需要权衡。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询