tcp -- reuseport不平滑升级

1. 热更新

热更新的目的是保证新进程启动,老的进程可以处理完已经处理的连接,然后退出。通用的方法是父子进程共享文件句柄方法,热更新时,新创建新的子进程,然后通知老的子进程退出。
这个方案有两个问题:

  • 父进程一直活跃,这样才能fork新子进程。
  • 父进程需要知道业务监听哪些端口

2. reuseport的方法

reuseport能够在两个不相关的进程监听同一端口,有负载均衡请求能力。在热更新的过程中,启动新进程,通知老进程关闭端口,处理完已经接收的连接后退出。
但在实际过程中,这样处理过程并不完美,监听端口有半连接或者全连接的队列没有处理完成,导致关闭过程中连接被reset。

3. 源码分析

shutdown丢弃连接的源码如下:

int inet_shutdown(struct socket *sock, int how) 
{
	switch (sk->sk_state) {
	case TCP_LISTEN:
		if (!(how & RCV_SHUTDOWN)) // 
			break;
		fallthrough;
	case TCP_SYN_SENT:
		err = sk->sk_prot->disconnect(sk, O_NONBLOCK);
		sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
		break;
	}
}

int tcp_disconnect(struct sock *sk, int flags)
{
	int old_state = sk->sk_state;
	/* ABORT function of RFC793 */
	if (old_state == TCP_LISTEN) {
		inet_csk_listen_stop(sk);
	}
}

void inet_csk_listen_stop(struct sock *sk)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct request_sock_queue *queue = &icsk->icsk_accept_queue;
	...
	while ((req = reqsk_queue_remove(queue, sk)) != NULL) {
		struct sock *child = req->sk;
		inet_child_forget(sk, req, child);
	}
	..
}

static void inet_child_forget(struct sock *sk, struct request_sock *req,
			      struct sock *child)
{
	sk->sk_prot->disconnect(child, O_NONBLOCK);
	...
	inet_csk_destroy_sock(child);
}

4 解决思路

目前linux不支持,这种解决的办法可以有3种:

  • 增加一种状态gracefulshutdown状态,如果新syn连接过来,发现reuseport状态,不分配此sock,已经建立的正常逻辑走,不过这个状态不好维护,需要应用层来决定关闭的时刻
  • 在关闭reuseport的时候,把队列内的连接重新hash到其他进程(为了均匀性还是保持hash状态),队列迁移
  • bpf方案
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值