eBay云计算“网”事|网络重传篇

2020 年 10 月 01 日

eBay云计算“网”事|网络重传篇

#导读
在之前的 eBay 云计算“网”事|网络超时篇 eBay 云计算“网”事|网络丢包篇里,我们针对 Linux 主机网络中常见的延时和丢包问题进行了分析。本篇将关注网络中另外一个常见的问题: 重传 。在网络环境中,重传率的高低往往直接影响到数据的传输效率,因此也是用户应用比较关心的一个指标。

一、问题描述

某个部署到 Kubernetes 集群的应用,会有多个 SLB(Softare Load Balancer)节点来对接收的数据流量进行分发。这些 SLB 节点原本部署在物理机上,在将某个业务节点部署到 Kubernetes 集群后,发现每 10 秒的重传包个数,相对于运行在物理机上时更高, 如图 1 所示。于是我们着手进行排查。

图 1 不同节点的 TCP 每 10 秒的重传个数

经过和应用方沟通并进入容器查看后,我们了解到相关信息:

  • 每个 SLB 节点,同时会有 25k 左右的 TCP 链接。这些链接的客户端节点,不一定相同,每个客户端节点可能会跟 SLB 创建多个链接,链接的维持时间一般都比较短。
  • 运行在物理机上的 SLB 节点,每 10 秒的重传包是 10k 左右,而运行在物理机上的 SLB 节点,每 10 秒的重传包在 2k 以下。
  • 重传包个数从 netstat -s|grep “segments retransmitted”得出,取 10 秒前后的差值,就得出每 10 秒的重传包数。
  • 每个 SLB 节点的业务量差别不大。运行在 Kubernetes 集群中的 SLB,业务量会比运行在物理机中的 SLB 流量多些,但是不至于造成重传统计上这么大的差别。

TCP 数据包的重传和网络链接客户端、网络路径强相关。在客户端和网络路径不同的情况下,节点的重传率可能会不同。因此两方的重传数据对比,很难说明重传率高就是因为业务容器化引起的。但我们仍旧需要对重传着手进行分析,并给出合理的解释。

二、问题分析

1. 是否有数据丢包的情况发生

因为涉及到重传,第一反应就是查看是否有丢包的情况发生。从 netstat -s 的情况来看,并没有明显的丢包情况发生。

2. TCP 协议栈配置参数是否一致

然后,我们对比下双边 TCP 配置参数,对比容器 Pod network namespace 和物理机下的 /proc/sys/net 的配置参数。两边的参数一致,除了 TCP 的拥塞(congestion)算法,前者是 BBR,后者是 CUBIC。于是修改了下容器内 tcp_congestion_control 的算法,一段时间后,当老的链接都关闭,新的链接拥塞算法都变为 BBR 后,重传包减少了一半左右,到了 6k/10 秒 ,如图 2 所示。尽管指标有了很大的改善,但是重传率还是比较高。

图 2 修改 TCP congestion 算法为 CUBIC

3. 重传的链接

因为链接比较多,我们需要查看具体是哪些链接的重传导致的问题。于是运行 BCC 的 tcpretrans.py 工具,来查看具体重传的链接信息。 结果发现,重传比较多的,都属于四个固定的虚拟机客户端。

于是在这些节点上通过 tcpdump 进行抓包,发现了如下现象:

  • 不管数据是来自于 Pod 还是来自于物理机的 SLB,在虚拟机上一直能看到有包乱序的情况。
  • 当接收到乱序包后,VM 会发送 SACK 数据包,而在 Pod 接收到 SACK 数据包后,Pod 会立刻进行重传,但物理机的 SLB 并没有。

4. 为何数据包会乱序

我们先来定位数据包乱序的原因。

在虚拟机所在的物理机上,对网卡和给虚拟机分配的 tap 端口上做 tcpdump,数据包并没有乱序,因此可以判断, 乱序应该是发生在虚拟机内部了。

在虚拟机上 kprobe 了函数 napi_gro_receive(),将接收的数据包进行打印,发现在数据包被该函数处理的时候,也并没有乱序,但是 tcpdump 上看到的数据包是乱序的,因此我们怀疑是 gro 引起的乱序问题。

通过 ethtool -K eth0 gro off 将虚拟机网卡的 gro 特性关闭。在 gro 关闭后,从 tcpdump 抓到的数据就不存在乱序情况了。因此将有包乱序的节点的 gro 都关闭,在 gro 关闭后,可以看到 TCP 的重传从 6k/10 秒 降到了 4k/10 秒

这些虚拟机使用的都是 ubuntu 16.04 的系统,且为 4.4 的 kernel 版本,所以网卡驱动的 gro 有 bug,导致包乱序,而如果使用的是 4.15 kernel 版本,就不会有乱序的情况发生。

既然了解了包乱序的问题,那么遗留的问题就是,Pod 和物理机对接收到 SACK 后的行为为何会不一致?

5. 为何收到 SACK 数据包的行为不一致

从 netstat -s 的命令输出来看,可以看出双方重传的差别,如图 3 所示:

图 3 重传的原因

Pod 容器的 TCPSackRecovery 和 TCPLossProbes 导致重传的次数差别不大,而物理机上,绝大部分的重传都是由 TCPLossProbes 引起的,TCPSackRecovery 只是 TCPLossProbes 的 1/20 左右。如果我们可以减少 Pod 容器的 TCPSackRecovery 比例,那么就能大大降低数据的重传。

在 TCP 协议栈中,通过 FACK,SACK,RACK 等算法来决定是否对 TCP 数据进行重传,这些算法在众多的 RFC 文档中都有详细定义,而对于开发人员来说,相对便捷的途径,是从 TCP 协议栈的实现来更快地了解 TCP 重传的多种原因。

从 TCP 协议栈的实现来看,它会调用 tcp_retransmit_skb() 函数来进行 TCP 数据包的重传,于是大概浏览了协议栈代码,整理出函数的调用关系图,如图 4 所示:

图 4 TCP 重传函数调用路径

如果在调用函数 tcp_retransmit_skb() 的地方,打印出函数调用的协议栈,那么可以很清楚地看到是因哪个路径而引起的重传。这类数据如果将函数调用栈打印出来,就是图 5 显示的情况:

图 5 重传的函数调用栈

在 tcp_ack() 中调用 tcp_xmit_recovery 来进行重传,期间需要调用多个函数进行 TCP 链接是否要进入 recovery 状态的判断。直接的栈调用关系,体现不出这种判断的具体细节,也不知道 TCP 链接进入 recovery 状态的具体原因。因此需要继续查看代码,进行更深入的分析。

在 tcp_xmit_recovery() 中,会根据 rexmit 的值来判断是否会调用 tcp_xmit_retransmit_queue()。而决定 rexmit 值的地方,就在函数 tcp_fastretrans_alert() 中。该函数也会有多个路径来决定是否给 rexmit 赋值。 因此,基于以下的逻辑及 eBPF,我们设计了一个重传的定位工具

  • 如果数据重传,打印出重传的栈,就可以判断出数据包的重传究竟是由于输入的数据引起的,还是 timer 超时引起的。
  • 针对输入数据引起的重传,我们需要知道输入数据的具体信息,以及重传数据的具体信息。这些信息包含数据的源 IP,目的 IP,tcp sequence,ack sequence,长度,tcp flag 字段等。
  • 在此基础上,我们需要知道输入数据包在协议栈中被哪些函数处理。因此利用 kprobe,探查了 tcp_ack(),tcp_fastretrans_alert() 里面的多个函数。如果某个函数不能被 kprobe,那么就需要 kprobe 函数调用的子程序。这些 kprobe 的函数通过 tcp 的 socket 和当前的 PID 为 key 的 eBPF map 进行关联,将调用到的函数存储到 map 中。在函数重传的时候,将该信息通过 event 发送给用户态。
  • 针对 tcp_fastretrans_alert(), 将函数的输入参数以及当前 tcp socket 的一些字段信息进行打印,例如 prior_snd_una, is_dupack ack_flag, tp->packets_out,tp->sacked_out,icsk->icsk_ca_state,tp->snd_una, tp->reordering , tp->mss_cache 等信息也一并计入到 map 中。

运行工具后,当数据发生重传时,我们可以获取到以下三类信息, 详情如图 6 所示:

  • 输入数据包的信息和重传数据包的信息。
  • 数据包的函数处理函数。
  • 数据包的处理协议栈。

图 6 抓取的数据包重传信息

根据 tcp_fastretrans_alert () 的函数调用信息,以及该数据包调用到的函数,不难推测出该数据因为触发了 tcp_force_fast_retransmit()而引起了重传。在 tcp_force_fast_retransmit()中,会进行图 7 的判断:

图 7 tcp_force_fast_retransmit() 函数

该处判断此 TCP 链接的 Highest sacked seq - unacknowledged seq >= reordering * MSS 是否满足条件,如果满足条件的话,则会进行重传操作。可以很明显看到,tp->reordering 就是决定是否进行重传的关键因素。因此通过 ss -i,查看了下所有链接的 tp->reordering 值,如图 8 所示:

图 8 TCP 重传的统计

在 ss -i 命令的输出结果中,如果 tp->reordering 等于默认值 3,那么就不会有值打印出来,而如果 tp->reordering 不等于 3,则会打印出来具体的值。从结果可以看出,因为链接的客户端和网络环境不同,容器环境内 SLB 和物理机器环境内 SLB 的 TCP 链接,相互之间的 tp->reordering 值差别很大。

从 tcp_force_fast_retransmit 的判断条件来看,tp->reordering 值越高,越不容易发生重传,这也是在抓包的时候,为什么同时收到 SACK 数据,容器发生重传,而物理机节点不发生重传的原因。至于 tcp->reodering 如何更新,网络上已经有很多的分析文章,这里不再赘述。

三、解决方案

在 TCP 链接建立的时候,tp->reordering 默认值是从 /proc/sys/net/ipv4/tcp_reordering(默认值为 3)获取的。之后根据网络的乱序情况,进行动态调整,最大可以增长到 /proc/sys/net/ipv4/tcp_max_reordering (默认值为 300) 的大小。

在网卡 TSO 开启的情况下,会发送超过 3 个 MSS 的数据包,如果采用 reordering 值为 3,稍微的乱序都可能导致重传,而很多的重传都是没有必要的。 因此尝试修改 /proc/sys/net/ipv4/tcp_reordering 的默认值为 6,以修复因为乱序而导致无谓重传的问题。 经修改后,从 netstat -s 的结果来看,TCPSackRecovery 的统计明显降低了,因此重传率也降低了,如图 9 所示。

图 9 tcp 重传率在增大 reordering 值后下降

需要注意的是:

  • 新建的 network namespace 的 /proc/sys/net/ipv4/tcp_reordering 默认值是 3,不会和 host network namespace 设置的值保持一致。
  • 监听端口的 socket 创建后,该 socket 上的后续链接,都会默认使用该 socket 的 tp->reordering 作为初始值,而不会动态从 /proc/sys/net/ipv4/tcp_reordering 里读取,因此,该值需要在监听端口的 socket 创建之前进行设置。

四、网络重传篇小结

通过 netstat -s 显示的 MIB 统计是该 network namespace 下所有链接统计的总和。在类似 SLB 这种具有几万个 TCP 链接的场景下,还是需要像 BCC 这样的 tcpretrans.py 工具来寻找重传率比较高的客户端,这样可以将问题收敛到某些链路上。

与丢包问题不同,重传原因在 TCP 内核协议栈中的 MIB 记录点明显偏少,因此也不容易直接通过 netstat -s 显示的 MIB 信息来获取,而这可以通过 eBPF 工具来抓取内核中对数据包的处理路径进行针对性分析。除了像本案例场景的参数调优以外,内核处理路径的抓取还可以输出数据包在协议栈内的信息,也能进一步帮助我们后续定位协议栈方面的代码问题。

eBay 云计算“网”事三部曲总结

网络环境作为云计算里面的重要一环,经常会发生各种各样的问题,也是用户反馈较多的问题点。本系列的三篇文章,从 网络延迟,网络丢包,网络重传 三个方面,针对网络的常见问题进行分析,均为线上真实案例。这些案例并不是我们处理过的最困难的问题,却是在网络环境中最典型的问题,因此拿来作为例子详细讲述。现在回头来看,当初定位问题的思路或方式还有很多可以改进的地方,但是认知和经验的积累从来不是一步到位的,因此定位的方法也会不断地修正和改进。

Linux 内核相关的问题定位一直都是云环境中的痛点之一,需要定位人员有丰富的知识积累以及相关经验。在关于 Linux 内核的问题定位上,一般都是根据经验和现场猜测怀疑点,不断修改参数重试或者借助搜索引擎来解决。该方式费时费力,并且解决问题的方法方式往往不可重复和扩展。下次碰到同一模块的问题,往往还是需要重来一遍。

在意识到这样的问题后,我们希望能够借助 eBPF 等手段,力求将内核白盒化,尽量能够从对代码的跟踪上找出问题的原因。 该方式在刚开始的时候,可能会比较痛苦,因为需要详细了解相关问题涉及到的 Linux 模块实现,并针对性形成该模块问题定位的方式以及工具集合,比较耗时耗力,但是只要定位该模块的方式形成后,就会达到磨刀不误砍柴工的效果。有时候,最难走的路,往往才是捷径。

本文转载自公众号 eBay 技术荟(ID:eBayTechRecruiting)。

原文链接

eBay 云计算“网”事|网络重传篇

2020 年 10 月 01 日 10:00 1410

评论

发布
暂无评论
发现更多内容

ARTS打卡Week 12

teoking

第五周作业

Vincent

极客时间 极客大学

区块链激励层——区块链生态建设的驱动力量

CECBC区块链专委会

区块链技术 驱动力量

布式系统消息异常该何去何从

架构师修行之路

分布式 异步

Mysql学习笔记:InnoDB事务和ACID模型

马迪奥

MySQL innodb

重新学习了一遍ThreadLocal

熊斌

学习总结

不使用Raft算法,就能简单做集群leader选举

架构师修行之路

分布式 架构师

洗牌算法

C语言与CPP编程

c c++ 算法 编程语言

华为与第四范式,正在酝酿一个帮企业跳出AI悖论的“秘密计划”

脑极体

浮点数的秘密

C语言与CPP编程

c c++ 编程语言 浮点数

架构师训练营第十四周总结

张明森

计算机的时钟(三):向量时钟

ElvinYang

CString 类的线程不安全问题

C语言与CPP编程

c c++ 编程语言

第五周总结

Vincent

极客时间 极客大学

C语言指针详解

C语言与CPP编程

c c++ 编程语言 指针

安全相关总结

简述C语言宏定义的使用

C语言与CPP编程

c c++ 编程语言

为什么互联网巨头们纷纷使用Git而放弃SVN?(内含Git核心命令与原理总结)

冰河

git 冰河 代码管理 代码仓库 分支合并

区块链应用层——生态体系的上层建筑

CECBC区块链专委会

区块链技术 生态体系

从一段 Dubbo 源码到 CPU 分支预测的一次探险之旅

yes的练级攻略

dubbo cpu

一文带你了解微服务架构和设计(多图)

Phoenix

架构 分布式 微服务

导致系统不可用原因及密码验证

03 Spring Security 入门实例

哈库拉玛塔塔

Spring Boot kotlin spring security

智能商业时代的思考(二)网络协同抓住用户

刘旭东

微信 商业价值 数据智能 网络协同 商业智能

记录问题 INSERT INTO table ... SELECT ... FROM dual WHERE not exists (...)问题

浅^安

sql SQL语法 sql查询

spark总结

认证、授权、鉴权和权限控制

哈库拉玛塔塔

spring security 用户权限 鉴权 权限

Spring Security 主要类解释

哈库拉玛塔塔

springsecurity

ARTS Week16

时之虫

ARTS 打卡计划

【高并发】面试官:讲讲什么是缓存穿透?击穿?雪崩?如何解决?

冰河

缓存 面试 穿透 击穿 雪崩

以大数据为依托提升基层治理效能

CECBC区块链专委会

大数据 信息化管理

eBay云计算“网”事|网络重传篇-InfoQ