[英]Stop accepting new TCP connections without dropping any existing ones
I have two servers listening on a TCP port behind a load balancer. 我有两个服务器在负载均衡器后面的TCP端口上侦听。 The load balancer can detect if a TCP connection attempt from a client was unsuccessful and retry it to the second server without dropping that connection. 负载平衡器可以检测到来自客户端的TCP连接尝试是否失败,然后重试到第二台服务器,而不会断开该连接。 I want to be able to bring any of these two servers down for maintenance without dropping a single client collection. 我希望能够关闭这两个服务器中的任何一个以进行维护,而不必删除单个客户端集合。
My servers use this code to process client requests: 我的服务器使用以下代码来处理客户端请求:
ServerSocketFactory ssf = ...
ServerSocket serverSocket = ssf.createServerSocket(60000);
try {
while (true) {
Socket socket = serverSocket.accept();
...// Do the processing
}
} catch (IOException e) {
...
}
...
My initial thought was to add a boolean that would be set on application shutdown and prevent new serverSocket.accept()
calls while waiting for all existing connection to be processed and closed. 我最初的想法是添加一个将在应用程序关闭时设置的布尔值,并在等待所有现有连接被处理和关闭时阻止新的serverSocket.accept()
调用。 However, new connection are being established even before the serverSocket.accept()
call. 但是,甚至在serverSocket.accept()
调用之前就已经建立了新的连接。 Here's what I see in Wireshark if I put a breakpoint before that call. 如果在该调用之前放置断点,这就是我在Wireshark中看到的内容。 The problem is at this point as soon as I call serverSocket.close()
, all such client connections get dropped. 问题是在这一点上,只要我调用serverSocket.close()
,所有此类客户端连接都会被丢弃。 What I want to achieve is some way of telling ServerSocket to stop accept all new connections (ie only send RST for new connections or let them time out), so the load balancer can reroute them to another server, but at the same time not drop any already established connections. 我想要实现的是一种告诉ServerSocket停止接受所有新连接的方法(即仅发送RST进行新连接或让它们超时),以便负载均衡器可以将它们重新路由到另一台服务器,但同时又不会掉线任何已经建立的连接。
Edit: I'm looking for some automated solution which wouldn't require me to change any load balancer or OS settings every time I want to update the application. 编辑:我正在寻找一种自动化的解决方案,它不需要我每次要更新应用程序时都更改任何负载平衡器或OS设置。
You could add firewall rule on the server which will block new but keep old connections active. 您可以在服务器上添加防火墙规则,该规则将阻止新的规则,但使旧的连接保持活动状态。 I guess the server is Linux based? 我猜服务器是基于Linux的? If so, you could try with: 如果是这样,您可以尝试:
iptables -A INPUT -p tcp --syn --destination-port <port> -j REJECT --reject-with icmp-host-prohibited
After that you can check with netstat is there any active connection and bring the application down once when there is no any: 之后,您可以使用netstat检查是否存在任何活动连接,并在没有任何活动时关闭应用程序一次:
netstat -ant|grep <port>|grep EST
After you finish with the maintenance, you can remove the firewall rule. 完成维护后,可以删除防火墙规则。 First, list all the rules to find it: 首先,列出所有规则以找到它:
iptables -L -n
And remove it: 并将其删除:
iptables -D INPUT <rule number>
At any point when ServerSocket.accept()
blocks, or ServerSocketChannel.accept()
returns null, the backlog queue is empty. 在ServerSocket.accept()
阻塞或ServerSocketChannel.accept()
返回null的任何时候,积压队列为空。 At that point , stop accepting and close the listening socket. 此时 ,停止接受并关闭监听套接字。 Wait for all existing accepted sockets to finish their work and let the application exit at that point. 等待所有现有的已接受套接字完成其工作,然后让应用程序退出。
The easiest way to solve your problem is to put additional load balancer locally right before your application server. 解决问题的最简单方法是在应用程序服务器之前将其他负载平衡器放在本地。
Check nginx
and HAproxy
and chose on of them, which is better for your task. 检查nginx
和HAproxy
并选择它们,这对您的任务来说更好。 They both have a feature for graceful shutdown, which means that they stop accepting new connections but continue serving existing to the end. 它们都具有正常关闭的功能,这意味着它们将停止接受新连接,但将继续为现有连接提供服务。 Another advantage is that your application doesn't require any changes in code. 另一个优点是您的应用程序不需要任何代码更改。
Graceful shutdown for nginx
: 对于正常关机nginx
:
nginx -s quit
Graceful shutdown for HAproxy
: HAproxy
关机:
haproxy -sf $(cat /var/run/haproxy.pid)
I came to the conclusion that what I'm trying to achieve is not possible on Linux. 我得出的结论是,我要实现的目标在Linux上是不可能的。 The problem is the OS completes the initial handshake with the clients by sending the SYN,ACK and ACK packet, without any control over this process by the application. 问题是操作系统通过发送SYN,ACK和ACK数据包完成了与客户端的初始握手,而应用程序对此过程没有任何控制。 After the handshake, the connection becomes established and the OS puts it in the backlog queue. 握手后,连接建立,操作系统将其放入积压队列。 As soon as the connection is established, the load balancer that I'm using (F5 BigIP) doesn't forward it to another server under any circumstances, regardless of what kind of health checks I have there. 建立连接后,无论我在哪种情况下进行健康检查,我正在使用的负载均衡器(F5 BigIP)在任何情况下都不会将其转发到另一台服务器。 When I close the socket, already established but not yet accepted connections from the backlog queue got dropped. 当我关闭套接字时,来自待办事项队列的已建立但尚未接受的连接被删除。
However, it's possible to achieve with Windows using the SO_CONDITIONAL_ACCEPT socket option and WSAAccept function of the Windows Sockets C++ API. 但是,可以使用Windows套接字C ++ API的SO_CONDITIONAL_ACCEPT套接字选项和WSAAccept函数在Windows中实现。 This option allows the application to control the initial handshake. 此选项允许应用程序控制初始握手。 A good explanation can be found in this answer : 一个很好的解释可以在这个答案中找到:
When calling listen() on a port, the OS starts accepting connections on that port. 在端口上调用listen()时,操作系统开始接受该端口上的连接。 This means that is starts replying SYN,ACK packets to connections, regardless if the C code has called accept() yet. 这意味着无论C代码是否已调用accept(),它都开始向连接回复SYN,ACK数据包。 ... However, on windows, the SO_CONDITIONAL_ACCEPT call allows the application to take control of the backlog queue. ...但是,在Windows上,SO_CONDITIONAL_ACCEPT调用使应用程序可以控制积压队列。 This means that the server will not answer anything to a SYN packet until the application does something with the connection. 这意味着在应用程序对连接进行任何操作之前,服务器不会对SYN数据包做出任何答复。 This means, that rejecting connections at this level can actually send RST packets to the network without creating state. 这意味着,拒绝此级别的连接实际上可以将RST数据包发送到网络,而无需创建状态。
It looks like Linux doesn't have a similar feature , as described in this answer : 看起来Linux 没有类似的功能 ,如以下答案所述 :
The three-way handshake is a part of the basic structure of tcp/ip, so it's imbeded in the stack (ie kernel level). 三向握手是tcp / ip基本结构的一部分,因此它已嵌入到堆栈中(即内核级别)。 All the non-kernel code you get your hands on operate AFTER the handshake. 握手后,您获得的所有非内核代码都会起作用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.