【问题标题】:Stop accepting new TCP connections without dropping any existing ones停止接受新的 TCP 连接而不丢弃任何现有的连接
【发布时间】:2017-11-11 20:59:15
【问题描述】:

我有两台服务器在负载平衡器后面的 TCP 端口上侦听。负载平衡器可以检测来自客户端的 TCP 连接尝试是否不成功,并在不断开该连接的情况下重试到第二台服务器。我希望能够在不丢失单个客户端集合的情况下关闭这两个服务器中的任何一个进行维护。

我的服务器使用此代码来处理客户端请求:

ServerSocketFactory ssf = ...
ServerSocket serverSocket = ssf.createServerSocket(60000);
try {
    while (true) {
        Socket socket = serverSocket.accept();
        ...// Do the processing
    }
} catch (IOException e) {
    ...
}
...

我最初的想法是添加一个布尔值,它将在应用程序关闭时设置,并在等待所有现有连接被处理和关闭时阻止新的serverSocket.accept() 调用。但是,甚至在serverSocket.accept() 调用之前就已经建立了新的连接。如果我在该调用之前放置断点,这就是我在 Wireshark 中看到的内容。 问题是,只要我打电话给serverSocket.close(),所有这样的客户端连接都会被丢弃。我想要实现的是某种方式告诉 ServerSocket 停止接受所有新连接(即只为新连接发送 RST 或让它们超时),因此负载均衡器可以将它们重新路由到另一台服务器,但同时不会丢弃任何已建立的连接。

编辑:我正在寻找一些自动化解决方案,它不需要我每次想要更新应用程序时都更改任何负载平衡器或操作系统设置。

【问题讨论】:

  • 我相信它是负载均衡器的功能,它检测哪些服务器可用于服务并将新连接转发到该服务器。我不会让你的 Socket 代码更复杂,因为它不是它的功能。查看您的负载均衡器运行状况检查和路由配置。如果您将其中一个服务器 ip 从平衡器路由表中取出,那么新的连接将采用另一种方式。
  • 是的,这是负载均衡器检测可用服务器的功能,它完成了它的工作。它检测连接是否未建立(重置或超时)并将该连接转发到另一台服务器。问题是我的服务器一直在接受新连接,即使我不希望它接受,在我打电话给serverSocket.close() 之前。一旦我调用它所有已经建立的连接,serverSocket.accept() 没有被调用,但都被丢弃了。在这种情况下,负载均衡器无法帮助我,因为它认为这些连接已建立并假定服务器可以处理它们。
  • 这取决于您的负载均衡器的配置方式。我使用的负载均衡器(F5)有一个健康检查配置。我们公开了一个 servlet-endpoint 供 LB 监控。如果我们让这个服务不可用,LB 会认为服务器宕机,不会在那里路由新的流量。
  • 我也使用 F5,它确实有健康检查。我使用Reselect Tries option and Inband monitor,所以如果连接失败,它总是会重试连接到另一台服务器。但它仍然没有解决上述问题,因为这些连接只有在它们已经建立后才会失败。使用一些“每隔 n 秒检查一次”健康监视器而不是 Inband 会更糟糕,因为在我关闭服务器和 F5 发送下一个健康检查请求并检测到它已关闭之间总是有一个时间范围。
  • 这是一个非常有趣的话题。你应该阅读veithen.github.io/2014/01/01/…。似乎没有办法做你想做的事,因为在 java 中将应用程序级别的 ack 队列设置为零是不可能的(而且无论如何也没有多大意义)你真的应该按照其他人的建议去做并实施另一个健康检查

标签: java sockets tcp high-availability


【解决方案1】:

您可以在服务器上添加防火墙规则,该规则将阻止新连接但保持旧连接处于活动状态。我猜服务器是基于Linux的?如果是这样,您可以尝试:

iptables -A INPUT -p tcp --syn --destination-port <port> -j REJECT --reject-with icmp-host-prohibited

之后,您可以使用 netstat 检查是否有任何活动连接,并在没有任何连接时关闭应用程序一次:

netstat -ant|grep <port>|grep EST

维护完成后,您可以删除防火墙规则。首先,列出所有规则以找到它:

iptables -L -n

并删除它:

iptables -D INPUT <rule number>

【讨论】:

  • 感谢您的回答,但不幸的是,它不适用于我的情况,因为我在服务器上没有 root。我当然可以要求更改 iptables 一次,但不是每次更新应用程序时。
【解决方案2】:

ServerSocket.accept() 阻塞或ServerSocketChannel.accept() 返回null 时,积压队列为空。 此时,停止接受并关闭监听套接字。等待所有现有的已接受套接字完成它们的工作,然后让应用程序退出。

【讨论】:

  • 如何可靠地确定ServerSocket.accept() 何时阻塞?有时我可以在一秒钟内建立大约 50 个新连接,所以即使我可以确定它何时阻塞,在那个时刻和我调用 ServerSocket.close() 的时刻之间,也可以建立一个新连接。这是一个竞争条件。
  • 只需将volatile boolean blocking; 添加到您的班级,在您调用accept() 时将其设置为true,并在它返回时立即设置为false。当然不精确。比赛条件也是不可避免的。你能用ServerSocketChannel吗?如果是这样,当Selector.select() 超时且仅注册ServerSocketChannel 时(对于 OP_ACCEPT),积压队列不仅是空的,而且在超时期间一直是空的。您可以像以前一样将结果接受的SocketChannels 转换为Sockets
  • 但是ServerSocketChannel 不会避免竞争条件,对吧?这只会降低在我关闭套接字之前建立新连接的可能性,因为有一段时间没有新连接了。
  • 鉴于积压队列存在并且您无法控制它,无论您做什么都无法避免竞争。
  • 我就是这么想的。看起来防止数据丢失的唯一 100% 方法是让客户端等待并在连接失败的情况下重试。我希望存在一个更优雅的解决方案,因为当需要高可用性时,我想要实现的应该是一项非常常见的任务。无论如何感谢您的回答。
【解决方案3】:

解决问题的最简单方法是在本地应用服务器之前放置额外的负载均衡器。

检查nginxHAproxy 并选择它们,这更适合您的任务。它们都具有优雅关闭的功能,这意味着它们停止接受新连接,但继续为现有连接提供服务。另一个优点是您的应用程序不需要对代码进行任何更改。

nginx 正常关机:

nginx -s quit

HAproxy 正常关机:

haproxy -sf $(cat /var/run/haproxy.pid)

【讨论】:

  • 我看不出它对已经积压的连接有何帮助,请参阅 Sami Korhonen 提供的链接veithen.github.io/2014/01/01/…。它仍然会丢弃这些已经建立的连接,但是 nginx 或 HAproxy 尚未调用 accept
  • 因此,无法解决您的问题。你不能用 Java 编写比 nginxHAproxy 已经拥有的更好的连接处理。
【解决方案4】:

我得出的结论是,我想要实现的目标在 Linux 上是不可能的。问题是操作系统通过发送 SYN,ACKACK 数据包完成与客户端的初始握手,而应用程序无法控制此过程。握手后,连接建立,操作系统将其放入积压队列。建立连接后,我使用的负载均衡器 (F5 BigIP) 在任何情况下都不会将其转发到另一台服务器,无论我在那里进行了何种健康检查。当我关闭套接字时,来自积压队列的已建立但尚未接受的连接被丢弃。

但是,可以使用 Windows 套接字 C++ API 的 SO_CONDITIONAL_ACCEPT 套接字选项和 WSAAccept 函数在 Windows 上实现。此选项允许应用程序控制初始握手。一个很好的解释可以在this answer

在端口上调用listen() 时,操作系统开始接受连接 在那个港口。这意味着它开始回复 SYN,ACK 数据包到 连接,无论 C 代码是否调用了 accept()。 ... 但是,在 Windows 上,SO_CONDITIONAL_ACCEPT 调用允许 应用程序来控制积压队列。这意味着 在应用程序之前,服务器不会对 SYN 数据包做出任何回应 对连接做一些事情。这意味着,拒绝 此级别的连接实际上可以向网络发送 RST 数据包 不创建状态。

它看起来像 Linux doesn't have a similar feature,如this answer 中所述:

三次握手是tcp/ip基本结构的一部分,所以 它嵌入在堆栈中(即内核级别)。所有非内核 握手后你得到的代码运行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-02-19
    • 2011-03-27
    • 2021-12-22
    • 2010-10-21
    • 2019-04-30
    • 2011-03-07
    • 1970-01-01
    相关资源
    最近更新 更多