网络流控的概念与背景
1、为什么需要网络流控
首先我们看上面这张最精简的网络流控的图,Producer 的吞吐率是 2MB/s,Consumer 是 1MB/s,这个时候我们就会发现在网络通信时我们的 Producer 的速度是比 Consumer 要快的,有 1MB/s 的速度差,假定我们两端都有一个 Buffer,Prodecer 端有一个发送用的 Send Buffer,Consumer 端有一个接收用的 Receive Buffer,在网络端的吞吐量是 2MB/s,过了 5s 后我们的 Receive Buffer 可能就撑不住了,这时候会面临两种情况:
- 如果 Receive Buffer 是有界的,这时候新到达的数据就只能被丢弃掉了。
- 如果 Receive Buffer 是无界的,Receive Buffer 会持续的扩张,最终会导致 Consumer 的内存耗尽。
2、网络流控的实现:静态限速
为了解决这个问题,我们就需要网络流控来解决上下游速度差的问题,传统的做法可以在 Producer 端实现一个类似 Rate Limiter 这样的静态限流,Producer 的发送速率是 2MB/s,但是经过这一层后,往 Send Buffer 去传数据时就会降到 1MB/s 了,这样的话 Producer 端发送速率跟 Consumer 端处理速率就可以匹配起来了,就不会导致上述问题。但是这个解决方案有两点限制:
- 事先无法预估 Consumer 到底能承受多大的速率;
- Consumer 的承受能力通常会动态地波动。
3、网络流控的实现:动态反馈/自动反压
针对静态限速的问题我们就演进到了动态反馈(自动反压)的机制,我们需要 Consumer 能够及时的给 Producer 做一个 feedback,即告知 Prodecer 能够承受的速率是多少。动态反馈分为两种:
- 负反馈:接收速率小于发送速率时发生,告知 Producer 降低发送速率;
- 正反馈:发送速率小于接收速率时发生,告知 Producer 可以把发送速率提上来。
让我们来看 Spark Streaming 反压实现案例:
Spark Streaming 里也有做类似这样的 feedback 机制,上图 Fetcher 会实时的从 Buffer、Processing 这样的节点收集一些指标然后通过 Controller 把速度接收的情况再反馈到 Receiver,实现速率的匹配。
疑问:为什么 Flink (before V1.5) 里没有用类似的方法实现 feedback 机制?
首先在解决这个疑问之前我们需要先了解一下 Flink 的网络传输是一个什么样的架构。
这张图就体现了 Flink 在做网络传输时基本的数据流向,发生端在发送网络数据前要经历自己内部的一个流程,会有一个自己的 Nework Buffer,在底层用 Netty 去做通信,Netty 这一层又有属于自己的 ChannelOutbound Buffer,因为最终是要通过 Socket 做网络请求的发送,所以在 Socket 也有自己的 Send Buffer,同样在接收端也有对应的三级 Buffer。学计算机网络时应该了解到,TCP 是自带流量控制的。实际上 Flink (before V1.5) 就是通过 TCP 的流控机制来实现 feedback 的。
TCP 流控机制
根据下图我们来简单的回顾一下 TCP 包的格式结构。首先,他有 Sequence number 这样一个机制给每个数据包做一个编号,还有 ACK number 这样一个机制来确保 TCP 的数据传输是可靠的,除此之外还有一个很重要的部分就是 Window Size,接收端在回复消息的时候会通过 Window Size 告诉发送端还可以发送多少数据。