前言
在十几年前,我们无法想象一个分布式系统会是什么样子。它给我们带来了全新的架构思路,但同时也引入了一些问题。
当时这些系统非常少有而且架构简单,工程师们通过尽可能的减少远程交互的方式降低复杂度。解决分布式问题最好的方法是尽可能的避免分布式系统,尽管这意味着会有大量重复的逻辑分布在不同的系统上。
但是,工业需求将架构继续推进,从原先的几个集中的系统演化为成百上千的微服务。在这个新的领域,我们不得不开始摆脱困境,应对新的挑战并提出问题,从以个案方式提出临时解决方案,到使用更复杂的方法。随着我们更多地了解问题领域并设计出更好的解决方案,我们开始将一些最常见的需求纳入模式,库和平台。
我们最初进行网络通信时发生了什么?
由于人们首先考虑让两台或更多台计算机相互通信,他们想到了如下的形式:
一个服务向另一个服务通信从而实现终端用户的需求。上面的视图显然过度简化了。因为丢失了在代码操作的字节层与通过电线发送和接收的电信号层之间转换的许多其它层。但是这种程度的抽象已经足以完成本次的讨论。稍微细节一些的网络堆栈图如下:
自20世纪50年代以来,上述模型的变种一直被使用。最初,计算机很少见且价格昂贵,因此两个节点之间的每个链接都经过精心设计和维护。随着计算机变得越来越便宜和普及,连接数量和数据量急剧增加。随着人们越来越依赖网络系统,工程师需要确保他们构建的软件符合用户所需的服务质量。
为了达到理想的质量水平,还有许多问题需要解决。人们需要找到机器发现彼此的方法,通过同一条线路处理多个同时连接的方法,允许机器在没有直接连接时相互通信的方法,在网络上路由数据包,加密流量等。
其中,有一种技术叫做流量控制。我们将此作为一个例子来说明。流量控制可以防止一台服务器发送的数据包比下游服务器可以处理的数据包多。这是必要的,因为在网络系统中,至少有两台不同的独立的计算机需要进行通信,而他们彼此之间并不太了解。计算机A以给定的速率向计算机B发送字节,但不能保证B将以一致且足够快的速度处理接收的字节。例如,B可能忙于并行运行其他任务,或者数据包可能无序到达,B被阻塞等待应该先到达的数据包。这意味着不仅A无法从B获得预期性能,而且还可能使事情变得更糟,因为它可能会使B过载。因为B必须将所有这些传入的数据包排入队列以进行处理。
有一段时间,人们期望构建网络服务和应用程序的人员在他们编写的代码中处理上述挑战。在我们的流量控制示例中,它意味着应用程序本身必须包含相应逻辑,以确保我们不会因为大量数据包使服务过载。这种高度网络相关的逻辑将会和业务逻辑混杂在一起。抽象显示图如下所示:
幸运的是,技术迅速发展,很快就有标准协议,如TCP / IP,将流量控制和许多其他问题纳入网络堆栈本身。这意味着该段代码已从您的应用程序提取到您的操作系统提供的底层网络层。
这个模型取得了广泛的成功。即使在需要高性能和可靠性的场景下,也很少有组织无法使用商用操作系统附带的TCP / IP堆栈来支持业务发展。
我们刚开始使用微服务时发生了什么
多年来,计算机变得更加便宜,无处不在,上面描述的网络堆栈已经证明自己可以用来实现是可靠连接。随着更多的节点和稳定的连接,业界已经使用各种风格的分布式网络系统,从细粒度的分布式代理和对象到由重型分布式组件构成面向服务的体系结构。
这种极端的分布情况带来了许多有趣的高级的使用场景和优点,但也出现了一些挑战。其中一些挑战是全新的,而其他挑战只是原始网络时中问题的高级版本。
在就是年代,Peter Deutsch和他的同事发表了“The 8 Fallacies of Distributed Computing”。其中,他列出了人们在使用分布式系统时会作出的假设。Peter的观点是,这些可能在更原始的网络架构或理论模型中成立,但它们在现代世界中并不成立:
- 网络是可靠地
- 无延时
- 无限带宽
- 网络是安全的
- 拓扑不会发生改变
- 有一个管理员存在
- 无传输成本
- 网络是同构的
将上面的列表作为“谬误”谴责意味着工程师不能忽视这些问题,他们必须明确地处理它们。
更复杂的是,向更多的分布式系统发展后 - 我们通常称之为微服务架构 - 在可操作性方面引入了新的需求。下面我们简单的列举了一些需要解决的问题:
- 快速配置计算机资源
- 基本监控
- 快速部署
- 易于存储配置
- 易于进入边界
- 验证和授权
- 标准化的RPC
因此,尽管几十年前开发的TCP / IP堆栈和通用网络模型仍然是使计算机相互通信的强大工具,但更复杂的架构引入了另一层要求,再一次,工作的工程师必须满足这些要求来支撑这样的架构。
例如服务发现和断路器,这两种技术被用于解决上面列出的弹性分布挑战。
历史往往重演,第一批基于微服务构建系统的组织遵循的策略与前几代网络计算机的策略非常相似。这意味着处理上述要求的责任由编写服务的工程师负责。
服务发现是自动查找哪些服务实例满足给定查询需求的过程,比如,一个名为Team
的服务需要发现一个名为Player
的服务,而且该服务的环境
为生产环境
。您将调用服务发现过程,该过程将返回合适的服务器的列表。对于更多的单片体系结构,这个简单的任务,通常使用DNS,负载平衡器和一些端口号约定来实现(比如所有的服务都将HTTP服务绑定到8080端口)。在更分布式的环境中,任务开始变得更加复杂,以前可能盲目信任DNS查找以发现依赖关系的服务现在必须处理客户端负载平衡,多个不同环境(开发和线上),地理上分离部署的系统之类的事情。如果说以前只需要一行代码来解析主机名,那么现在的服务需要多行样板代码来处理更高分布引入的各种极端情况。
断路器是Michael Nygard在他的书中提出的模式:
断路器背后的基本思路很简单。将受保护的函数调用包装在断路器对象中,从而监视该对象是够故障。当故障达到某个阈值时,断路器跳闸,并且所有对断路器的进一步调用都返回错误,而根本没有进行受保护的呼叫。通常,如果断路器跳闸,还需要触发某种监控器警报。
这些简单的基础设施可以为服务之间的交互增加更多可靠性。然而,就像其他一切一样,随着分布式水平的提高,它们往往变得更加复杂。系统中出现问题的可能性将呈指数级增长,因此,即使是“断路器跳闸时某种监视器警报”等简单的事情也不一定足够了。一个组件中的一个故障可以在许多客户端和客户端的客户端之间创建一系列效果,同时触发数千个断路器跳闸。过去只需几行代码的东西现在需要大量的样板代码来处理仅存在于这个新场景中的情况。
事实上,上面列出的两个例子可能很难正确的实现,像Twitter的Finagle和Facebook的Proxygen这样因此而诞生的大型复杂库变得非常流行。它们作为避免在每个服务中重写相同逻辑的手段。
上面描述的模型在大多数开创微服务架构的组织中使用,如Netflix,Twitter和SoundCloud。随着他们系统中服务的数量的增长,他们也不时发现这种方法的各种缺点。
即使在使用像Finagle这样的库时,代价最高的挑战可能是,组织仍然需要花时间从其工程团队那里建立粘合剂,将库与其他生态系统联系起来。据我在SoundCloud和DigitalOcean的经验,我估计如果在100-250工程师组织中遵循这一策略,需要将1/10的员工专门用于构建工具。有时这种成本是明确的,因为工程师被分配到专门用于构建工具的团队,但隐藏的成本是员工没有从事于产品的开发而是工具的开发。
第二个问题是上面的库限制了可用于微服务的工具,运行环境和语言。微服务的库通常是针对特定平台编写的,无论是编程语言还是JVM等运行环境。如果组织使用库支持的平台以外的平台,则通常需要将代码移植到新平台本身。这将会占用工程师大量的时间。工程师无法从事核心业务和产品的开发,而是必须再次构建工具和基础架构。就是为什么像SoundCloud和DigitalOcean这样的中型组织决定只为其内部服务 - Scala和Go分别支持一个平台。
该模型需要讨论的最后一个问题是治理。库模型可能会抽象出解决微服务架构需求所需功能的实现,但它本身仍然是需要维护的组件。确保数千个服务实例使用相同或至少兼容的库版本并非易事,每次更新都意味着集成,测试和重新部署所有服务 - 即使服务本身没有任何变化。
下一步
与我们在网络堆栈中看到的类似,我们非常希望将大规模分布式服务所需的功能提取到底层平台中。
人们使用更高级别的协议(如HTTP)编写非常复杂的应用程序和服务,甚至不考虑TCP如何控制其网络上的数据包。这种场景是微服务所需要的,从事服务工作的工程师可以专注于业务逻辑,避免浪费时间编写服务的基础架构代码或是管理整个系统的库和框架。
将这个想法融入我们的图中,我们最终会得到如下内容:
遗憾的是,更改网络堆栈以添加此层并不可行。许多从业者解决方案是将其作为一组代理实现。里的想法是服务不会直接连接到它的下游依赖项,而是所有流量都将通过一小块软件,该软件对上层透明,并且具备微服务所需要的功能。
这个领域的第一个记载的文档使用了sidebar的概念。sidebar是一个辅助过程,它可以在您的应用程序旁边运行,并为其提供额外的功能。
虽然有几种这样的开源代理实现,但它们往往设计用于特定的基础架构组件。例如,当涉及到服务发现时,Airbnb的Nerve&Synapse的服务在Zookeeper中注册,而对于Prana,会使用Netflix自己的Eureka服务注册表。
随着微服务架构的日益普及,我们最近看到了新的代理浪潮,其灵活性足以适应不同的基础架构组件和偏好。这个领域第一个广为人知的系统是Linkerd,由Buoyant根据他们工程师之前在Twitter的微服务平台上的工作创建的。很快,Lyft的工程团队宣布Envoy遵循类似的原则。
Service Mesh
在这种模型中,每个服务都将具有配套代理边车。鉴于服务仅通过sidecar代理相互通信,我们最终得到类似于下图的部署:
Buoyant的首席执行官William Morgan发现,代理之间的互连形成了一个网状网络。
服务网格是用于处理服务到服务通信的专用基础设施层。它负责通过包含现代的云原生应用的复杂服务拓扑来可靠地传递请求。实际上,服务网格通常实现为一组轻量级网络代理,这些代理与应用程序代码一起部署,而不需要知道具体的应用程序。
可能这个定义中最强大的方面是它不再将代理视为孤立的组件,并承认它们形成的网络本身就是有价值的东西。
随着组织将其微服务部署转移到更复杂的运行环境,如Kubernetes和Mesos,人们已经开始使用这些平台提供的工具来正确实现网状网络的这种想法。
查看我们的鸟瞰图,我们看到实际的服务流量仍然直接从代理流向代理,但控制平面知道每个代理实例。控制平面使代理能够通过合作实现访问控制和度量收集等操作:
最近宣布Istio项目是此类系统最突出的例子。
现在要完全了解服务网格在大规模系统中的影响还为时尚早。在我看来,这种模式有两个显而易见的好处。首先,不必编写定制软件来处理微服务架构最终的商品代码,这将使许多小型组织能够享受以前只有大型企业才能使用的功能。第二个是这个架构可能让我们最终做到使用最佳工具/语言进行开发的梦想,而不必担心每个平台的库和模式的可用性。
想要了解更多开发技术,面试教程以及互联网公司内推,欢迎关注我的微信公众号!将会不定期的发放福利哦~