1 row in set (0.22 sec) 1 row in set (0.42 sec) 1 row in set (0.83 sec) 1 row in set (1.62 sec) 1 row in set (3.23 sec) ...
如果断开重连,又从0.22重新开始倍增。 一开始我们认为是后端ADS的处理有问题,但绕过LVS直接访问ADS的时候,响应时间是不变的,那么显然问题出在LVS上。内核LVS的调试信息比较少,在LVS机器的ADS侧抓包,我们看到了一个奇怪的现象:
看上去是LVS机器收到了一个1913的包,处理不了,所以回了ICMP差错报文,告诉ADS端要分片Fragment;ADS端收到差错报文后重传,由于是同一条连接,所以每次收到ICMP报文后,重传定时器都进行了指数退避,现象上来看就是select的时间发生了倍增。 这里有3个问题:
问题1:为什么会有超过1500的大包?
之前在交换机上,报文发送时会查看mtu,如果超出,总是会分片;但在服务器上,如果有的网卡支持TSO/GSO/GRO,那么发送的报文大小会超过mtu,也就是上面我们看到的1912这种报文。网卡 LSO/LRO、GSO/GRO、TSO :
GSO(generic-segmentation-offload)/ TSO(TCP-segmentation-offload) 所谓的GSO,实际上是对TSO的增强。TSO将tcp协议的一些处理下放到网卡完成以减轻协议栈处理占用CPU的负载。通常以太网的MTU是1500Bytes,除去IP头(标准情况下20Bytes)、TCP头(标准情况下20Bytes),TCP的MSS (Max Segment Size)大小是1460Bytes。当应用层下发的数据超过了mss时,协议栈会对这样的payload进行分片,保证生成的报文长度不超过MTU的大小。但是对于支持TSO/GSO的网卡而言,就没这个必要了,可以把最多64K大小的payload直接往下传给协议栈,此时IP层也不会进行分片,一直会传给网卡驱动,支持TSO/GSO的网卡会自己生成TCP/IP包头和帧头,这样可以offload很多协议栈上的内存操作,checksum计算等原本靠CPU来做的工作都移给了网卡。
GRO(generic-receive-offload)/ LRO(large-receive-offload) LRO通过将接收到的多个TCP数据聚合成一个大的数据包,然后传递给网络协议栈处理,以减少上层协议栈处理 开销,提高系统接收TCP数据包的能力。 而GRO的基本思想跟LRO类似,克服了LRO的一些缺点,更通用。后续的驱动都使用GRO的接口,而不是LRO。
问题1的解决方法很简单,将涉及到的硬件关闭TSO/GSO/GRO/LRO即可。
$ ethtool -K etho tso off $ ethtool -K etho gso off $ ethtool -K etho gro off $ ethtool -K etho lro off $ ethtool -k eth0 Features for eth0: rx-checksumming: on tx-checksumming: on scatter-gather: on tcp-segmentation-offload: off udp-fragmentation-offload: off generic-segmentation-offload: off generic-receive-offload: off large-receive-offload: off rx-vlan-offload: on tx-vlan-offload: on ntuple-filters: on receive-hashing: on
不过这样重启后就失效了,还是需要写到配置文件里去。
todo:还不知道怎么在ifcfg-ethx里记录,所以我把上面几条命令写到/etc/rc.local里了。
问题2:为什么超过mtu的报文,LVS返回了ICMP差错报文?
额。没有为什么,支持Fullnat以后,内核对于超过mtu的报文会直接丢弃并返回ICMP差错报文,具体查看patch里代码:
+/* Response transmit icmp to client + * Used for NAT / local client / FULLNAT. + */ +int +ip_vs_fnat_response_icmp_xmit(struct sk_buff *skb, struct ip_vs_protocol *pp, + struct ip_vs_conn *cp, int offset) +{ + struct rtable *rt; /* Route to the other host */ + int mtu; + struct iphdr *iph = ip_hdr(skb); + + /* lookup route table */ + if (!(rt = ip_vs_get_rt(&cp->caddr, RT_TOS(iph->tos)))) + goto tx_error_icmp; + + /* MTU checking */ + mtu = dst_mtu(&rt->u.dst); + if ((skb->len > mtu) && (iph->frag_off & htons(IP_DF))) { + ip_rt_put(rt); + IP_VS_DBG_RL_PKT(0, pp, skb, 0, + "fnat_response_icmp(): frag needed for"); + goto tx_error; + }
吐槽一下,内核真的没法accept这样的patch:
- 收到目的地址是virtual service的报文,不管报文端口号跟virtual service的端口对不对的上,全部做转发
- 超过mtu的报文直接drop
问题3:为什么我们实验室的环境并没有出现这个问题?
这个问题困扰了我好几天。一开始是怀疑客户现场对LVS的ADS侧配置了聚合口,但真的不科学。后来看了下实验室环境的LVS虚拟机里的ethtool,才恍然大悟。
原因说起来也很简单,我们的LVS是个虚拟机,其网卡是qemu-kvm虚拟的。实验室环境网卡的Device model是Hypervisor default,在qemu-kvm环境里实际是老旧的RTL8139网卡,它不支持TSO等特性;而在客户现场的Device model是virtio,它支持TSO等,所以LVS在内核里会收到超过mtu的报文。
转载请注明:爱开源 » 为什么经过fullnat了以后,select查询变慢了呢