企业数据库之ARP相关的内核参数unres_qlen

发布时间:2020-05-06 浏览次数:148

背景

我们在某客户生产环境中发现,应用服务器启动并与后端数据库建立连接池时,有概率发生部分连接建立失败的情况。经过排查发现,这种情况与ARP相关的内核参数unres_qlen有关。这篇文章将通过测试手段复现连接超时的情况,并详尽解析其底层的原因与机制。

复现环境

操作系统:RHEL 6.6

内核版本:2.6.32-504.el6.x86_64

测试程序(为客户端,并发发起16个TCP连接,超时时间设置为500ms):

企业数据库之ARP相关的内核参数unres_qlen——爱可生


运行测试程序的机器IP为10.0.0.102,服务端IP为10.0.0.101

现象描述

在运行测试程序tcp_unres_qlen前,先在10.0.0.102上清掉10.0.0.101的ARP缓存

企业数据库之ARP相关的内核参数unres_qlen——爱可生


然后运行tcp_unres_qlen程序,只有3个TCP连接成功建立,其他13个TCP连接都失败了;如果后续再多次运行tcp_unres_qlen,所有TCP连接都能正常建立,没有超时的情况,无法复现第一次运行tcp_unres_qlen的情况;后续再次清掉ARP缓存后运行tcp_unres_qlen又能复现连接超时的情况。

企业数据库之ARP相关的内核参数unres_qlen——爱可生


问题分析

通过在10.0.0.101上抓包可以验证上述结果(tcpdump src 10.0.0.102):

企业数据库之ARP相关的内核参数unres_qlen——爱可生


可以看到10.0.0.102先是广播了一个ARP请求,询问10.0.0.101的MAC地址,然后紧接着发出了三个SYN包到10.0.0.101,后续这三个TCP连接也成功建立,而测试程序tcp_unres_qlen发起的另外13个TCP连接在10.0.0.101上没有抓到任何数据包,说明都超时失败了。

在运行测试程序tcp_unres_qlen的同时,10.0.0.102上使用dropwatch工具抓到了如下信息:

企业数据库之ARP相关的内核参数unres_qlen——爱可生


经过多次测试反复观察,排除dropwatch抓到的其他干扰信息,会发现__neigh_set_probe_once+16e (0xffffffff8146892e)出现的次数每次相加都是13,正好与13个超时失败的TCP连接相对应。这表示__neigh_set_probe_once函数偏移16e的位置有包被丢弃,通过SystemMap来定位:

企业数据库之ARP相关的内核参数unres_qlen——爱可生


__neigh_set_probe_once的位置87c0+16e偏移量正好等于892e处于__neigh_event_send和neigh_resolve_output之间,那么也就是说包在__neigh_event_send函数丢掉了,查找kernel源码发现:

企业数据库之ARP相关的内核参数unres_qlen——爱可生


neigh/default/unres_qlen - INTEGER__neigh_set_probe_once函数中neigh->nud_state = NUD_INCOMPLETE;所以看__neigh_event_send中if (neigh->nud_state == NUD_INCOMPLETE) {}中的内容,可以看到当skb_queue_len(&neigh->arp_queue) >= neigh->parms->queue_len时会清空sk_buff,相当于丢弃相应的skb,体现在TCP层面就是TCP连接发起方的第一个SYN包被丢掉了(SYN包的重传次数是由内核参数net.ipv4.tcp_syn_retries决定的,而第一次发送SYN包失败后到第二次重传的时间间隔RTO是由RTT决定的,所以不同的网络条件下,这个RTO时间会不同,且每多重传一次RTO时间还会根据算法加大,这也是为什么tcp_unres_qlen程序中故意将超时时间设置为很短的500ms<RTO,人为制造连接超时的原因,这部分的知识可参看本文最后的参考文献[1]),导致连接超时失败。可以看出这个判断结果的得出是由neigh->parms->queue_len的值决定的,这个值对应的内核参数就是net.ipv4.neigh.*.unres_qlen(unresolved_queue_length),参考内核文档中的描述(参考文献[2]):


neigh/default/unres_qlen - INTEGER

The maximum number of packets which may be queued for each unresolved address by other network layers. (deprecated in linux 3.3) : use unres_qlen_bytes instead. Prior to linux 3.3, the default value is 3 which may cause unexpected packet loss. The current default value is calculated according to default value of unres_qlen_bytes and true size of packet.

Default: 31

neigh/default/unres_qlen_bytes - INTEGER

The maximum number of bytes which may be used by packets queued for each unresolved address by other network layers. (added in linux 3.3) Setting negative value is meaningless and will return error.

Default: 65536 Bytes(64KB)

可以看出,在kernel 3.3之前,unres_qlen的默认值为3,这个参数的具体作用是什么呢,让我们来看一下一个TCP连接建立的过程:

1、程序发出SYN建链报文后,报文到IP层需要进行路由查询;

2、路由查询完成后,报文到ARP层查询下一跳IP对应的MAC地址;

3、如果本地没有缓存相关的ARP记录,就需要把这个SYN报文放进一个队列里缓存起来,然后发起ARP请求;

4、ARP层收到ARP回应报文之后,从缓存中取出SYN报文,完成二层数据帧的封装并发送给驱动,后面的事情就不说了。

这其中的关键问题就在于第3步,unres_qlen参数将在这里起作用,缓存上层数据报文的队列长度即unres_qlen只有3,也就是说这个队列最多同时只能缓存3个SYN报文,如果并发发起超过3个TCP连接,没能塞进队列里的SYN报文就都被丢弃了,TCP层面会计算RTO时间,重发SYN包。

结论

在许多场景中,例如应用程序启动时,并发发起多个TCP连接到数据库建立连接池,如果应用程序的TCP连接超时时间设置过短,就有可能出现上述部分连接失败的情况,这种情况偶然发生难以复现排查,对于kernel<3.3版本,出现上述问题的解决方案是加大unres_qlen的值。

上一篇: 有关MySQL单线程insert的性能模型分析

下一篇: 使用Apache Zookeeper分布式部署PHP应用程序

咨询客服 在线咨询
400-820-6580 免费电话