接前一篇文章 TCP的backlog导致的HBase超时问题 https://yq.aliyun.com/articles/117801?spm=5176.8091938.0.0.kypXIC
如上图所示,用户业务服务器(ApplicationServer)上面发起HTTP GET/PUT请求,经过SLB到达后端服务器(HBase-Rest-Server), 一般请求链路耗时大概100ms左右,但是会有一定的概率出现耗时很长(超过3s)。
业务方提出问题:
1.为什么slb到ecs连接多 2.为什么过slb后耗时多了2s多
备注:
linux 2.6.32 内核 TCP_TIMEOUT_INIT 3秒是syn报文第一次重传默认规定时间
linux 3.10内核 TCP_TIMEOUT_INIT 1秒是syn报文第一次重传默认规定时间.
到此问题比较明确是TCP建立连接失败, 不断重传SYN报文引起延时, 为了证明让在业务ECS上用nstat以下命令查看一下建立连接失败的统计和建立连接队列是否存在溢出.
果然发现执行结果TcpAttemptFails 偶尔非0, TcpExtListenOverflows有10多的值出现.
关于连接数量的分析:
查看用户slb实例前端流量与连接状态图,可知每秒平均259条新建连接
业务服务器两台, 从上边nstat输出观察可知每台每秒约100-130条TcpPassiveOpens连接,两台的和值与前端连接数量符合.
给出业务ECS端建立连接失败优化调整建议:net.ipv4.tcp_max_syn_backlog = 16384 原默认值1024
net.core.somaxconn = 65535 原默认值128
服务器程序中listen backlog = 8191
tw参数:net.ipv4.tcp_tw_reuse改成1
net.ipv4.tcp_max_tw_buckets增加一倍,改为10000
tcp_fin_timeout 调低吧 15秒
但调整后还是发现有耗时3秒的情况, 随后调查HBase服务发现jetty默认backlog 50, 没有做修改, 修改后耗时较长现象消除.
下边属于上述参数内核代码部分分析
内核参数somaxconn,sysctl_max_syn_backlog 和listen backlog参数之间的关系分析
要搞清楚内核中与建立连接有关tcp_max_syn_backlog,somaxconn两个参数的作用,需要细化说明tcp建立连接阶段两个队列的含义:linux内核中用struct request_sock_queue -> struct listen_sock->struct request_sock结构存储当前正在请求建立连接的sock,称作半连接状态(用syn_backlog表示)。request_sock有个成员变量指针指向对应的struct sock。struct request_sock_queue中rskq_accept_head和rskq_accept_tail分别指向已经建立完连接的request_sock,称作全连接状态(用backlog表示),这些sock都是完成了三次握手等待程序调用accept接受连接。半连接队列在内核中的具体的变量是:inet_csk(sk)->icsk_accept_queue-> listen_opt使用了以下两个变量维护半连接队列长度:
已使用队列长度 inet_csk(sk)->icsk_accept_queue->qlen 最大队列长度限制 icsk_accept_queue->listen_opt->max_qlen_log (即长度限制2^ max_qlen_log)全连接队列在内核中的表示:
request_sock_queue结构中使用rskq_accept_head和rskq_accept_tail维护了全连接队列。inet_csk(sk)->icsk_accept_queue->rskq_accept_head inet_csk(sk)->icsk_accept_queue->rskq_accept_tail使用了以下两个变量维护全连接队列长度
unsigned short sk_ack_backlog; //(全连接队列)可接受连接队列,使用过程中队列的计数 unsigned short sk_max_ack_backlog; //(全连接队列)最大的可接受连接队列,默认值是listen时设置的backlog参数服务器端程序通过listen函数设置监听端口 backlog参数,内核理论上将允许该端口最大同时接收2*backlog参数个并发连接“请求”(不含已被应用程序接管的连接)——分别存放在 syn_backlog 和 backlog 队列——每个队列的最大长度为backlog值(为什么是最大还取决于内核参数稍后解释)。syn_backlog 队列存储 SYN_ACK 状态的连接,backlog 则存储 ESTABLISHED 状态但尚未被应用程序接管的连接。accept系统调用时将从全连接backlog队列的rskq_accept_head取出head节点req请求,并从此队列中移除,具体的实现可以参考内核中的tcp_check_req()函数,此函数调用完成后也将握手完成后的连接放入了ehash散列表,就与连接阶段的两个队列没有关系了。
内核参数somaxconn,sysctl_max_syn_backlog 和listen backlog参数之间的关系分析这部分的分析有兴趣的可以查看内核中SYSCALL_DEFINE2(listen, int, fd, int, backlog)和reqsk_queue_alloc()函数的实现。
listen()参数backlog,内核参数somaxconn, 内核参数tcp_max_syn_backlog中最小值加1后,向上扩展为2整数次幂后 做为半连接队列的长度。代码的实现如下:
if ((unsigned)backlog > somaxconn)//listen调用的backlog大于somaxconn则取两者之小值 backlog = somaxconn; nr_table_entries = min_t(u32, backlog, sysctl_max_syn_backlog);//取与max_syn_backlog之最小值 nr_table_entries = max_t(u32, nr_table_entries, 8);// nr_table_entries小于8时取为8 nr_table_entries = roundup_pow_of_two(nr_table_entries + 1); //roundup_pow_of_two - round the given value up to nearest power of two 作用是:计算出最接近2的n次方并且大于size的值即向上扩展为2整数次幂 最终nr_table_entries的值就是半连接队列的长度。listen()参数backlog,内核参数somaxconn取二者的最小值做为全连接队列的长度。
if ((unsigned)backlog > somaxconn)//listen调用的backlog大于somaxconn则取两者之小值backlog = somaxconn; sk->sk_max_ack_backlog = backlog; 相关资源:敏捷开发V1.0.pptx