<audio id="audio" title="26 | 应用层多播：如何快速地分发内容？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/3f/37/3feb9595eea01aa4e439c2eec0828a37.mp3"></audio>

你好，我是陶辉。

[[第7讲]](https://time.geekbang.org/column/article/235302) 我们曾介绍了网络层的IP协议是如何支持多播的，这节课我们再来从应用层看看如何实现多播功能。

当你的分布式集群只有十多个节点时，每次发布版本时，尽可以从发布服务器，将新版本的安装包通过ftp、scp、wget等工具分发到各个节点中。可是，一旦集群规模达到成千上万个节点时，再这么做就会带来很大的问题，文件分发的时长高达几个小时，甚至会打挂文件源终止分发流程。在微服务环境中这点尤为明显， 毕竟每个Docker镜象的体积动辄就在数百兆字节以上。

虽然网络层的IP协议允许通过路由器、交换机实现高效的多播，但IP层很难实现文件的可靠传输，而且跨越多个局域网时路由器等网络设备对IP多播的支持也不好。此时，通过应用层的接力传播，就能通过多播思想大幅提升系统的传输效率，解决上述问题。

除了分发文件场景外，应用层多播协议也常用于完全去中心化的分布式系统，特别是在管理成千上万个节点的状态时非常有用。比如Gossip就是这样一个多播协议，[[第22讲]](https://time.geekbang.org/column/article/254600) 介绍过的Cassandra数据库使用它来同步节点间的状态，比特币通过它传输账本数据，Redis集群也使用它同步Redis进程间的状态。

那么这节课我们就重点介绍应用层中的多播协议，并以阿里的蜻蜓、Cassandra中的Gossip协议为例，看看它们的工作原理。

## 认识应用层的多播协议

之所以需要应用层的多播协议，是因为网络层的IP多播功能（参见[[第7讲]](https://time.geekbang.org/column/article/235302) 的IP广播与组播）有以下4个方面的问题：

- 从功能上看，IP多播缺失了质量控制、可靠性传输等特性，无法满足绝大部分场景中的要求；
- 从管理上看，监控跨网络的多播报文并不容易，多播地址的管理也很复杂，而且多播很容易造成网络洪峰及安全问题；
- 从市场上看，单播报文在终端上的计费很成熟，相反，运营商对多播报文的计费要困难许多，相对缺乏推进动力；
- 从产业协作上看，IP多播必须由各设备厂商的路由器、交换机配合，由于网络层是由内核实现的，所以还要同步升级操作系统。

这些因素都使得网络层的多播功能难以推广，因此，各类基于IP单播功能的应用层多播协议应运而生。可能有些同学对于单播、多播这些网络知识还不熟悉，下图我将传统单播、网络层多播、应用层多播放在一起对比着看，你可以很容易看到它们的区别。

这里仍然以版本发布场景为例，主机1是发布节点，它需要将文件分发到其余3台主机上，其中传统单播协议的玩法，是在主机2、3、4上分别向主机1发起下载文件命令，因此主机1上有3条TCP下载链路，图中我用3种不同的颜色表示。很明显，此时主机1的下行流量一定会成为瓶颈，而且随着集群规模的扩大，网络链路中的路由器也会很快达到流量瓶颈。

<img src="https://static001.geekbang.org/resource/image/34/7a/3457fac0f6811d21357188c48fe5867a.png" alt="">

再来看效率最高的网络层多播，主机1可以仅通过1次报文传输，将文件分发到所有主机上。在整个流程中，仅由路由器将网络报文扩散到相邻的主机、路由器上，没有任何多余的动作。如同上述所说，网络层多播的使用有很多困难，这里列出它是为了方便与应用层多播作对比。

在上图的应用层多播中，文件传输分为两个阶段。首先，主机2、3直接从主机1中下载文件（参见绿色与红色线）。其次，当主机3完成下载后，主机4再从主机3上下载文件（参见蓝色线）。可见，相对于IP多播，**应用层多播有以下3个缺点：**

首先网络效率下降了不少，这由2个原因所致：

- 数据在网络中有冗余，比如主机1将数据重复发送了2次（参见红色、绿色线），主机3既接收了一次数据，也发送了一次数据；
- 虽然图中主机4从同一局域网中的主机3下载数据，但在复杂的互联网环境中，应用层很难掌握完整的路由器组网信息，主机4一旦跨网络从主机2上下载数据，这就增加了路由器A及网络链路的负载，效率进一步下降。

其次，由于传输路径变长了，文件传输的总完成时间也变长了。比如，主机4的总传输路径是：主机1 -&gt; 路由器A -&gt; 路由器B -&gt; 主机3 -&gt; 路由器B -&gt; 主机4，既多出了主机3、路由器B这两个传输环节，而且进入主机3后，协议栈的处理深度也增加了。

最后，单次传输路径中引入了功能复杂的主机，相比仅由网络设备参与的IP多播，可靠性、稳定性也降低了。

说完缺点，我们再来看应用层多播的优点。

- 首先，它回避了IP多播的问题，无须改变现有组网环境，也不需要管理组播IP地址，立刻就可以应用在当下的生产环境中；
- 其次，在数以万计的大规模集群下，单一发布源很容易被流量打爆，进而导致分发流程停止，应用层多播可以避免这一问题；
- 再次，通过应用层节点的接力分发，整个传输带宽被大幅度提高了，分发速度有了数量级上的飞跃；
- 最后，如果分发集群跨越不同传输成本的网络（比如多个区域IDC构成的集群），在应用层也很容易控制分发策略，进而减少高成本网络的数据传输量，提升经济性。

所以，综合来说，**集群规模越大，应用层多播的优势也越大。**实际上十多年前，我们在使用BT、迅雷下载时，就已经接触到应用层多播协议了，接下来我们结合2个服务器端的案例，看看多播协议的实现与应用。

## 应用层多播协议是如何工作的？

其实，应用层多播主要是指一种[P2P（Peer to Peer）](https://zh.wikipedia.org/wiki/%E5%B0%8D%E7%AD%89%E7%B6%B2%E8%B7%AF)网络传输思想，任何基于IP单播的通讯协议都可以拿来使用。比如针对于在分布式集群中分发软件安装包的场景，完全可以使用HTTP协议实现应用层的分发，阿里巴巴开源的[Dragonfly蜻蜓](https://github.com/DarLiner/Dragonfly)就是这么做的。

蜻蜓拥有1个中心化的集群服务节点：SuperNode，其中既可以直接保存着源文件，也可以通过HTTP缓存加速第三方文件源（比如Docker仓库）。集群中的每个节点都要启动dfget进程（替代了传统的wget），它就像我们平时使用的迅雷，在下载文件的同时，也会将自己下载完成的文件传输给其他节点。其中，通过HTTP的Range文件分段下载规范，dfget就可以实现断点续传、多线程下载等功能，如下图所示：

[<img src="https://static001.geekbang.org/resource/image/2f/00/2fbba7f194bd3bcabded582467056700.png" alt="" title="图片及下图来源：https://github.com/DarLiner/Dragonfly/blob/master/docs/zh/architecture.md">](https://github.com/DarLiner/Dragonfly/blob/master/docs/zh/architecture.md)

各个dfget程序间通过SuperNode协调传输关系，这样绝大部分dfget程序并不需要从SuperNode节点下载文件。比如上图中的节点C，通过HTTP Range协议从节点A中下载了文件的第1块，同时并发地从节点B中下载了文件的第2块，最后再把这些Block块拼接为完整的文件。

这里你可能会想，这不就是一个P2P下载工具么？是的，但你站在集群运维的角度，这就是基于应用层多播协议的文件分发工具。当每个节点部署dfget服务后，新版本安装包发布时，就可以由SuperNode节点推送，经由各个dfget进程以多播的形式分发下去，此时性能会获得大幅度的提升。

<img src="https://static001.geekbang.org/resource/image/36/82/36de22b49038a6db2b8bc7ce953e5c82.png" alt="">

上图是传统的wget单播与蜻蜓多播分发文件的性能对比图。我们可以看到，传统方式下，**分发客户端越多（Y轴）总分发时长（X轴）就越大，特别是1200个以上的并发节点下载文件时，会直接将文件源打爆。**而采用应用层多播方式后，下载时长要低得多，而且伴随着节点数的增加，下载时长也不会增长。

蜻蜓虽然传输效率很高，但SuperNode却是保存着全局信息的中心节点，一旦宕机就会造成系统不可用。我们再来看去中心化的[Gossip流言协议](https://en.wikipedia.org/wiki/Gossip_protocol)是如何实现应用层多播的。

Gossip协议也叫epidemic传染病协议，工作原理如下图所示。在这个分布式网络中，并没有任何中心化节点，但只要第1个种子节点被感染为红色后，每个节点只需要感染其相邻的、有限的几个节点，最终就能快速感染网络中的所有节点（即仅保证最终一致性）。

<img src="https://static001.geekbang.org/resource/image/fa/b5/fa710dc6de9aa9238fee647ffdb69eb5.gif" alt="">

当然，所谓的“感染”就是数据的传输，这一算法由1987年发布的《[Epidemic algorithms for replicated database maintenance](http://bitsavers.trailing-edge.com/pdf/xerox/parc/techReports/CSL-89-1_Epidemic_Algorithms_for_Replicated_Database_Maintenance.pdf)》论文提出，同时证明了算法的收敛概率。Cassandra数据库、Fabric区块链、Consul系统等许多去中心化的分布式系统，都使用Gossip协议管理集群中的节点状态。以Cassandra为例，每秒钟每个节点都会随机选择1到3个相邻节点，通过默认的7000端口传输包含节点状态的心跳信息，这样集群就可以快速发现宕机或者新增的节点。

[<img src="https://static001.geekbang.org/resource/image/79/fe/7918df00c24e6e78122d5a70bd6bd2fe.png" alt="" title="图片来源：https://www.linkedin.com/pulse/gossip-protocol-inside-apache-cassandra-soham-saha">](https://www.linkedin.com/pulse/gossip-protocol-inside-apache-cassandra-soham-saha)

## 小结

这一讲我们介绍了应用层的多播协议。

网络层的IP多播功能有限，对网络环境也有过多的要求，所以很难通过多播协议提升传输效率。基于IP单播协议（如TCP或者UDP），在应用代码层面实现分布式节点间的接力转发，就可以实现应用层的多播功能。

在分布式集群的文件分发场景中，阿里开源的[Dragonfly蜻蜓](https://github.com/DarLiner/Dragonfly)可以将发布节点上的源文件，通过HTTP协议推送到集群中的每个节点上，其中每个节点在应用层都参与了多播流量分发的实现。当节点数到达千、万级时，蜻蜓仍然能保持较低的分发时延，避免发布节点被下行流量打爆。

在完全去中心化的分布式集群中，每个节点都没有准确的全局信息，此时可以使用Gossip流言协议，通过仅向有限的相邻节点发送消息，完成整个集群的数据同步，实现最终一致性。因此，Gossip协议常用于大规模分布集群中的节点状态同步。

## 思考题

最后，留给你一道讨论题。在5G完成设备层的组网后，类似华为NewIP这样的基础协议层也在做相应的重构，其中[Multicast VPN协议](https://support.huawei.com/enterprise/en/doc/EDOC1000173015/e0de8568/overview-of-rosen-mvpn)就将现有IPv4无法在公网中推广的多播功能，在VPN逻辑链路层实现了。你对未来多播协议的发展又是如何看的？欢迎你在留言区与大家一起探讨。

感谢阅读，如果你觉得这节课让你了解到应用层的多播协议，而通过它可以大幅度提升分布式集群的网络传输效率的话，也欢迎你把今天的内容分享给你的朋友。
