返回

一次 Netty 代码不健壮导致的大量 CLOSE_WAIT 连接成因探析

后端

引言

在高并发系统中,网络连接的健康和稳定至关重要。然而,在实际开发中,往往会出现各种各样的问题,导致连接异常或不稳定。本文将以一次 Netty 代码不健壮导致的大量 CLOSE_WAIT 连接的案例为基础,深入探讨其成因,并提出解决方案。

背景介绍

CLOSE_WAIT 是 TCP 连接的一种状态,表示本地端已经关闭了连接,但远程端尚未关闭。这通常是由于本地端在关闭连接之前,远程端还有数据需要发送,导致本地端无法完全关闭连接。在高并发系统中,大量 CLOSE_WAIT 连接的存在可能会导致资源泄露、性能下降等问题。

问题分析

在本次案例中,问题出在 Netty 代码的一个 try-catch 块中。该 try-catch 块包裹的逻辑太多,导致在 OOM(内存溢出)throwable 异常处理时,没能成功注册事件,也没有关闭已创建的连接。

具体而言,在 Netty 代码中,有一个用于处理客户端连接的 ChannelInitializer 类。在这个类中,有一个 handleAdd() 方法,负责为每个客户端连接注册事件。在 handleAdd() 方法中,首先会创建一个 ChannelPipeline,然后在 ChannelPipeline 中注册各种事件处理器。

在注册事件处理器时,使用了 try-catch 块来处理异常。如果在注册过程中发生异常,则会打印异常信息,但不会关闭已经创建的连接。这导致在 OOM 异常发生时,虽然事件注册失败了,但连接仍然保持着,处于 CLOSE_WAIT 状态。

解决方案

为了解决这个问题,需要在 ChannelInitializer 类中对 handleAdd() 方法进行修改。在 try-catch 块中,如果发生异常,除了打印异常信息之外,还需要关闭已经创建的连接。这样,当事件注册失败时,连接就会被关闭,不会处于 CLOSE_WAIT 状态。

修改后的代码如下:

public void handleAdd(ChannelHandlerContext ctx) throws Exception {
    ChannelPipeline pipeline = ctx.pipeline();
    try {
        // 注册各种事件处理器
    } catch (Throwable t) {
        logger.error("Error registering event handlers", t);
        ctx.close();
    }
}

总结

通过对本次案例的分析,我们可以看到,在 Netty 代码中,如果 try-catch 块包裹的逻辑太多,可能会导致在异常处理时出现问题。因此,在编写 Netty 代码时,应该尽量避免在 try-catch 块中包裹过多的逻辑,尤其是与资源释放相关的逻辑。此外,在处理异常时,应该确保释放已经创建的资源,以避免资源泄露和性能下降的问题。