•微服务•分布式系统•服务网•模式•
自从几十年前分布式系统被首次提出,我们了解到分布式系统能够实现在这之前我们甚至没有想到过的用例,但它们也会引入各种新问题。
当这些系统很少且简单时,工程师通过尽量少远程交互来应对分布式带来的复杂性。处理分布式最安全的方法是尽可能地避免它,即使这意味着跨各种系统的重复逻辑和冗余数据。
但是,行业各种需求,从一些较大的中央计算机到成百上千的小型服务,一直在推动我们往前。在这个新世界中,我们必须开始摆脱困境,应对新挑战和开放性问题,首先是以个案方式完成临时解决方案,然后再采用更复杂的方法。随着我们更多地了解问题域并设计出更好的解决方案,我们开始将一些最常见的需求纳入模式,库,和最终纳入平台。
我们刚开始联网计算机时发生了什么
关于两台或两台以上的计算机相互通信,人们首先设想到如下图:
服务与另一个服务通信以实现用户的某个目标。上述是一个简化视图,没有包括诸如字节编解码、线路上发送和接收的电信号等层。尽管如此,如上抽象视图仍能满足我们如下的讨论。让我们将网络堆栈作为一个独立的组件来显示以添加更多细节:
自20世纪50年代以来,上述模型的多种变体沿用至今。最初,计算机少且贵,因此两个节点间的连接都经过精心设计和维护。随着计算机变得越来越便宜和越来越流行,节点间的连接及其上传输的数据量急剧增加。随着人们越来越依赖网络系统,工程师需要确保他们构建的软件符合用户的服务质量要求。
为了达到理想的质量水平,还有许多问题亟需解决。机器怎么发现彼此,同一条线路如何处理多个连接,机器间未直连时如何相互通信,如何在网络上路由数据包、加密流量等。
我们以上述问题中的流量控制为例。流量控制是防止一台上游服务器发送的数据包超出下游服务器可处理极限的一种机制。这种机制是非常必要的,在网络系统中,至少有两台不同的、独立且彼此间互不了解的计算机,计算机A以给定的速率向计算机B发送字节,但B不能保证以稳定且足够快的速度处理接收到的字节,因为B可能并行运行着其他任务,或数据包无序到达,B阻塞等待最先应到的数据包。这意味着B不仅可能达不到A期望的性能,甚至可能比那更糟,过载使得B必须将所有接收到的数据包进行排队处理。
有一段时间,人们期望构建网络服务或应用程序的开发人员会在他们编写的代码中处理上面提到的问题,在流量控制示例中,意味着应用程序本身必须包含逻辑以确保不会发送过多的包至下游服务。业务代码包含大量网络处理相关逻辑。在我们的抽象图中,它将是这样的:
幸运的是,技术迅速发展,很快就有标准协议如TCP/IP将流控制和许多其他问题纳入网络堆栈本身。这意味着相关逻辑从应用程序下沉到操作系统提供的底层网络层:
这种模式非常成功。即便需要高性能和高可靠,也只有非常少的组织无法仅使用商用操作系统自带的TCP/IP堆栈来驱动业务发展。
当我们第一次开始使用微服务时发生了什么
多年来,计算机变得更加便宜和无所不在,上述网络堆栈模型已经被证明是构建可靠连接系统事实上的工具集。随着越来越多节点和稳定的连接,业界使用了多种类型的网络系统,从细粒度的分布式agents、对象到由重量级分布式组件组成的面向服务的体系结构。
这种极端分布式带来了许多有趣的高级用例和好处,但也带来了一些挑战,其中一些挑战是全新的,但剩下的只是我们在原始网络时所遇到的挑战的更高级的版本而已。
在90年代,Peter Deutsch和他在Sun Microsystems的工程师同事一起编写了“分布式计算的8个谬误”,其中列出了人们在使用分布式系统时所做的一些错误假设,Pete的观点是,在更原始的网络架构或理论模型中,这些假设可能是真的,但它们在现代世界中并不成立:
- 网络可靠
- 延迟为零
- 带宽是无限的
- 网络是安全的
- 拓扑不会改变
- 有一个管理员
- 运输成本为零
- 网络是同质的
将上述列表作为“谬误”意味着工程师不能忽视这些问题,他们必须明确地处理它们。
更加分布式的系统即微服务架构-在可操作性方面引入了新的需求-使事情进一步复杂化。我们之前详细讨论了其中一些,如下简要列出其中一些必须处理的内容:
- 快速获取计算资源
- 基本监控
- 快速部署
- 轻松获取存储资源
- 轻松接入边缘网关
- 认证/授权
- 标准化的RPC
因此,虽然几十年前开发的TCP/IP堆栈和通用网络模型仍然是计算机间相互通信的强大工具,但更复杂的架构引入了另一层需求,在这种架构下开发人员必须实现这些需求。
例如,服务发现和断路器,这两种技术用于解决上面提到的弹性和分布挑战。
历史往往会重演,第一批基于微服务架构构建系统的组织遵循的策略,与前几代构建网络通信的策略非常相似。即意味着处理上述需求的责任由编写服务的工程师负责。
服务发现,是一个自动查找服务实例的进程,该服务实例能响应给定请求,例如,Teams
服务需要查找一个带environment=production
属性的Players
服务实例,您将调用一些服务发现进程,该进程将返回部署运行了相应实例的服务器的列表。对于更加单体的结构,通常使用DNS、负载平衡器(如nginx、F5)和一些约定的端口号(例如,所有服务将其HTTP服务器绑定到端口8080)实现这个简单任务。在更加分布式的环境中,这个任务变得更加复杂,前一种方式,服务可以盲目信任DNS服务器查找到其所依赖的服务,而现在必须处理client-side负载平衡、多套不同运行环境(例如,分段与生产)、地理位置分散的服务器等。以前只需一行代码来解析主机名,现在服务需要多行样板代码来处理更加分散所引入的各种极端情况。
断路器是Michael Nygard在他的书Release It中编目的一种模式。我喜欢Martin Fowler对断路器模式的总结:
断路器背后的基本思想非常简单。将受保护的函数调用包装在断路器对象中,该对象监视故障。一旦故障达到某个阈值,断路器就会跳闸,并且所有对断路器的进一步调用都会直接返回错误,而不会对受保护函数进行调用。通常,如果断路器跳闸了,您还需要某种监控告警。
这些简单的设备可以提升服务间交互的可靠性。然而,随着分布式水平的提升,它们往往变得更加复杂。系统中出现问题的可能性随着分布呈指数级增长,因此即使是“断路器跳闸时某种监视器告警”等简单的事情也不一定是直截了当的。一个组件中的一个故障可以在许多客户端和客户端的客户端间产生一系列影响,同时触发数千个电路跳闸。过去只需几行代码的东西现在需要大量的样板代码来处理。
实际上,上面提到的服务发现和断路器很难被正确实现,所以像Twitter的Finagle和Facebook的Proxygen这样的大型复杂类库变得非常流行,用以避免在每个服务中重写相同逻辑。
大多数开发微服务架构服务的组织如Netflix,Twitter和SoundCloud,遵从如上描述的模型。随着他们系统中服务数量的增长,他们也发现了这种方式的缺点。
- 最大的挑战可能是,即使使用像Finagle这样的类库,组织仍然需要其工程团队投入时间来构建胶水代码以粘合类库与现有生态系统。根据我在SoundCloud和DigitalOcean的经验,估计在一个100至250个工程师的组织中,若要遵循这一策略,需要抽取1/10的员工专门用于构建这些工具。有时这种成本是明确的,根据专门构建工具的工程师人数即可评估,但花费在产品上的时间更多是不可见的。
- 第二个问题是上述模型限制了用于微服务的工具、运行时和开发语言。微服务的类库通常是针对特定平台编写的,无论是针对编程语言还是针对一个运行时如JVM。如果组织使用的是非类库支持的平台,则通常需要将相关代码移植到新平台,这消耗了工程宝贵的时间。工程师不再只需聚焦核心业务和产品,而需同时构建工具和基础架构。这就是为什么像SoundCloud和DigitalOcean这样的中型组织决定其内部服务只支持Scala或Go其中之一个平台。
- 上述模型最后一个值得讨论的问题是治理。类库模型可能会抽象实现微服务架构所需特性,但它本身仍然是一个需要维护的组件。确保成千上万的服务实例使用相同或至少兼容的库版本并非易事,每次更新都意味着集成、测试和重新部署所有服务 - 即使服务本身没有任何更改。
下一个逻辑步骤
与我们在网络堆栈中看到的类似,将大规模分布式服务所需的通用功能提取到底层平台是非常有必要的。
人们使用更高级别的协议(如HTTP)编写非常复杂的应用程序和服务,而不用考虑TCP如何控制其网络上的数据包。在微服务开发中,希望也能这样,从事服务开发的工程师可以只专注于他们的业务逻辑,而不浪费时间编写自己的服务基础架构代码或管理跨服务的库和框架。
将这个想法融入我们的图表中,我们最终可能会遇到以下情况:
遗憾的是,更改网络堆栈以添加此层是一项不可行的任务。许多从业者采用的解决方案是将其作为一组代理实现,服务不直连到它的下游依赖项,而是将所有流量都通过一小块软件透明地传递出去,以此添加所需的功能。
在这个里提出sidecars概念,sidecars是一个辅助进程,与服务一起运行并为其提供额外的功能。2013年,Airbnb撰写了关于Synapse和Nerve的文章,他们开源实现的sidecars。一年后,Netflix推出了Prana,这是一款专用于非JVM应用程序连接NetflixOSS生态系统中的sidecars。在SoundCloud,我们构建了sidecars,使我们的Ruby遗留系统能够使用我们为JVM微服务构建的基础架构。
虽然有如上几种开源代理实现,但它们往往设计用于特定的基础架构组件。例如,当涉及到服务发现时,Airbnb的Nerve&Synapse假设服务注册在Zookeeper中,而对于Prana,应该使用Netflix的Eureka服务注册表。
随着微服务架构的日益普及,我们最近看到了新的代理浪潮,其灵活性足以适应不同的基础架构组件和偏好。这个领域的第一个广为人知的系统是Linkerd,由Buoyant根据他们的工程师在Twitter的微服务平台上的先前工作创建。很快,Lyft的工程团队宣布Envoy遵循类似的原则。
服务网格
在这种模型中,每个服务都将有配套代理sidecar。服务仅通过sidecar代理相互通信,我们最终得到类似于下图的部署:
Buoyant首席执行官威廉·摩根(William Morgan)观察到proxy之间的互联形成了一个 网状网络。 在2017年初,William为这个平台写了一个定义,称之为 Service Mesh:
服务网格是用于处理服务到服务通信的专用基础设施层。负责复杂服务拓扑中服务(包括云原生应用)间请求的可靠传递。实际上,服务网格通常实现为一组轻量级网络代理,这些代理与应用程序代码一起部署,而无需知道应用程序。
可能他关于服务网格的定义中最重要的方面是,不再将代理视为孤立的组件,并承认它们形成的网络本身就是有价值的东西。
随着越来越多的组织将其微服务部署迁移到更复杂的运行时如Kubernetes和Mesos,这些组织已经开始使用这些平台所提供的工具来正确实现网状网络理念。模式由一组独立且孤立地工作的的proxy节点,转向适当的、有点集中式的、带控制平面的模式。
。
查看我们的鸟瞰图,我们看到实际的服务流量仍然直接从proxy流向proxy,但控制平面知道每个proxy实例。控制平面使proxy能够实现访问控制和度量收集等操作:
最近宣布的Istio项目是此类系统最突出的例子。
现在要完全理解服务网格在大规模系统中的影响还为时尚早。这种方法的两个好处对我来说已经很明显了。首先,不必编写代码对接微服务架构基础代码,即可让许多小型组织享受以前只有大型企业可用的功能,从而创建各种有趣的用例。第二个是这个架构可能让我们最终实现使用最佳工具/语言的梦想,而不必担心每个平台的库和模式的可用性。
致谢
Monica Farrell,Rodrigo Kumpera,Etel Sverdlov,Dave Worth,Mauricio Linhares,Daniel Bryant,Fabio Kung和Carlos Villela对本文的草稿进行了反馈。
修订记录
· 2017年3月3日 - 首次发布
· 2017年8月5日 - 合并反馈
翻译自:https://philcalcado.com/2017/08/03/pattern_service_mesh.html