You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免带Netty服务端与客户端的Spring Boot应用出现CheckpointOpenResourceException

问题描述

基于Azul 21.0.2,在集成Spring WebFlux(含基于Netty的响应式Web客户端)的Spring Boot 3.2.2应用中测试Coordinated Restore at Checkpoint(CRac)功能时,执行启动命令:

java -Dspring.context.checkpoint=onRefresh -XX:CRaCCheckpointTo=./crac -jar my-app.jar

出现原生代码层面的打开文件描述符问题(timerfd、eventfd、eventpoll),导致检查点生成失败,抛出如下异常:

17:01:11.144 7742 ERROR main o.s.boot.SpringApplication - Application run failed

org.springframework.context.ApplicationContextException: Failed to take CRaC checkpoint on refresh
        at org.springframework.context.support.DefaultLifecycleProcessor$CracDelegate.checkpointRestore(DefaultLifecycleProcessor.java:534) ~[spring-context-6.1.3.jar!/:6.1.3]
        ...
Caused by: org.crac.CheckpointException: null
        at org.crac.Core$Compat.checkpointRestore(Core.java:144) ~[crac-1.4.0.jar!/:na]
        at org.crac.Core.checkpointRestore(Core.java:237) ~[crac-1.4.0.jar!/:na]
        at org.springframework.context.support.DefaultLifecycleProcessor$CracDelegate.checkpointRestore(DefaultLifecycleProcessor.java:528) ~[spring-context-6.1.3.jar!/:6.1.3]
        ... 15 common frames omitted
        Suppressed: jdk.internal.crac.mirror.impl.CheckpointOpenResourceException: FD fd=6 type=unknown path=anon_inode:[eventpoll]
                ... 17 common frames omitted
        Suppressed: jdk.internal.crac.mirror.impl.CheckpointOpenResourceException: FD fd=7 type=unknown path=anon_inode:[eventfd]
                ... 17 common frames omitted
        Suppressed: jdk.internal.crac.mirror.impl.CheckpointOpenResourceException: FD fd=10 type=unknown path=anon_inode:[timerfd]
                ... 17 common frames omitted
        ...

补充信息:

  • 已确认问题与Netty强相关,移除所有Netty依赖/代码后检查点可正常生成。
  • 同时使用WebFlux服务端和WebClient时会触发该问题,应用还集成了Spring Data Redis Reactive(Lettuce)、Spring Kafka、Spring GraphQL,采用Kotlin协程开发。

解决方案与问题分析

问题本质

这不是Spring框架的Bug,而是Netty在CRac场景下的资源生命周期管理问题:Netty的EventLoopGroup(包括服务端WebFlux的EventLoop和WebClient的EventLoop)会提前初始化并持有原生文件描述符(eventpoll、eventfd、timerfd属于Netty NIO事件循环的核心资源),而Spring在onRefresh阶段触发检查点时,这些资源还未被正确关闭/暂停,导致CRac检测到打开的原生资源并抛出异常。

分步解决方法

1. 延迟WebClient的EventLoop初始化

WebClient默认会提前创建自己的EventLoopGroup,在检查点生成前就持有了打开的文件描述符。可以通过配置让WebClient的EventLoopGroup在检查点之后再初始化:

@Bean
fun webClient(): WebClient {
    return WebClient.builder()
        .clientConnector(ReactorClientHttpConnector { httpClient ->
            httpClient.run {
                tcpConfiguration { tcpClient ->
                    tcpClient.bootstrap { b ->
                        b.group(NioEventLoopGroup(0)) // 0表示使用默认线程数,但延迟初始化
                    }
                }
            }
        })
        .build()
}

2. 控制WebFlux服务端EventLoop的生命周期

Spring WebFlux的Netty服务端默认会在上下文刷新阶段启动EventLoop,需要自定义配置让其在检查点完成后再启动:

@Bean
fun nettyServerCustomizer(): NettyServerCustomizer {
    return NettyServerCustomizer { httpServer ->
        httpServer.run {
            tcpConfiguration { tcpServer ->
                tcpServer.bootstrap { b ->
                    b.group(NioEventLoopGroup(0))
                }
            }
        }
    }
}

3. 处理Lettuce Redis客户端的Netty资源

Lettuce也依赖Netty,需要配置Lettuce客户端在检查点前关闭连接池,检查点恢复后重新初始化:

@Bean
fun redisConnectionFactory(): ReactiveRedisConnectionFactory {
    val clientConfig = LettuceClientConfiguration.builder()
        .clientOptions(ClientOptions.builder()
            .disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
            .build())
        .build()
    val redisConfig = RedisStandaloneConfiguration("localhost", 6379)
    return LettuceConnectionFactory(redisConfig, clientConfig).apply {
        // 注册CRac钩子,检查点前关闭连接
        addLifecycleProcessor(object : Lifecycle {
            override fun isRunning(): Boolean = false

            override fun start() {}

            override fun stop() {
                this@apply.shutdown()
            }
        })
    }
}

4. 调整CRac检查点触发时机

如果上述配置仍有问题,可以将检查点触发时机从onRefresh改为onApplicationReady,此时应用已完成初始化,部分资源可以被正确暂停:

java -Dspring.context.checkpoint=onApplicationReady -XX:CRaCCheckpointTo=./crac -jar my-app.jar

注意:此方法需要确保所有Netty资源在onApplicationReady前已正确注册CRac钩子,能够在检查点前暂停资源。

5. 使用Netty的CRac适配模块

Netty从4.1.94版本开始提供了CRac的初步支持,Spring Boot 3.2.2默认依赖Netty 4.1.100.Final,可手动添加Netty的CRac模块,它会自动注册CRac钩子,在检查点前关闭Netty的EventLoop资源,恢复时重新初始化:

<!-- Maven示例,Gradle同理 -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-crac</artifactId>
    <version>4.1.100.Final</version>
</dependency>

总结

核心思路是确保所有基于Netty的组件(WebFlux服务端、WebClient、Lettuce)在CRac检查点生成前,暂停或关闭持有原生文件描述符的资源,并在恢复时重新初始化。优先升级Netty版本并使用其CRac适配模块,其次调整检查点触发时机或自定义组件的初始化逻辑。

内容的提问来源于stack exchange,提问作者Brian Preuß

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 07:44:52