云计算百科
云计算领域专业知识百科平台

Spring Cloud gateway 占满服务器cpu

查看 gateway进程状态发现 

7301  XNIO-1 I/O-2    95.3% 7298  XNIO-1 I/O-1    94.4% 两个线程占用大

  • XNIO 是 Undertow 的底层 NIO 网络库,用于非阻塞网络 IO。

  • 如果出现高 CPU,可能是因为这两个线程在处理某些 阻塞任务、超高频请求 或者 网络连接卡死但未关闭。

盲猜是Undertow导致的问题

 在pox里面果真找到了Undertow

,这时候我们看看这两个线程在干嘛

要在 jstack 里找到它们,需要把这两个十进制的 TID 转成十六进制:

printf "7301 → 0x%x\\n7298 → 0x%x\\n" 7301 7298  

使用jdk的 jstack(先保证你有,你可以点进bin里面看有没有jstack)导出dump.txt

/home/java/openJDk/jdk-17.0.15/bin/jstack 4603 > dump.txt

它会在你当前所在的目录导出一个 dump.txt

然后你就打开这个txt搜索0x1c85和0x1c82

这里是栈信息,可以分析这两个线程在干什么

"XNIO-1 I/O-2" #71 prio=5 os_prio=0 cpu=342102189.69ms elapsed=357373.87s tid=0x00007f9afd09c250 nid=0x1c85 runnable  [0x00007f9a846fd000]    java.lang.Thread.State: RUNNABLE     at org.xnio.nio.WorkerThread.run(WorkerThread.java:480)

"XNIO-1 I/O-1" #70 prio=5 os_prio=0 cpu=341952471.37ms elapsed=357373.88s tid=0x00007f9afd09b570 nid=0x1c82 runnable  [0x00007f9a847fe000]    java.lang.Thread.State: RUNNABLE     at sun.nio.ch.EPoll.wait(java.base@17.0.15/Native Method)     at sun.nio.ch.EPollSelectorImpl.doSelect(java.base@17.0.15/EPollSelectorImpl.java:118)     at sun.nio.ch.SelectorImpl.lockAndDoSelect(java.base@17.0.15/SelectorImpl.java:129)     – locked <0x00000000e18de358> (a sun.nio.ch.Util$2)     – locked <0x00000000e18de260> (a sun.nio.ch.EPollSelectorImpl)     at sun.nio.ch.SelectorImpl.select(java.base@17.0.15/SelectorImpl.java:146)     at org.xnio.nio.WorkerThread.run(WorkerThread.java:532)

可以看到

  • 线程来源是 XNIO(Undertow 的 NIO 底层) 线程名 XNIO-1 I/O-* 明确说明你在跑的是 Undertow 的 I/O 线程池,不是 Gateway 默认的 Reactor Netty。

  • 为什么会高 CPU?

    • XNIO-1 I/O-2 直接在 WorkerThread.run 主循环里,一直 RUNNABLE,说明它不停地在做 select + 事件分发,即便在空闲时也可能使用“忙轮询”(busy-spin)模式。

    • XNIO-1 I/O-1 虽然卡在了 EPoll.wait,但因为 XNIO 默认可能把 epoll 调用 timeout 设置为 0 或非常短,也会非常频繁地唤醒再 select,从而消耗大量 CPU。

用Undertow理论上是没有问题的

Undertow是一个开源的、灵活的、高性能的非阻塞性应用服务器,由JBoss提供。它可以用作嵌入式服务器,也可以用作大型项目的全功能应用服务器。Undertow的设计以提供最高的性能和最大的灵活性为主要目标,支持非阻塞性和阻塞性处理方式,可以处理十万级的并发连接。

问题可能出在你的依赖引入了spring-boot-starter-web

为什么不能引入 spring-boot-starter-web

  • spring-boot-starter-web 是基于 Servlet + Spring MVC 的栈,默认内嵌 Tomcat(或者你显式改成 Undertow)。

  • 一旦你的应用里有了 Servlet 容器(Tomcat/Undertow),Spring Boot 就会把 WebFlux(reactive)模式关闭,走 Servlet 模式。

  • 而 Spring Cloud Gateway 是基于 Reactor Netty(纯异步非阻塞)的,底层依赖 Reactor Netty 的 reactor-http-* 线程池,路由转发、过滤器链、限流等都走的异步逻辑。

  • 加了 spring-boot-starter-web 以后,网关会被迫跑到一个阻塞的 Servlet 容器里,反而引出你现在看到的 XNIO(Undertow 的底层 NIO)线程,并且所有的转发、过滤都成了阻塞调用,不仅不高效,还容易死锁、无限重试。

但是不巧的是,我的gateway的pom没有见到spring-boot-starter-web的身影,说明不是这个问题。

那没办法了,只能回归 Gateway 标配 — Reactor Netty 

移除

  • spring-boot-starter-web(如果有)

  • spring-boot-starter-undertow(如果有)

 application.yml 加上 web-application-type

在你的 application.yml 头部,加上这几行:

spring:   main:     web-application-type: reactive  

然后重新打包重新上传jar包运行

终于清爽了。

总结

当你把 Spring Cloud Gateway 部署到 Undertow(即 Servlet 容器)上,而不是走它内置的 Reactor Netty,底层就不再用“阻塞式 epoll 等待”,而是变成了 XNIO 的 忙轮询(busy-spin)模型——几乎无间断地调用 Selector.select(0) 或直接在 WorkerThread.run() 的主循环里打转。

从你贴的线程栈看:

css

"XNIO-1 I/O-2" … at org.xnio.nio.WorkerThread.run(WorkerThread.java:480) "XNIO-1 I/O-1" … at sun.nio.ch.EPoll.wait(Native Method) at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:118) at org.xnio.nio.WorkerThread.run(WorkerThread.java:532)

  • XNIO-1 I/O-2 一直在 WorkerThread.run() 的主循环里做 “收消息 → 分发 → 再收” 的工作,即便空闲也在不停循环;

  • XNIO-1 I/O-1 卡在了 EPoll.wait,但 XNIO 默认给 epoll 的 超时时间为 0(或者非常短),于是它根本不阻塞,立刻返回再下一次 select——这就相当于在“死循环”中不断地唤醒和轮询。

两个线程都处于 RUNNABLE 状态、CPU 全速运转,结果就是 100% 的占用率。


为什么 Reactor Netty 没有这个问题?

  • Reactor Netty 的 epoll/kqueue 等待是 阻塞式 的:当没有事件时,内核会把线程挂起,直到有网络可读写或超时才唤醒——因此空闲时几乎不消耗 CPU。

  • XNIO 为了极限的低延迟,默认使用 busy-spin idle strategy,牺牲 CPU 周期去换取亚微秒级的唤醒时间,但在 Gateway 这种并发不极端、事件量不算天量的场景下就显得“咄咄逼人”——不停地在空中打转。


小结

  • Undertow/XNIO:忙轮询 ⇒ 高实时性 ⇒ CPU 飙升

  • Reactor Netty:阻塞式 epoll 等待 ⇒ 低空闲消耗 ⇒ Gateway 推荐

这也是为什么官方强烈建议 Spring Cloud Gateway 一定要跑在 WebFlux + Reactor Netty 模式下:它的所有调度、限流、熔断、路由机制都是围绕 Reactor 事件循环设计的,放到 XNIO 上就会出现这种 100% CPU 的“空转”情况。

赞(0)
未经允许不得转载:网硕互联帮助中心 » Spring Cloud gateway 占满服务器cpu
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!