1. 前言
近期看《理解了实现再谈⽹络性能》(作者:张彦飞,个人网站)一书,发现下图简洁明了的阐述了TCP的半连接队列与全连接队列的关系,故摘抄至本文做记录。
Linux主机上半连接以及全连接队列的长度均较为保守,这导致在瞬时高并发场景下(网站门户、物联网服务器等),如果队列满了,客户端在三次握手期间发送的syn或者ack包会被服务端丢弃。客户端在等待较长时间后,会发出TCP Retransmission重传包,进一步加剧了网络压力。
服务端半连接队列满导致syn包被丢弃,如下图所示:
2. 修改半连接队列
半连接队列⻓度 Linux内核中,主要受tcp_max_syn_backlog影响 加⼤它到⼀个合适的值就可以。
sudo echo "2048" > /proc/sys/net/ipv4/tcp_max_syn_backlog
sudo sysctl -p
若要此设置永久生效, 需要将相应的设置添加到/etc/sysctl.conf文件中。
3. 修改全连接队列
全连接队列⻓度是应⽤程序调⽤listen时传⼊的backlog以及内核参数net.core.somaxconn⼆者之中较⼩的那个。因此需要同时调整你的应⽤程序和该内核参数。
3.1 内核参数修改
Linux主机默认的somaxconn参数值为128,建议根据服务端实际负载调整(现阶段阿里云以及腾讯云的默认配置为4096)。
sudo sysctl -w net.core.somaxconn=1024
sudo sysctl -p
若要此设置永久生效, 需要将相应的设置添加到/etc/sysctl.conf文件中。
3.2 应用设置全连接队列长度
TCP全连接队列(也称为accept队列)的大小可以通过SO_BACKLOG选项在Netty的ServerBootstrap中设置。这个选项指定了内核应该为正在监听的套接字排队的最大连接数。Netty设置如下所示:
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 2028) // 设置TCP全连接队列的大小
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new YourServerHandler());
}
});
4. 队列溢出错误处理
netstat -s 可查看到当前系统半连接队列满导致的丢包统 计,但该数字记录的是总丢包数。可借助 watch 命令动态监控。
4.1 半连接队列异常
如果下⾯的数字在你监控的过程中变了,那说明当前服务器有因为半连接队列满⽽产⽣的丢包。
watch 'netstat -s | grep LISTEN'
4.2 全连接队列异常
watch 'netstat -s | grep overflowed'
4.3 调整队列后依然溢出
如果加⼤队列后仍然有⾮常偶发的队列溢出的话,我们可以暂且容忍。
如果仍然有较⻓时间处理不过来怎么办?另外⼀个做法就是直接报错,不要让客户端超时等待。例如将Redis、Mysql等后端接⼝的内核参数tcp_abort_on_overflow
为1。如果队列满了,直接发reset给client。告诉后端进程/线程不要痴情 地傻等。这时候client会收到错误connection reset by peer
。牺牲⼀个⽤户的访问请求,要⽐把整 个站都搞崩了还是要强的。