第一章 微服务

 
Eric Evans的《领域驱动设计》一书帮助我们理解了用代码呈现真实世界的重要性,
并且 告诉我们如何更好地进行建模。持续交付理论告诉我们如何更有效及更高效地发布软件 产品,
并指出保持每次提交均可发布的重要性。
基于对Web的理解,我们寻找到了机器 与机器交互的更好方式。Alistair Cockbum的六边形架构理论( http://alistair.cockburn.us/Hexagonal+architecture)
把我们从分层架构中極救出来,从而能够更好地体现业务逻辑。
借助虚拟化平台,我们能够按需创建机器并且调整其大小,借助基础设施的自动化我们也 很容易从一台机器扩展到多台。在类似Amazon和Google这样成功的大型组织中,有很多 小团队,他们各自对某个服务的全生命周期负责。
最近,Netflix分享了构建大型反脆弱系 统的经验,
而这种构建方式在10年前是很难想象的。
 
随着领域驱动设计、持续交付、按需虚拟化、基础设施自动化、小型自治团队、
大型集群 系统这些实践的流行,微服务也应运而生。它并不是被发明出来的,
而是从现实世界中总 结出来的一种趋势或模式。但是没有前面提及的这些概念,
微服务也很难出现。在本书接 下来的内容中,我会尝试把这些概念整合起来,
从而给出一个涉及如何构建、管理和演化 微服务的全景图。
 
在一个单块系统内,通常会创建一些抽象层或者模块来保证代码的内聚性,
从而避免上 述问题。内聚性是指将相关代码放在一起,在考虑使用微服务的时候,
内聚性这一概念 很重要。Robert C. Martin 有一个对单一职责原则(Single Responsibility Principle, http:// programmer.97things.oreilly.com/wiki/index.php/The_Single_Responsibility_Principle)的论述:“把因相同原因而变化的东西聚合到一起,而把因不同原因而变化的东西分离开来。”该论 述很好地强调了内聚性这一概念。
 
微服务将这个理念应用在独立的服务上。根据业务的边界来确定服务的边界,
这样就很容 易确定某个功能代码应该放在哪里。而且由干该服务专注于某个边界之内,
因此可以很好 地避免由于代码库过大衍生出的很多相关问题。
 
服务之间均通过网络调用进行通信,从而加强了服务之间的隔离性,避免紧耦合。
 
这些服务应该可以彼此间独立进行修改,并且某一个服务的部署不应该引起该服务消费方 的变动。
对于一个服务来说,我们需要考虑的是什么应该暴露,什么应该隐藏。如果暴露 得过多,
那么服务消费方会与该服务的内部实现产生耦合。这会使得服务和消费方之间产 生额外的协调工作,从而降低服务的自治性。
 
服务会暴露出API (Application Programming Interface,应用编程接口),
然后服务之间通 过这些API进行通信。API的实现技术应该避免与消费方耦合,这就意味着应该选择与具 体技术不相关的API实现方式,
以保证技术的选择不被限制。本书后面会讨论选择好的解 耦性API的重要性。
 
如果系统没有很好地解耦,那么一旦出现问题,所有的功能都将不可用。有一个黄金法则 是:你是否能够修改一个服务并对其进行部署,
而不影响其他任何服务?如果答案是否定 的,那么本书剩余部分讨论的那些好处对你来说就没什么意义了。
 
1.2.1技术异构性
在一个由多个服务相互协作的系统中,可以在不同的服务中使用最适合该服务的技术。
尝 试使用一种适合所有场景的标准化技术,会使得所有的场景都无法得到很好的支持。
 
如果系统中的一部分需要做性能提升,可以使用性能更好的技术栈重新构建该部分。
系统中的不同部分也可使用不同的数据存储技术,比如对于社交网络来说,
图数据库能够更好 地处理用户之间的交互操作,但是对于用户发布的帖子而言,
文档数据库可能是一个更好 的选择。图1-1展示了该异构架构。
 
 
图1-1:微服务帮助你轻松地采用不同的技术
 
微服务可以帮助我们更快地采用新技术,并且理解这些新技术的好处。
尝试新技术通常伴 随着风险,这使得很多人望而却步。尤其是对于单块系统而言,
采用一个新的语言、数据 库或者框架都会对整个系统产生巨大的影响。
对于微服务系统而言,总会存在一些地方让 我可以尝试新技术。
你可以选择一个风险最小的服务来采用新技术,即便出现问题也容易 处理。
这种可以快速采用新技术的能力对很多组织而言是非常有价值的。
 
不过为了同时使用多种技术,也需要付出一些代价。有些组织会限制语言的选择,
比如 Netflix和Twitter选用的技术大多基于JVM (Java Virtual Machine, Java虚拟机),
因为他 们非常了解该平台的稳定性和性能。他们还在JVM上开发了一些库和工具,使得大规模 运维变得更加容易,但这同时也使得我们更难以采用Java外的其他技术来编写服务和客户 端。尽管如此,Twitter和Netflix也并非只使用一种技术栈。另一个会影响多技术栈选用 的因素是服务的大小,如果你真的可以在两周内重写一个服务,那么尝试使用新技术的风 险就降低了不少。
 
贯穿本书的一个问题是,微服务如何寻找平衡。第2章我们会讨论如何做技术选择,
其中 主要专注于演进式架构;第4章主要关注集成,你将学会如何避免服务之间的过度耦合,
从而可以使其彼此独立地进行技术演化。
 
1.2.2弹性
 
弹性工程学的一个关键概念是舱壁。如果系统中的一个组件不可用了,但并没有导致级联 故障,
那么系统的其他部分还可以正常运行。服务边界就是一个很显然的舱壁。
在单块系 统中,如果服务不可用,那么所有的功能都会不可用。
对于单块服务的系统而言,可以通 过将同样的实例运行在不同的机器上来降低功能完全不可用的概率,
然而微服务系统本身 就能够很好地处理服务不可用和功能降级问题。
 
微服务系统可以改进弹性,但你还是需要谨慎对待,因为一旦使用了分布式系统,
网络就 会是个问题。不但网络会是个问题,机器也如此,
因此我们需要了解出现问题时应该如何 对用户进行展示。
第11章会就弹性处理和对故障模式的处理做更多讨论。
 
1.2.3扩展
庞大的单块服务只能作为一个整体进行扩展。即使系统中只有一小部分存在性能问题,
也需要对整个服务进行扩展。如果使用较小的多个服务,
则可以只对需要扩展的服务进 行扩展,
这样就可以把那些不需要扩展的服务运行在更小的、性能稍差的硬件上,如图 1-2所示。
 
图1-2:可以钎对那些需要扩展的微服务进行扩展
 
Gilt是一个在线时尚零售商,他们就是因为这个原因而采用了微服务。2007年,
他们还是 一个单一的Rails应用,2009年,Gilt的系统无法解决其负载。
通过将系统的核心部分抽 离出来之后,Gilt在流量处理方面有了大大的改进。
如今Gilt有450多个微服务,每一个 服务都分别运行在多台机器上。
 
在使用类似Amazon云服务之类的平台时,也可以只对需要的服务进行扩展,
从而节省成 本。通过架构来节省成本的情形还真是不多见。
 
1.2.4简化部署
在有几百万代码行的单块应用程序中,即使只修改了一行代码,
也需要重新部署整个应用 程序才能够发布该变更。这种部署的影响很大、风险很高,
因此相关干系人不敢轻易做部 署。干是在实际操作中,部署的频率就会变得很低。
这意味着在两次发布之间我们对软件
 
做了很多功能增强,但直到最后一刻才把这些大量的变更一次性发布到生产环境中。
这 时,另外一个问题就显现出来了:两次发布之间的差异越大,出错的可能性就更大!
 
在微服务架构中,各个服务的部署是独立的,
这样就可以更快地对特定部分的代码进行部 署。如果真的出了问题,
也只会影响一个服务,并且容易快速回滚,
这也意味着客户可以 更快地使用我们幵发的新功能。
Amazon和Netflix等组织采用这种架构主要就是基干上述 考虑。
这种架构很好地清除了软件发布过程中的种种障碍。
 
微服务部署领域的技术在过去几年时间里发生了巨大的变化,
第6章会对该话题做更深入 的讨论。
 
1.2.5与组织结构相匹配
我们经历过太多由于团队和代码库过大引起问题的情况。当团队是分布式的时候,
问题会 更明显。我们也知道在小型代码库上工作的小团队更加高效。
 
微服务架构可以很好地将架构与组织结构相匹配,避免出现过大的代码库,
从而获得理想 的团队大小及生产力。服务的所有权也可以在团队之间迁移,
从而避免异地团队的出现。 在第10章讲解康威定律时会对该话题做更深入的讨论。
 
1.2.6可组合性
分布式系统和面向服务架构声称的主要好处是易于重用已有功能。
而在微服务架构中,根 据不同的目的,人们可以通过不同的方式使用同一个功能,
在考虑客户如何使用该软件时 这一点尤其重要。
单纯考虑桌面网站或者移动应用程序的时代已经过去了。
现在我们需要 考虑的应用程序种类包括Web、原生应用、移动端Web、平板应用及可穿戴设备等,
针对 每一种都应该考虑如何对已有功能进行组合来实现这些应用。
现在很多组织都在做整体考 虑,拓展他们与客户交互的渠道,
同时也需要相应地调整架构来辅助这种变化的发生。
 
在微服务架构中,系统会开放很多接缝供外部使用。当情况发生改变时,
可以使用不同的 方式构建应用,而整体化应用程序只能提供一个作常粗粒度的接缝供外部使用。
如果想要 得到更有用的细化信息,你需要使用榔头撬开它!
第5章会讨论如何将已有的单块应用程 序分解成为多个微服务,
并且达到可重用、可组合的目的。
 
 
1.2.7对可替代性的优化
如果你在一个大中型组织工作,很可能接触过一些庞大而丑陋的遗留系统。
这些系统无人 敢碰,却对公司业务的运营至关重要。更糟糕的是,
这些程序是使用某种奇怪的Fortran 变体编写的,并且只能运行在25年前就应该被淘汰的硬件上。
为什么这些系统直到现在 还没有被取代?其实你很清楚答案:工作量很大,
而且风险很高。
 
当使用多个小规模服务时,重新实现某一个服务或者是直接删除该服务都是相对可操作 的。
想想看,在单块系统中你是否会在一天内删掉上百行代码,并且确信不会引发问题?
微服务中的多个服务大小相似,所以重写或移除一个或者多个服务的阻碍也很小。
 
使用微服务架构的团队可以在需要时轻易地重写服务,或者删除不再使用的服务。
当一个 代码库只有几百行时,人们也不会对它有太多感情上的依赖,所以很容易替换它。
 
1.3面向服务的架构
SOA (Service-Oriented Architecture,面向服务的架构)是一种设计方法,
其中包含多个服务,而服务之间通过配合最终会提供一系列功能。一个服务通常以独立的形式存在于操作系统进程中。
服务之间通过网络调用,而非采用进程内调用的方式进行通信。
 
人们逐渐认识到SOA可以用来应对臃肿的单块应用程序,从而提高软件的可重用性,
比 如多个终端用户应用程序可以共享同一个服务。它的目标是在不影响其他任何人的情况下 透明地替换一个服务,
只要替换之后的服务的外部接口没有太大的变化即可。这种性质能 够大大简化软件维护甚至是软件重写的过程。
 
SOA本身是一个很好的想法,但尽管做了很多尝试,人们还是无法在如何做好SOA这件 事情上达成共识。
在我看来,业界的大部分尝试都没能把它作为一个整体来看待,因此很 难给出一个比该领域现有厂家提供的方案更好的替代方案。
 
实施SOA时会遇到这些问题:通信协议(例如SOAP)如何选择、第三方中间件如何选 择、
服务粒度如何确定等,目前也存在一些关于如何划分系统的指导性原则,
但其中有很 多都是错误的。本书的剩余部分会分别讨论这些问题。
一些激进人士可能会认为这些厂家 提出并推动SOA运动的目的不过就是想要卖更多的产品,而这些相似的产品最终破坏了 SOA的目标
 
现有的SOA知识并不能帮助你把很大的应用程序划小。它没有提到多大算大,也没有讨 论如何在现实世界中有效地防止服务之间的过度耦合。
由于这些点没有说清楚,所以你在 实施SOA时会遇到很多问题。
 
在现实世界中,由于我们对项目的系统和架构有着更好的理解,所以能够更好地实施 SOA,而这事实上就是微服务架构。
就像认为XP或者Scrum是敏捷软件开发的一种特定 方法一样,你也可以认为微服务架构是SOA的一种特定方法。
 
1.4.1共享库
 
基本上所有的语言都支持将整个代码库分解成为多个库,这是一种非常标准的分解技术。
这些库可以由第三方或者自己的组织提供。
 
不同的团队和服务可以通过库的形式共享功能。比如说,我可能会创建一系列有用的集合 操作类工具,
或者一个可以重用的统计库。
 
团队可以围绕库来进行组织,而库本身可以被重用。但是这种方式存在一些缺点。
 
首先,你无法选择异构的技术。一般来讲,这些库只能在同一种语言中,或者至少在同一 个平台上使用。
其次,你会失去独立地对系统某一部分进行扩展的能力。再次,
除非你使 用的是动态链接库,否则每次当库有更新的时候,都需要重新部署整个进程,
以至于无法 独立地部署变更。而最糟糕的影响可能是你会缺乏一个比较明显的接缝来建立架构的安全 性保护措施,
从而无法确保系统的弹性。
 
共享库确实有其相应的应用场景。有时候你会编写代码来执行一些公共任务,
这些代码并 不属于任何一个业务领域,并且可以在整个组织中进行重用,
很显然这些代码就应该成为 可重用的库。但是你还是需要很小心,如果使用共享代码来做服务之间的通信的话,
那么 它会成为一个耦合点。第4章会再讨论该问题。
 
服务之间可以并且应该大量使用第三方库来重用公共代码,但有时候效果不太好。
 
 
1.4.2模块
 
除了简单的库之外,有些语言提供了自己的模块分解技术。
它们允许对模块进行生命周期 管理,这样就可以把模块部署到运行的进程中,
并且可以在不停止整个进程的前提下对某 个模块进行修改。
 
作为一个与具体技术相关的模块分解技术,OSGI (Open Source Gateway Initiative,开放服 务网关协议)值得一提。
Java本身并没有真正的模块概念,至少要到Java 9才能看到这个 特性加入到语言中。
OSGI最初是Eclipse Java IDE使用的一种安装插件的方式,
而现在很 多项目都在使用库来对Java程序进行模块化。
 
OSGI的问题在于它非常强调诸如模块生命周期管理之类的事情,
但语言本身对此并没有 足够的支持,
这就迫使模块的作者做更多的工作来对模块进行适当的隔离。
在一个进程内 也很容易使模块之间过度耦合,从而引起各种各样的问题。
我个人对OSGI的经验是,它 带来的复杂度要远远大于它带来的好处,
即使对于很优秀的团队来说也是不可避免。业界
 
 
的其他同事也多有类似的看法。
Erlang采用了不同的方式,模块的概念内嵌在Edang语言的运行时中,
因此这种模块化分 解的方式是很成熟的。你可以对Erlang的模块进行停止、重启或者升级等操作,
且不会引 起任何问题。Erlang甚至支持同时运行同一个模块的多个版本,
从而可以支持更加优雅的 模块升级。
 
Erlang的模块化能力确实非常惊人,
但是即使我们非常幸运地能够使用具有这种能力的平 台,
还是会存在与使用共享库类似的缺点,即它会大大限制我们采用新技术和独立对服务 进行扩展的能力,
并且有可能会导致使用过度耦合的集成技术,
同时也会缺乏相应的接缝 来进行架构的安全性保护。
 
还有一个值得注意的事情是:尽管在一个单块进程中创建隔离性很好的模块是可能的,
但 是我很少见到真正有人能做到。这些模块会迅速和其他代码耦合在一起,
从而失去意义。
而进程边界的存在则能够有效地避免这种情况的发生(至少很难犯错误)。
尽管我不认为 这是使用进程隔离的主要原因,
但是事实上确实很少有人能够在同一个进程内部做到很好 的模块隔离。
 
除了把系统划分为不同的服务之外,
你可能也想要在一个进程内部使用模块进行划分,
但 是仅仅使用模块划分不能解决所有的问题。如果你只使用Erlang,
可能会花很长时间才能 把Erlang的模块化做好,但是我怀疑大部分人不会这么做。
对于剩下的人来说,模块能够 提供的好处与共享库比较类似。
 
1.5没有银弹
 
在本章结束之前,我想强调一点:微服务不是免费的午餐,更不是银弹,
如果你想要得 到一条通用准则,那么微服务是一个错误的选择。
你需要面对所有分布式系统需要面对 的复杂性。
尽管后面用很多的篇幅来讲解如何管理分布式系统,但它仍然是一个很难的 问题。
如果你过去的经验更多的是关于单块系统,那么为了得到上述那些微服务的好处,
你需要在部署、测试和监控等方面做很多的工作。你还需要考虑如何扩展系统,
并且保 证它们的弹性。如果你发现,还需要处理类似分布式事务或者与CAP相关的问题,
也不 要感到惊讶!
 
每个公司、组织及系统都不一样。微服务是否适合你,
或者说你能够在多大程度上采用微 服务,取决于很多因素。
在本书的剩余章节中我会试图给出一些指导,指出一些常见的陷 阱,
从而帮助你制定出清晰的演化路线。    -
 

猜你喜欢

转载自www.cnblogs.com/mongotea/p/11992016.html