webrtc的某个版本中实现BBR算法,后来被移除了。但是没有给出原因。
这一个多月,我在仿真环境中[1]测试了BBR算法,在某些测试环境中竟然可以观测到超过50%的丢包。正常的BBR算法一般也就2%~3%的丢包,50%的丢包率属实有点变态。
到这里,可能得出结论,BBR不适用于webrtc,就完事了。寻找原因,耗时多而又功劳少。幸亏我是临时工,没有kpi的要求。
我原来觉得是quic中的BBR算法存在瑕疵,后来移植了TCP BBR的代码到webrtc中,在一些测试例子中,看到了正常的工作曲线。但是换了一些网络链路测试参数(带宽,时延),就有不正常的结果。
什么原因导致了这么高的丢包?
BBR算法在多个流争抢带宽的条件下,网络中的时延不可避免地增加。参看链接[2]的测试图片。在互联网这种数据转发模式下,不管什么拥塞控制算法,只要它要探测更高的带宽,总会在某个时刻导致网络传输时延的增长。
BBR控制发包节奏的因子:congestion_window(cwnd)和pacing_rate。在webrtc中,还有另一个因素,编码器的目标码率(target_rate)。参考链接[3]给出的源码,webrtc中实现的BBR算法按照窗口滤波器的值配置target_rate。
NetworkControlUpdate BbrNetworkController::CreateRateUpdate(
Timestamp at_time) const {
DataRate bandwidth = BandwidthEstimate();
if (bandwidth.IsZero())
bandwidth = default_bandwidth_;
TimeDelta rtt = GetMinRtt();
DataRate pacing_rate = PacingRate();
DataRate target_rate =
config_.pacing_rate_as_target ? pacing_rate : bandwidth;
// ....
// ....
update.congestion_window = GetCongestionWindow();
return update;
}
在PROBE_BW,cwnd = 2 * BandwidthEstimate() * min_rtt_。当往返时延(smooth_rtt)增加时,可能会出现这样这一种现象:BandwidthEstimate() > (cwnd / smooth_rtt)。这个时候,编码器target_rate大于(cwnd / smooth_rtt),cwnd成为瓶颈。当网络中飞行的数据包大于cwnd,pacer暂停向网络中发送数据包。会限制数据包向网络中的发送速度。尽管PacingController中的的pacing rate>=target_rate,由于cwnd的限制,PacingController会暂停向网络中发送数据包。
absl::optional<bool> RtpTransportControllerSend::GetCongestedStateUpdate()
const {
bool congested = transport_feedback_adapter_.GetOutstandingData() >=
congestion_window_size_;
if (congested != is_congested_)
return congested;
return absl::nullopt;
}
void PacingController::SetCongested(bool congested) {
if (congested_ && !congested) {
UpdateBudgetWithElapsedTime(UpdateTimeAndGetElapsed(CurrentTime()));
}
congested_ = congested;
}
编码器生成的数据包,只能在PacingController缓存,造成 packet_queue_数据包排队时延的增长。当时延超过一定的阈值时候,PacingController完全不按照BBR设置的速率控制发包节奏。为了限制最大的排队时延,PacingController存在超发机制。根据期望时延,重新计算发包速率(adjusted_media_rate_ )。比如根据我的日志,BBR要求pacing_rate为3Mbps,而PacingController最终决定的发送速率可能是6Mbps。
void PacingController::MaybeUpdateMediaRateDueToLongQueue(Timestamp now) {
adjusted_media_rate_ = pacing_rate_;
if (!drain_large_queues_) {
return;
}
DataSize queue_size_data = QueueSizeData();
if (queue_size_data > DataSize::Zero()) {
// Assuming equal size packets and input/output rate, the average packet
// has avg_time_left_ms left to get queue_size_bytes out of the queue, if
// time constraint shall be met. Determine bitrate needed for that.
packet_queue_.UpdateAverageQueueTime(now);
TimeDelta avg_time_left =
std::max(TimeDelta::Millis(1),
queue_time_limit_ - packet_queue_.AverageQueueTime());
DataRate min_rate_needed = queue_size_data / avg_time_left;
if (min_rate_needed > pacing_rate_) {
adjusted_media_rate_ = min_rate_needed;
RTC_LOG(LS_VERBOSE) << "bwe:large_pacing_queue pacing_rate_kbps="
<< pacing_rate_.kbps();
}
}
}
BBR的工作机制和webrtc的pacer原理,使得BBR算法完全处于失控状态,造成了极其变态的丢包率。
如果关掉超发机制(drain_large_queues_ = false),就要忍受PacingController排队时延的不断膨胀。过高的时延是RTC类业务要尽力避免的。
所以,BBR算法被移除,还是不冤枉的。
那么,如何改进。
这两周来,我一直在魔改算法参数,希望能够得到一个正常结果。比如在probe_cruise时,我设置target_rate 为0.95 * BandwidthEstimate()。在一些测试中,能够观测到正常的结果,在其他场景中,就不行。
我尝试去掉拥塞窗口的限制。在几个测试例子里,算法测试还算正常。
主要更改点:
- 参考BBR v2,每5秒进入一次probe rtt,设置窗口为BDP/2。算法只在probe rtt限制拥塞窗口值:
if (PROBE_RTT == mode_) {
update.congestion_window = ProbeRttCongestionWindow();
} else {
update.congestion_window = DataSize::Infinity();
}
- 在probe down 状态(pacing_gain = 0.75),设置编码器的目标码率为0.5 * BandwidthEstimate()。
关于拥塞控制算法在实时通信领域的优化,我总结了一篇论文,贴到了arxiv:https://arxiv.org/pdf/2409.10042
Refer:
[1] webrtc-gcc-ns3
[2] quic-on-ns3
[3] bbr in webrtc