LeeYzero的博客

业精于勤,行成于思

0%

端到端设计原则

最近又看了一遍 End-To-End Argument In System Design,有一些新的思考,但都集中在一些点上,不够系统,在此先记录下来,后续有新的想法再做补充和整理。

核心观点

这篇论文在由MIT计算机科学实验室发表于1984年,是一篇非常经典的关于系统设计的论文。论文论证了分布式系统设计的一个事实:端到端的可靠通信只能由通信的两端(End-Point)来保证,而中间的媒介(如消息中间件、网关、路由器以及TCP协议栈等)只能提高通信效率,但无法保证通信两端的可靠性。

在一个系统设计中,系统设计师的主要工作是对功能进行合理抽象,明确定义功能边界。将功能职责进行合理划分的设计原则是系统设计师最重要的工具之一,端到端设计原则便是这样的工具,它在分布式系统设计中,对功能职责的划分起了一定的指导作用。

举例说明

论文中给出了一个两个主机间的文件传输为例来说明为什么端到端的可靠通信只能由通信的两端来保证。考虑要实现一个应用程序,用于将主机A上的文件传输到另一个远程主机B上。整个过程可以抽象以下5个步骤:

1、在主机A上的应用程序按固定大小的块从主机A的磁盘中读取文件。

2、在主机A上的应用程序将数据通过数据通信系统进行传输,数据通信系统使用某种数据传输协议,将数据拆分成数据包进行传输。

3、数据通信网络将数据从主机A传输至主机B。

4、在主机B上的应用程序从数据通信系统中按相册协议读取出数据。

5、在主机B上的应用程序将接收到的数据写入主机B的磁盘上。

在这个模型中,任何一步出错都会造成传输失败,主要有以下问题:

1、假定文件被正确存储在A主机上,读取的时候,由于磁盘损坏等原因可能造成读取的数据不正确。

2、在主机A或主机B上的应用程序,由于程序缺陷(比如拷贝数据错误等)造成数据错误。

3、处理器或内存在拷贝、缓存时可能出现暂时的错误。

4、通信过程可能出现位错误、丢包或重传等问题。

5、在传输过程中主机可能出现崩溃。

对于这个例子,如果我们先抛开两端(也就是主机A和主机B上的应用程序),在两端之间的中间媒介,如磁盘、内存、数据通信系统等即使通过冗余、错误检查、超时重试、故障恢复等手段保证其可靠性,仍然不能保证上述第2点出问题,也就是端本身造成的数据错误,最终还是需要端做相同的事情来保证可靠性。

如果对上面的例子还是没理解,我再举一个现实生活中的例子,比如A跟B打电话,A告诉B他中彩票了。我们把A跟B当端点,在这个过程中,如果因为A或B的手机信号问题,造成B没有听清楚,那么这相当于是中间媒介故障造成这次通话异常;现在假设A和B的手机以及周围信号都没有问题(通过各种手段保证中间媒介的可靠性),A说的话能正常传达了B的手机(注意是B的手机),那是不是B就一定听到了呢?也不一定,有可能B此时正在想别的事情,或被其它人打扰而在大脑中没有接收到B传达的信息,这种情况就相当于上述两端的应用程序缺陷。此时B可以对A说,不好意思,没听清楚,你再说一遍,A重新说了一遍后B得知了A中彩票的消息,这种情况相当于上述说的端来保障通信可靠性。

通过以述例子,我们知道端到端的通信只有两端才能保证可靠性,中间媒介层去做可靠性保证显得多余了,那是不是说中间媒体层就不需要做可靠性保证了呢?这就引出论文的另一个观点:中间媒介只能提高通信效率,而不能保证通信两端的可靠性。那如何理解这句话呢?

就拿上述文件传输的例子,如果在一个网络非常差的环境下传输一个大文件,如果只靠端来保证可靠性的话,失败率必定非常高(通常来说,应用程数据分片拆分比网络传输的数据包大很多)。但如果在数据通信系统中(如TCP协议栈)支持数据超时重传、数据校验,那就会极大提升端系统的传输成功率。

正确识别端

那么怎么界定端呢?这个就需要根据实际需求来取舍了。比如对一个语音传输系统,如果端是人的话,是应该优先保证实时性,再考虑正确性,在传输过程中是可以容忍少许包损坏或噪音的;但如果端是一个录音程序,则需要优先考虑正确性,再考虑实效性,可以容忍延迟,但不能容忍包损坏或错误。

一些思考

1、这篇论文对TCP/IP分层协议设计影响深远,分层增强了模块化,界定每个层次的功能职责,也就是降低了整体的复杂度。纵观整个软件发展的历史,其实就是在降低软件的复杂度,分层、模块化、设计模式、DDD、封装思想无一不是在解决软件复杂性这个问题。用Unix设计哲学来说就是KISS(Keep It Simple and Stupid)。

2、设计的本质就是做trade-off,端到端设计原则只是提供一种指导思想,就想正确识别端中列举的例子,需要根据具体需求,更多的是根据现有资源,人力、物力、市场、基础设施等去做平衡来博取最大化ROI。

3、知乎 上提到一种克服端到端的方法(其实本质上没有逃脱端到端原则),通过反转中间件和APP的角色(无处不在的依赖倒置原则),让中间件变成End-Point,当APP通过注入(inject)的方式成为中间件框架中的一部分逻辑。

参考资源

[1] End-To-End Argument In System Design
[2] End to End Argument(可能是最重要的系统设计论文)