“时延低的传输速率一定高于时延高的网络”,“hash表的查询操作只需要O(1)的时间”,“bbr拥塞算法优于cubic算法”。“大部分场景”都正确并不适用所有场景,但是平时大家总是将这些“大部分正常”当作永远正确来处理。然而小概率并不是不发生,就程序而言,我们写的程序逻辑在大部分场景都是正常,但是可能某些场景下就会出现逻辑异常,也就是我们常说的程序bug。

今天我要说的delay_ack导致的bbr速率异常问题也是一种“小部分场景”异常的问题。在推动某头部直播客户上云的过程中,客户投诉卡顿率飙升,主要表现是出现速率突然掉底的问题,比如下图是随时间吞吐量的变化图,4分钟的时间内出现三次速率突然抖降的现象。

整个传输链路较多,经过逐级定位发现住传输卡在流,推流和解码部分使用两台虚拟机来进行功能隔离,也就是两台虚拟机之间的传输问题。

我司机房的两台虚拟机之间的RTT时间非常的短,实测在60~120us。而友商A机房的数据对比,普遍在200us~300us,是我们的两倍。另一家厂商B,时延在300us~2ms之前。据客户反馈,友商A也会发现这种码率掉底的问题,只是触发概率很低,而友商B几乎不发生。看着是“时延越低,越容易触发”的异常,于是使用tc命令分别添加了200us和1ms的时延进行测试,200us的时延场景下推流4分钟,偶尔发生一次掉底现象。而时延增加1ms后,基本不发生码率掉底问题。

看似通过使用tc命令增加时延就能解决这个问题,但解决问题需要“知其然知其所以然”的态度,技术研究不能总想着找到规避方案,更需要发现其深层本质原因。

bbr拥塞算法正常情况下,只有两种情况下会主动抑制发送的行为,一种是检测到有规律的丢包进入到long-term状态,另一种是长时间min_rtt不更新进入到probe_rtt状态。但显然我们这个场景并不符合这两种case,long-term需要丢包,虚拟机内部传输基本没有丢包发生。probe_rtt状态的退出条件是一轮时间&200ms的时间,而我们这个场景会持续好几秒的时长。于是只能使用kprobe探测bbr的运行状态,bbr_main函数是bbr的主函数,主要输入参数是通过struct rate_sample *rs指针进行传入,rate_sample结构体如下图所示,我们打印出几个关键信息,比如带宽bw,lt_use_bw,is_app_limited,interval_us等等,当然为了方便对照wireshark对应的位置,还打印了tp->snd_una变量。

struct rate_sample {u64  prior_mstamp; /* starting timestamp for interval */u32  prior_delivered;   /* tp->delivered at "prior_mstamp" */s32  delivered;     /* number of packets delivered over interval */long interval_us;   /* time for tp->delivered to incr "delivered" */long rtt_us;        /* RTT of last (S)ACKed packet (or -1) */int  losses;        /* number of packets marked lost upon ACK */u32  acked_sacked;  /* number of packets newly (S)ACKed upon ACK */u32  prior_in_flight;   /* in flight before this ACK */bool is_app_limited;    /* is sample from packet with bubble in pipe? */bool is_retrans;    /* is sample from retransmission? */
};

根据wireshark找到码率下降的时间点,此时的snd_una的值为3962813608,找到kprobe打印输出的日志对应的部分,发现了该时刻发生了带宽陡降的问题,bbr带宽被错误的更新了。而bbr带宽的更新的条件是当前的带宽采样值非app_limited,或者当前的采集值大于当前带宽,才会调用minmax_running_max去尝试更新带宽,而如果最大带宽已经很久没有更新则会被替换。

static void bbr_update_bw(struct sock *sk, const struct rate_sample *rs)
{   .../* If this sample is application-limited, it is likely to have a very* low delivered count that represents application behavior rather than* the available network rate. Such a sample could drag down estimated* bw, causing needless slow-down. Thus, to continue to send at the* last measured network rate, we filter out app-limited samples unless* they describe the path bw at least as well as our bw model.** So the goal during app-limited phase is to proceed with the best* network rate no matter how long. We automatically leave this* phase when app writes faster than the network can deliver :)*/if (!rs->is_app_limited || bw >= bbr_max_bw(sk)) {/* Incorporate new sample into our max bw filter. */minmax_running_max(&bbr->bw, bbr_bw_rtts, bbr->rtt_cnt, bw);}
}

直觉问题就变是“为何这么小的带宽采用值是非app_limited”。那就要看什么时候才会设置这个rs->is_app_limited,在接收ack会在tcp_rate_skb_delivered函数中设置,也就是发送的时候如果是app_limited型,就设置rs->is_app_limited。

void tcp_rate_skb_delivered(struct sock *sk, struct sk_buff *skb, struct rate_sample *rs)
{      ...
if (!rs->prior_delivered ||after(scb->tx.delivered, rs->prior_delivered)) {rs->prior_delivered  = scb->tx.delivered;rs->prior_mstamp     = scb->tx.delivered_mstamp;rs->is_app_limited   = scb->tx.is_app_limited;rs->is_retrans       = scb->sacked & TCPCB_RETRANS;...
}

发送数据包时,在tcp_rate_skb_sent中根据tp->app_limited来进行设置,tp->app_limited会在tcp_rate_check_app_limited函数中设置,并且只在应用层写数据中tcp_sendmsg_locked/tcp_sendpage_locked调用。具体细节不在详述,主要大意是应用层写数据的时候,发现当前已经无数据可以发送并且还有足够的拥塞窗口未使用时就会认为是app_limited,最常见场景就是所有数据都已经被确认,重新开始发送的第一轮所有的包都会被认为是app_limited类型。

void tcp_rate_skb_sent(struct sock *sk, struct sk_buff *skb)
{...TCP_SKB_CB(skb)->tx.first_tx_mstamp = tp->first_tx_mstamp;TCP_SKB_CB(skb)->tx.delivered_mstamp    = tp->delivered_mstamp;TCP_SKB_CB(skb)->tx.delivered       = tp->delivered;TCP_SKB_CB(skb)->tx.is_app_limited  = tp->app_limited ? 1 : 0;
}
void tcp_rate_check_app_limited(struct sock *sk)
{struct tcp_sock *tp = tcp_sk(sk);if (/* We have less than one packet to send. */tp->write_seq - tp->snd_nxt < tp->mss_cache &&/* Nothing in sending host's qdisc queues or NIC tx queue. */sk_wmem_alloc_get(sk) < SKB_TRUESIZE(1) &&/* We are not limited by CWND. */tcp_packets_in_flight(tp) < tp->snd_cwnd &&/* All lost packets have been retransmitted. */tp->lost_out <= tp->retrans_out)tp->app_limited =(tp->delivered + tcp_packets_in_flight(tp)) ? : 1;
}

而当设置完tp->app_limited后开始发送,有数据被确认,之后发送的数据数据包则不认为是app_limited。这也是为啥时延越小就越容易触发的原因,因为越小的时延,越容易出现发送后几个数据包的时候,已经有数据包被ack了,也就是已经开始算下一轮了。

void tcp_rate_gen(struct sock *sk, u32 delivered, u32 lost, bool is_sack_reneg, struct rate_sample *rs)
{struct tcp_sock *tp = tcp_sk(sk);u32 snd_us, ack_us;/* Clear app limited if bubble is acked and gone. */if (tp->app_limited && after(tp->delivered, tp->app_limited))tp->app_limited = 0;...
}

那是不是这个app_limited判定不合理呢?我个人觉得不是,app_limited主要衡量发送该某数据包的时刻,网络中inflight是否充足的一个概念。如果发送这个数据包时,inflight充足,那这个就是一个充足的带宽采样,至于是否发送delay_ack导致计算出来带宽过小,不在他的考虑范围。

interval_us是发送间隔send phase和接收间隔ack phase二者的最大值,正常的interval_us只有150us左右,而异常点因为delay_ack存在,造成了rcv_interval变为40ms左右,从而计算出一个很小的带宽值。bbr对这种保守的方式解释是为了防止出现接收速率高于发送速率的情形。但是并未考虑到这种delay造成的接收速率远小于发送速率的问题。为什么没有考虑呢?主要是刚好delay_ack的采样值更新带宽的概率很低,因为bw是minmax结构体,采样获得一个局部最大bw可以保持8轮的时间,并且在8轮内更新第二大bw和第三大bw,如果没有出现app_limited类型采样值。也就是发生的条件是需要前面8轮都是app_limited类型,刚好第一个app_limited类型的采样发生了delay_ack。前面一条的条件就很难满足,但是在直播场景下就非常容易满足。

那如何解决呢?不想变更bbr代码可以通过关闭delay_ack来实现,此前我一直以为关闭delay_ack需要setsockopt不断设置TCP_QUICKACK,实际路由中的metirc中可以开启RTAX_QUICACK来进入quicack模式。如果想更改代码,可以考虑忽略delay_ack的采样值,社区之后的代码中有在rs中传递进一个is_ack_delayed变量标示,可以搜索commit 41c3d35 。

delay_ack引发bbr速率掉底血案(上)相关推荐

  1. Google“谷歌”引发的王怀南血案

    Google"谷歌"引发的王怀南血案------ 从为Google取中文名的事情可以看出王怀南的不合格 Google的中文名竟然取名"谷歌",让各位网友晕倒几大 ...

  2. 终于换掉了驾驶证上的丑照!超简单附教程,赶紧收藏!

    最近终于换掉了驾驶证上的丑照!原来换照片这么简单,效率快的话24小时就可以搞定了! 大家先看看我的照片怎么样?放在下面了.之前的照片太难看了就不放了,总之比之前的照片好看一百倍(自信叉腰)~ 那么怎么 ...

  3. 甚至有些还掉到书本上

    我觉得本人很克制,我坐在第二排,无论怎么变卦,只不过一只蚊子有甚么好怕的.让我尤其的难熬痛苦.我与雨爱变得渐渐言语 人就不会觉得热了啊!雨爱,却又要装出一副什么都不懂的孩子的样子容貌,请说明:转载自 ...

  4. 肤质测试html,做完这个测试终于知道自己是什么肤质了!(内附不同肤质的持久底妆上法)...

    不少宝宝特别是化妆新手们都不太清楚自己是什么肤质. 护肤化妆不知道从何入手 建议还是小迷糊的朋友们不妨做以下测试, (珍妮丝自己测过还挺准的嘿嘿) 文章后面还给你们准备了不同肤质的持久底妆上法. 只需 ...

  5. 小米MIX2“掉包门”波及上百人,京东霸气回应:我们赔

    小米MIX2"掉包门"波及上百人,京东霸气回应:我们赔 昨天是小米MIX2上市的日子,小米在官方商场和各大经销商开通了抢购模式,89秒就全部售完.京东作为小米最大的第三方营销渠道, ...

  6. 一行代码引发的集群服务宕掉的血案分析

    本文禁止转载! 紧急处理过程 11.05号晚上接近20点,有同事反应其它服务调用XXX服务出现少量超时,看了下Cat监控,发现些许机器当时处于fullgc,因为我们的XXX服务本身在高峰期就有较为频繁 ...

  7. 品西游之馒头引起的血案(上)

    上接:品西游之大闹天宫(二) 泾河龙王因为私改降雨量.降雨时间,所以被天庭判处死刑,立即执行.表面看来,这是一个很简单的案子.但是,仔细分析,会发现里面疑点重重,甚至可能包藏见不得人的阴谋. 泾河龙王 ...

  8. 手机桌面上的计算机为什么不能删除,桌面上的东西删不掉怎么办 桌面上的东西删不掉解决方法...

    随着经济的发展,已经越来越多的人都有了电脑.使用电脑的时候大家就会遇到很多的问题,例如桌面上的程序删不掉.当你遇到着个问题的时候,你肯定就会很着急,特别想快点找到解决的办法.今天,小编就来帮你解决问题 ...

  9. 计算机网络按功能自底而上划分,大连理工大学2011计算机期末模拟题3

    1.计算机的硬件系统由五大部分组成,其中( D )是整个计算机的指挥中心. A.接口电路 B.运算器 C.系统总线 D.控制器 2.十六进制数1000转换十进制数是(D). A.8192 B.2048 ...

最新文章

  1. pandas使用groupby函数计算dataframe数据中每个分组的N个数值的滚动计数个数(rolling count)、例如,计算某公司的多个店铺每N天(5天)的滚动销售额计数个数
  2. node.js php模板,node.js中EJS 模板的使用教程
  3. 【计算机网络】数据链路层 : 信道划分 介质访问控制 ( 数据链路 | 介质访问控制分类 | 频分多路复用 FDM | 时分多路复用 TDM | 波分复用 WDM | 码分多路复用 CDM 计算 )★
  4. 玲珑杯 ACM Round #10
  5. 电脑如何进入bios模式_电脑如何进入bios关闭软驱
  6. Python基础概念_8_字符串处理
  7. c++中,可以用类名直接访问非静态成员函数?
  8. C/C++打造《百万级人脸识别系统》
  9. 代码整洁之道(一)最佳实践小结 1
  10. [翻译] UIImageView-Letters
  11. iir matlab 系数,手把手教你用matlab生成STM32官方IIR滤波器的系数
  12. 2d 蓝图_“二渲三”打破传统思维!Netflix冲奥动画会推动2D动画变革吗?
  13. illegal utf8 encoding at (190)
  14. IOS:UI设计之UISegmentedControl相关基础
  15. java编程练习题四
  16. SQL语句的各种连接查询
  17. 服务器搬迁方案_机房搬迁的一般步骤及实施方案
  18. OpenG的特点及功能
  19. iphone显示不了wifi已连接服务器,苹果手机显示已经连接wifi但是不能上网如何解决...
  20. Java深入理解深拷贝和浅拷贝区别

热门文章

  1. 波长,频率,传播距离三者的关系
  2. mac下查看文件路径
  3. windows下内网穿透工具Ngrok安装与使用
  4. 2021-08-19剑指 Offer 36. 二叉搜索树与双向链表
  5. Python 3.0 beta 1 变化大,更简洁、更统一
  6. 虚拟机没有声音,提示 “使用的设备标识号已超出本地系统范围”的解决方法
  7. 【网友评出的得分最高的100部电影】你有多少部没看过?留着找时间看咯!!
  8. SolidWorks2010常用快捷键
  9. Qt 3D教程(二)初步显示3D的内容
  10. 通过json文件将符合要求对应键的内容输出到txt文档