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

Tomcat中部署多GRPC Netty服务,应用重部署端口占用问题

解决Tomcat重部署时gRPC服务器端口占用问题

这个问题在非Spring Boot的Spring+Tomcat环境里非常典型——当你重部署应用时,旧的Web应用实例没有被彻底清理,导致gRPC服务器绑定的Netty线程还在占用6565/7575端口,新实例启动时就会抛出java.net.BindException: Address already in use。下面是几个可靠的解决方案:

1. 为gRPC服务器Bean添加销毁回调

你用@PostConstruct启动了gRPC服务器,但没有对应的销毁逻辑来关闭它。Spring提供了@PreDestroy注解(或实现DisposableBean接口),可以在Bean被销毁时主动关闭gRPC服务器,释放端口和线程:

@Component
public class GrpcServerBean implements DisposableBean {
    private Server grpcServer;

    @PostConstruct
    public void startGrpcServer() throws IOException {
        // 初始化并启动gRPC服务器的业务代码
        this.grpcServer = ServerBuilder.forPort(6565)
                .addService(new MasterDataServiceImpl())
                .build()
                .start();
    }

    @Override
    public void destroy() throws InterruptedException {
        if (grpcServer != null) {
            // 先优雅关闭,等待正在处理的请求完成
            grpcServer.shutdown();
            // 设置30秒超时,确保服务器有足够时间终止
            if (!grpcServer.awaitTermination(30, TimeUnit.SECONDS)) {
                // 超时后强制关闭剩余连接
                grpcServer.shutdownNow();
            }
        }
    }
}

Tomcat在卸载旧应用时,会触发destroy()方法,保证gRPC服务器能正常释放资源。

2. 使用Servlet上下文监听器兜底

有时候Bean的销毁顺序可能受Spring容器影响,改用ServletContextListener可以更可靠地监听Web应用的生命周期,确保在应用销毁时关闭gRPC服务器:

@WebListener
public class GrpcServerContextListener implements ServletContextListener {
    private Server grpcServer;

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        try {
            this.grpcServer = ServerBuilder.forPort(6565)
                    .addService(new MasterDataServiceImpl())
                    .build()
                    .start();
        } catch (IOException e) {
            throw new RuntimeException("Failed to start gRPC server", e);
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        if (grpcServer != null) {
            grpcServer.shutdown();
            try {
                if (!grpcServer.awaitTermination(30, TimeUnit.SECONDS)) {
                    grpcServer.shutdownNow();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

记得在web.xml里注册这个监听器,或者确保Spring配置能扫描到@WebListener注解。

3. 配置Tomcat减少类加载器泄漏

类加载器泄漏是导致旧线程残留的常见原因,你可以修改Tomcat的context.xml,添加以下参数优化类加载器回收:

<Context antiResourceLocking="true" antiJARLocking="true">
    <!-- 其他原有配置 -->
</Context>

antiResourceLocking会让Tomcat在部署时复制JAR文件,避免文件锁定;antiJARLocking则防止JAR被占用,帮助类加载器正确回收旧应用的资源。

4. 开发环境临时方案(不建议生产使用)

如果只是开发阶段想快速绕过问题,可以给gRPC服务器设置端口重用选项,让新实例能绑定已被旧进程占用的端口:

this.grpcServer = ServerBuilder.forPort(6565)
        .addService(new MasterDataServiceImpl())
        .withChildOption(ChannelOption.SO_REUSEADDR, true)
        .build()
        .start();

这个选项允许操作系统快速重用TIME_WAIT状态的端口,但生产环境还是要依赖前面的正确关闭逻辑,避免资源泄漏隐患。

内容的提问来源于stack exchange,提问作者Kasun Kariyawasam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:01:15