Service Mesh架构的发展

  1. 1. 网络计算机架构的演变
  2. 2. 微服务架构的演变
  3. 3. 微服务架构的下一阶段
  4. 4. Service Mesh

本文是对Phil Calçado的文章Pattern: Service Mesh的翻译,主要是为了学习一下Service Mesh发展的历史和对Service Mesh的简介,欢迎批评指正。

自从分布式系统的概念提出几十年来(Lynch, Nancy A. (1996), Distributed Algorithms),分布式系统用在了我们很多想不到的地方,但它也引入了各种各样的新问题。

当分布式系统在很简单的时候,开发者通过减少远程交互的方式来处理增加的复杂性。解决分布式问题最安全的方式就是避免分布式的发生,即使这样会使不同的系统存在重复的业务逻辑和数据

但行业的需求一直在推动我们向前走,从仅有少数几台的大型服务器到成百上千的小型服务。如今,我们必须要全力以赴积极的应对新的挑战和悬而未决的问题。分布式系统的发展经历了如下过程:

  • 首先是针对每种情况提供临时解决方案;
  • 然后是更复杂的解决方案;
  • 随着我们对问题领域的更多了解并设计出更好的解决方案,我们开始将一些最常见的需求总结具体化为:patterns,libraries,并最终落地为platforms。

1. 网络计算机架构的演变

自从人们开始想到让多台计算机通信以来,他们设想了以下的模式:

跨机的ServiceA和ServiceB之间进行通信,以达到用户的某些目的。这是一个简化的视图,这里省略了在代码操作的数据和通过线路发送接收的电信号之间转换的很多层。不过这个抽象对于我们讨论问题已经足够了。接下来我们显示网络堆栈部分作为一个不同组件来增加更多的细节信息:

从上世纪50年代,上述模型就一直在使用。由于起初计算机作为稀缺资源很是珍贵,因此每个节点之间的链路都是经过精心设计和维护的。随机计算机的普及,计算机的连接数量和通信数据急剧增长。随着人们越来越依靠计算机网络系统,工程师们需要确保他们给客户提供高质量的软件服务。

为了设计高质量的服务,有许多的问题需要解决。需要解决不同主机之间如何找到彼此多个连接在一条物理线路上,允许未直连的计算机进行通信,跨网络路由转发数据加密等等。

上面提出的问题中,有一个叫做流量控制(Flow Control)的东西,流量控制是一种机制,可以防止一台服务器发送过多的数据包,超出下游服务器的处理能力。这是很有必要的,因为在网络系统中,您至少有两台彼此独立且不太了解对方的计算机。ComputerA以一定的速率给ComputerB发送数据,但是不能保证B以一致且足够快的速度处理接收的数据。例如B可能忙于并行运行其他任务,或者数据包可能无序到达,而B被阻塞等待本应最先到达的数据包。这意味着,不仅A不会具有B预期的性能,而且还可能使情况变得更糟,因为它可能会使B过载,而B现在必须将所有这些传入的数据包排队等待处理。

有一段时期,编写网络服务和应用程序的开发者来负责处理上述流量控制的问题。这就意味着在编写应用程序过程中必须包含上述逻辑以确保我们不会使服务过载。这种重网络处理的逻辑和我们的业务逻辑同时存在代码里。在我们的抽象图中,应该是这样的:

幸运的是,技术的快速发展,有很多的标准(如TCP/IP)将流量控制和许多其他的网络问题的解决方案整合到网络堆栈本身中。这意味着这段代码仍然存在,但已从您的应用程序提取到操作系统提供的网络层。如下图:

这种模式取得了很大的成功,几乎没有组织不在商用操作系统上不使用TCP/IP协议栈来解决网络问题的,即使在高性能和高可靠的应用场景也是如此。

2. 微服务架构的演变

近些年来,计算机的普及度可以说无处不在,上面描述的网络协议栈已经证明了其是可靠的连接系统的工具集。有了更多的网络节点和稳定的连接,业界设计了各种类型的网络系统,从细粒度的分布式代理/对象到庞大且高度分布式的面向服务的体系结构(Service-Oriented Architecture)。

这些设计,带来了许多有趣高级的应用和好处,但同时也带来了一些挑战,其中一些挑战是全新的,其他挑战只是我们在上面讨论原始网络通信时所遇到挑战的高级别版本。

在90年代,Peter Deutsch和他的工程师同事们在Sun Microsystems公司编写了The 8 Fallacies of Distributed Computing,指出了人们在使用分布式系统时往往会做出的假设。Peter指出这些假设在原始网络架构或者理论模型中可能是正确的。但在现实实践中是错误的:

  • 网络是可靠的;
  • 零延迟;
  • 无限的带宽;
  • 网络是安全的;
  • 网络拓扑结构是不变的;
  • 有管理者;
  • 传输成本为零;
  • 网络是同构的;

反对这些假设,意味着工程师在实践过程中不能忽略这些问题,必须要予以解决。

接下来问题变得更复杂了,开始出现更复杂的分布式系统,我们称之为微服务架构,在操作性上引入了新的需求,我们之前详细的讨论其中的一些问题,这里列出一个微服务架构必须要处理问题的列表:

  • 计算资源的快速调度;
  • 基础监控;
  • 快速部署;
  • 存储的快速调度;
  • 便捷的边缘访问;
  • 鉴权;
  • 标准化RPC;

尽管几十年前开发的TCP/IP协议和通用的网络模型在计算机通信方面仍然是一个强大的工具,但更复杂的体系架构带来了另一层面的通信需求,这又再次需要负责此架构的工程师来编写完成。

举例来说,服务发现熔断器这两种技术用于解决上面列出的几个弹性和分布挑战。

就像历史总是会重演,最先构建基于微服务的系统的组织走了与最初计算机网络系统发展相似的策略。这意味着要解决上面的问题,就需要工程师独立的去完成对应的服务。

服务发现是一个进程,当给定一个查询条件时,能够自动发现对应服务的实例。例如,服务Teams需要找到服务Players中环境属性设置为Productions的实例,你需要调用服务发现程序然后返回对应的服务器列表。对于比较单一的架构环境中,这个功能通常可以通过使用DNS,负载均衡,或者端口号上的一些约定(例如,所有服务把http服务绑定在8080端口)来实现。但对于分布式程度更高的环境,任务就变得更加复杂,之前可以简单依赖DNS查找来获取依赖关系的服务,现在必须处理客户端负载均衡,多个不同运行环境,地理上分离的服务器,等等。如果以前你需要一行代码来解析主机名,那么现在就需要用很多行代码来处理分布式所带来的各种特殊情况。

熔断器是Michael Nygard在他的Release It一书中提出的一种模式,我比较喜欢 Martin Fowler在其文章中对该模式的总结:

熔断器背后的原理非常简单,将要保护的函数调用封装在熔断器对象中,对其进行故障的监控。当失败次数达到一个阈值,熔断器就会触发(断开),后续所有该熔断器的调用都会返回失败,而不会再进行调用。通常,当熔断器触发时我们还需要一些告警通知。

上述的服务都是非常简单的功能,能够让服务之间的交互更加的可靠。然后,就像其他架构一样,随着分布式程度的增加,这些服务的复杂度变得越来越高。随着分布式程度增加,系统中的错误可能成倍的增长,即使是很简单的”熔断器触发时的一些告警通知”也变得不再那么简单。一个组件的一个错误,可能会在多个客户端之间发生级联效应(Cascade effect),甚至是触发成千上万的熔断器的同时触发。之前需要很少的代码可以实现的服务,在新的分布式程度很高的的环境中则需要大量的代码来解决。

事实上,上述两个服务发现熔断器的两个服务,很难去正确的实现,以至于 Twitter’s Finagle and Facebook’s Proxygen的两个庞大和复杂的库变得十分的流行,避免在每个服务中再造轮子。

如上,这种Lib模式被研发微服务架构的大多数组织所遵循,如Netflix、Twitter和SoundCloud,随着他们系统中服务数量的增加,他们也遇到了这种服务模型的各种缺点。

首先,最昂贵的挑战是,就是使用像Finagle这样的库,团队依然需要投入工程师们去封装微服务库以适配现有的生态系统。根据我在SoundCloud和DigitalOcean的经验,对于一个100-250人的团队中,这项工作需要抽出1/10的人力去进行。有时候这个这个成本开销时明确的,但更多的时候成本是不可见的,因为他们是从业务产品开发的过程中抽出时间来完成的。

第二个问题是,上面的库模式限制了微服务可以使用的工具,运行时环境,以及使用的编程语言。微服务的库通常是为特定的平台所编写的,可以是源代码或者类似JVM运行时的库。如果团队使用的平台不是库所支持的平台,那就需要进行代码移植以适配新的平台。这很浪费稀有的开发时间。工程师们将需要重新构建工具和底层基础设施,而不是他们的核心业务和产品上。这就是为什么一些中型企业像SoundCloud和DigitalOcean决定对于内部的服务只支持一种平台的实现-分别是Scala和Go。

最后一个值得讨论的问题是,库治理。这种Lib模式可以满足微服务架构中所需的抽象实现,但是它仍然是一个需要维护的组件。确保成千上万的服务实例使用相同或者版本兼容的lib,绝不是一件容易的事情。每一次lib的更新,都意味了所有服务的集成,测试,重新部署,即使服务本身没有任何改变。

3. 微服务架构的下一阶段

无独有偶,像网络协议栈发展的过程一样,将大规模分布式服务所需要的功能剥离出来集成到底层平台是一个众望所归的选择。

人们通过应用层的协议(例如HTTP)写出了很多复杂的应用程序和服务,甚至不用考虑TCP是如何控制数据包在网络上传输的。这就是我们微服务所需要的,从事服务开发的工程师们可以专注于业务逻辑的开发,避免浪费时间去编写服务基础设施代码或者管理这些库和框架。

把这个想法集成到图中,我们可以得到类似如下的图:

不幸的是,更改协议栈来增加微服务的功能不是一个可行的方案,许多开发者是通过一组代理来实现此功能。这里的设计思想是服务不需要和下游服务直连,所有的流量都通过该代理透明的来实现对应的功能

这种设计思想第一个有记录的研究使用了SideCar的概念,SideCar是一个辅助进程,运行在你的业务进程旁边,并提供额外的功能。2013年,Airbnb写了一篇关于Synapse和Neurove的文章,他们开源了SideCar模式的实现。一年以后,Netfix推出了Prana,一种能够让非JVM的应用也可以从NetflixOSS生态系统中受益的SideCar实现。在SoundCloud,我们构建了SideCar使我们的Ruby遗产能够使用我们为JVM微服务构建的基础设施。

虽然有一些开源的SideCar实现,但是它们往往被设计成基于特定的基础组件,例如Airbnd的Nerve & Synapse的服务发现的实现是基于服务注册在ZooKeeper中的,而Prana则是使用了Netfix自己的Eureka服务注册实现。

随着微服务架构的流行,最近我们看到了新一轮的Sidecar实现,它们足够灵活可以适应不同的基础组件和偏好,其中最著名的就是Linkerd,它是由Buoyant基于之前在Twitter的微服务框架而开发的。很快,Lyft的工程师团队也公布了Envoy项目,其拥有同样的设计准则。

4. Service Mesh

在这种模型中,每个服务都会有一个配套的代理SideCar。考虑到服务之间的通信仅仅通过SideCar代理,我们最终得到如下的部署图:

Buoyant的CEO William Morgan ,发现了各个SideCar代理之间互联组成了一个网状网络,2017初,William为这个网状的平台起了一个“Service Mesh”的定义

Service Mesh是一个用于服务和服务之间通信的专用基础设施层。它负责服务请求能够在复杂的服务拓扑(组成了云原生应用)中可靠的进行投递。在实践中,Serivce Mesh的典型实现是作为轻量级网络代理阵列,部署在应用程序旁边,不需要业务进程感知到。

William关于Service Mesh的定义中,最有说服力的一点是,他不再将SideCar代理视为一个独立组件,而是承认了它们组成的网络像它们自身一样是有价值的

随着很多公司将它们的微服务部署到更复杂的系统运行环境中,例如Kubernetes和Mesos,人们开始使用这些平台提供的工具来实现合适的Serivce Mesh的想法。它们将独立的SideCar代理从独立的工作环境中转移到一个适当的,有点集中的控制面。

看下我们的鸟瞰图,服务之间的流量仍然是通过SideCar代理来进行转发,但是控制平面知道每个SideCar实例。控制平面能够让代理实现例如访问控制,指标收集等需要协作完成的事情。

最近发布的lstio项目是上述系统最优秀的实践。

现在要完全理解Service Mesh在大规模系统中的影响还为时尚早。但Service Mesh所带来的两个好处对于我来说已经很明显了。首先不需要编写定制软件来处理微服务体系结构中最终商用代码,这允许许多小公司能够享受只有大公司才能使用的功能,以此创建有趣的案例。其次,Serivce Mesh的架构允许我们工作中使用最好的工具和语言来实现我们的需求,而不用担心平台库和模式的兼容性问题