《企业级容器云架构开发指南》
595
2022-05-30
2.2.2 微服务的特性
微服务有九大特性,我们逐一来了解一下。
(1)特性一:“组件化”与“多服务”
如图2-8所示,传统实现组件的方式是通过库(library),库和应用一起运行在进程中,库的局部变化意味着整个应用的重新部署。通过服务来实现组件,意味着将应用拆散为一系列的服务运行在不同的进程中,那么单一服务的局部变化只需重新部署对应的服务进程。
将软件库(libraries)定义为这样的组件,即它能被链接到一段程序,且能通过内存中的函数来进行调用。然而,服务(services)是进程外的组件,它们通过诸如Web Service请求或远程过程调用这样的机制来进行通信。原来编写程序,无论是使用C语言还是Java,都会引入很多组件,一个应用程序外部引入很多成熟的库。到了微服务,实际上我们要把原来引入的这些组件也单独变成一个个的服务,可能原来是一个日志的组件,或是消息的组件,在这之上我们做了某个业务,到了微服务我们会把日志、消息、应用过程都作为一个个单独的服务,这就是组件化与多服务。微服务作为组件的更高级形式解决了一个关键问题:服务可以被独立部署,对一个服务进行修改,只需要构建和重新部署这一个服务,对系统的其他服务没有影响,但前提是服务的接口协议没有发生变化,只是对服务内部进行升级。如果涉及服务接口协议的升级,那么情况就要复杂一些,调用这个服务相关的其他服务可能也会修改升级。
图2-8 微服务特性一:“组件化”与“多服务”
(2)特性二:围绕“业务功能”组织团队
康威定律中提到围绕业务功能组织团队,简单来说就是什么样的团队产生什么样的架构,这也是微服务所有架构的根基。如图2-9所示,具体来说,就是如果团队是按照业务功能组成的多个小微团队,那么每一个小组开发出来的必然是微服务;如果团队是按照前端、中间键、数据库这样的方式构建的,那么开发出来的必然是单体。
我们来回顾一下康威定律,其中提到了4个观点。
1)定律一:组织的沟通方式会通过系统的设计表达出来。简单来说,假设一个团队有N人,那么会有多少条沟通线路?答案是N×N-1/2。无论是做项目还是做产品,要解决的最大问题就是沟通问题。沟通的好坏在很大程度上决定项目的成败。所以有多少条沟通线路,可能最后就会有多少个模块。沟通的问题会带来系统设计的问题,进而影响整个系统的开发效率和最终产品的结果。
图2-9 微服务特性二:围绕“业务功能”组织团队
2)定律二:即便有再多的时间,一件事也不可能做得完美,但总有时间做完一件事。我们可以这样理解,做项目时必须围绕项目目标在给定的时间和资源内,把一件事情做到最好。建议放弃打造完美系统的想法,而是通过不断的试飞,发现问题,确保问题发生时系统能自动复原,而不追求飞行系统的绝对正确和安全。这就是持续集成和敏捷开发在微服务中得以应用和推广的原因。此外,这和互联网公司维护的分布式系统的弹性设计是一个道理。对于一个分布式系统,几乎永远不可能找到并修复所有的bug,单元测试覆盖 1000%也没有用,错误流淌在分布式系统的血液里。解决方法不是消灭这些问题,而是容忍这些问题,在问题发生时能自动恢复。在微服务组成的系统中,每一个微服务都可能出错,这是常态,我们只要有足够的冗余和备份即可,即所谓的弹性(resilience)设计或者叫高可用(high availability)设计。
3)定律三:也就是前面我们讲到的围绕业务功能组织团队。线性系统和线性组织架构间有潜在的一致同态特性,它们是一一对应的。这是康威第一定律组织和设计间内在关系的一个具体应用。更直白地说,想要什么样的系统,就搭建什么样的团队。如果团队分成前端团队、Java后台开发团队、DBA团队、运维团队,系统就会“长”成图2-10所示的样子。
相反,如果系统是按照业务边界划分的,即按照一个业务目标把自己的模块做成小系统、小产品,大系统就会长成图2-11所示的样子,即微服务的架构。
图2-11 微服务的架构
微服务的理念是团队间应该服务内部高内聚,服务之间低耦合。定义好系统的边界和接口,在一个团队内全栈,让团队自治,如果团队按照这样的方式组建,将沟通的成本维持在系统内部,每个子系统就会更加内聚,彼此的依赖耦合能变弱,跨系统的沟通成本也就能降低。
4)定律四:大的系统总是比小的系统更倾向于分解。前面说了,人是复杂的社会动物,人与人的沟通非常复杂。但是当我们面对复杂系统时,又往往只能通过增加人力来解决。这时,我们的组织一般是如何解决这个沟通问题的呢?答案是分而治之。业务需求驱动系统越长越大,当达到一定规模时,无论我们有没有微服务架构这个理论,我们首先想到的就是把大模块拆成小模块,把大的团队拆成小团队,只有分解了,才能把人的自由性发挥出来,才能够做好后面的事情。
人与人的沟通是非常复杂的,一个人的沟通精力是有限的,所以当问题太复杂需要很多人解决的时候,需要通过拆分组织来提升沟通效率。组织内人与人的沟通方式决定了他们参与的系统设计,管理者可以通过不同的拆分方式带来不同的团队间沟通方式,从而影响系统设计。如果子系统是内聚的,与外部的沟通边界是明确的,能降低沟通成本,对应的设计也会更合理高效。复杂的系统需要通过容错弹性的方式持续优化,不要指望一个大而全的设计或架构,好的架构和设计都是慢慢迭代出来的。
(3)特性三:关注产品而不是项目,也就是“谁构建谁运行”
传统的应用开发都是基于项目模式的,开发团队根据一堆功能列表开发出一个软件应用并交付给客户后,该软件应用就进入维护模式,由另一个维护团队负责,开发团队的职责结束。而微服务架构建议避免采用这种项目模式,更倾向于让开发团队负责整个产品的全部生命周期。亚马逊对此提出了一个观点:You build it, you run it(谁构建,谁运行)。开发团队对软件在生产环境中的运行负全部责任,让服务的开发者与服务的使用者(客户)形成每日的交流反馈,来自客户的直接反馈有助于开发者提升服务的品质。
如图2-12所示,在微服务理念下,开发团队不是完成开发、交到运维团队之后就不再管了,而是这个微团队永远为其所做的模块负责,在开发的时候就要考虑运营和运维需求,关注产品和微项目,并且改变回馈,让环路尽快有反馈。也就是说在需求阶段就要把测试做好,按照接口的约定模拟测试和服务,并且后续按照约定彼此独立上线、独立测试,尽快回路、尽快回馈、尽快反馈,这些都是微服务要关注的问题。
(4)特性四:“智能端点”与“傻瓜管道”
原来SOA讲得更多的是ESB架构,服务之间的沟通要通过总线,总线除了通信,还要负责路由、安全等各种控制。这种做法最大的弊端是:一般总线都是商用的产品,因此会有相对的语言依赖度、数据依赖度,造成对总线的依赖度很重。
如图2-13所示,对于微服务架构,我们不像原来会有一个很“重”的ESB总线,微服务架构抛弃了 ESB 过度复杂的业务规则编排、消息路由等。服务作为智能终端,将所有的业务智能逻辑在服务内部进行处理,而将服务间的通信尽可能地轻量化,不添加任何额外的业务规则。所以这里的智能终端是指服务本身,而哑管道是通信机制,可以是同步的 RPC,也可以是异步的MQ,它们只作为消息通道,在传输过程中不会附加额外的业务智能。
图2-12 微服务特性三:关注产品而非项目
图2-13 微服务特性四:“智能端点”与“傻瓜管道”
最常用的第二种协议是通过一个轻量级的消息总线来发送消息。此时所选择的基础设施通常是“傻瓜”型的(仅仅像消息路由器所做的事情那样傻瓜),如RabbitMQ或ZeroMQ那样的简单实现,即除了提供可靠的异步机制(fabric)以外,不做其他任何事情,智能功能存在于那些生产和消费诸多消息的各个端点中,即存在于各个服务中。
(5)特性五与特性六:去中心化地治理技术&去中心化地管理数据
去中心化包含两层意思:技术栈的去中心化与数据去中心化,如图2-14所示。
图2-14 微服务的特性五、特性六
每个服务面对的业务场景不同,可以有针对性地选择合适的技术解决方案。但也需要避免过度多样化,结合团队实际情况来取舍,如果每个服务都用不同的语言技术栈来实现,则维护成本很高。
不像传统应用共享一个缓存和数据库,每个服务独享自身的数据存储设施(缓存、数据库等),这样有利于服务的独立性,隔离相关干扰。
去中心化地管理数据,其表现形式多种多样。从最抽象的层面看,这意味着各个系统对客观世界所构建的概念模型将彼此各不相同。当在一个大型的企业中进行系统集成时,这就是一个常见的问题。例如,对于“客户”这个概念,从销售人员的视角看,就与从支持人员的视角看有所不同。从销售人员的视角所看到的一些被称之为“客户”的,或许在支持人员的视角中根本找不到。而那些在两个视角中都能看到的事物,或许各自具有不同的属性。更糟糕的是,那些在两个视角中具有相同属性的事物,或许在语义上有微妙的不同。
上述问题在不同的应用程序之间经常出现,同时也会出现在这些应用程序内部,特别是当一个应用程序被分成不同的组件时。思考这类问题的一个有用的方法就是使用领域驱动设计(Domain-Driven Design,DDD)中的“限界上下文”的概念。DDD将一个复杂的领域划分为多个限界上下文,并且将其相互之间的关系用图画出来。这一划分过程对于单块和微服务架构两者都是有用的,而且在服务和各个限界上下文之间所存在的自然的联动关系,能有助于澄清和强化这种划分。
如同在概念模型上进行去中心化的决策一样,微服务也在数据存储上实施去中心化的决策。尽管各个单块应用更愿意在逻辑上各自使用一个单独的数据库来持久化数据,但是各家企业往往喜欢一系列单块应用共用一个单独的数据库,许多这样的决策是被供应商的各种版权商业模式所驱动出来的。微服务更喜欢让每一个服务来管理其自有数据库,其实现可以采用相同数据库技术的不同数据库实例,也可以采用完全不同的数据库系统,这种方法被称作“多语种持久化”。在一个单块系统中也能使用多语种持久化,但是这种方法在微服务中出现得更加频繁。在各个微服务之间将数据的职责进行“去中心化”的管理会影响软件更新的管理。处理软件更新的常用方法是当更新多个资源的时候,使用事务来保证一致性,这种方法经常在单块系统中被采用。
像这样使用事务,有助于保持数据的一致性。但是在时域上会引发明显的耦合,这样在多个服务之间处理事务时会出现一致性问题。这时候数据一致性可能只要求数据在最终达到一致,并且一致性问题能够通过补偿操作来进行处理。
(6)特性七:“基础设施”自动化
如图2-15所示,微服务的每一个模块都是单独的部署单元。“无自动化不微服务”,自动化包括测试和部署。单一进程的传统应用被拆分为一系列的多进程服务后,意味着开发、调试、测试、监控和部署的复杂度都会相应增大,必须要有合适的自动化基础设施来支持微服务架构模式,否则开发、运维成本将大大增加。
(7)特性八:“ 容错”设计
如图2-16所示,微服务架构采用粗粒度的进程间通信,引入了额外的复杂性和需要处理的新问题,如网络延迟、消息格式、负载均衡和容错,忽略其中任何一点都属于对“分布式计算的误解”。容错设计就是要做好日志、做好监控,能够最快地检测出故障,尽快地恢复故障。
具体的应对措施包括:
图2-15 微服务特性七:“基础设施”自动化
图 2-16 微服务特性八:“容错”设计
网络超时:在等待响应时,不要无限期地阻塞,而是采用超时策略,使用超时策略可以确保资源不会被无限期地占用。
限制请求的次数:可以为客户端对某特定服务的请求设置一个访问上限,如果请求已达上限,就要立刻终止请求服务。
断路器模式(circuit breaker pattern):记录成功和失败请求的数量。如果失效率超过一个阈值,触发断路器使得后续的请求立刻失败。如果大量的请求失败,就可能是这个服务不可用,再发请求也无意义。在一个失效期后,客户端可以再试,如果成功,关闭此断路器。
提供回滚:当一个请求失败后可以进行回滚逻辑。例如,返回缓存数据或者一个系统默认值。
(8)特性九:“演进式”设计
如图2-17所示,微服务A和微服务B之间互相的接口叫做契约,实际上就是它们之间接口的输入请求、响应、具体的交互机制。在做微服务A和微服务B需求的同时,一定要先定义这个契约,当契约定义好时,它们彼此之间就可以独立地开发和测试。我们作为A可以按照这个契约去“打桩”,也就是做一个模拟器,不依赖于具体版本和功能是否可用,最初上线测试时使用“打桩”的模拟器去做相应的测试。同样,B也按照A给的请求反应去“打桩”,它也不依赖A,即B可以独立开发、测试。只要契约没有变化,它们彼此之间就相互独立,彼此之间没有依赖,就可以按照各自的版本去做自己的更新;当契约发生变化时,一定要做相应的沟通,重新提供“打桩”,提供彼此之间规范的模板。所以当我们做了微服务以后,在需求阶段最重要的是要先定义好对外部暴露哪些API,内部有什么功能,外部暴露什么,我们不可能将内部逻辑全部暴露,暴露得越多,所要做的控制就越多,这是做微服务时最先考虑的。所以在满足外部请求的同时要考虑有哪些值得暴露的API。
图2-17 微服务特性九:“演进式”设计
一旦采用了微服务架构模式,那么在服务变更时要特别小心,服务提供者的变更可能引发服务消费者的兼容性被破坏,要时刻谨记保持服务契约(接口)的兼容性。一条普适的健壮性原则(伯斯塔尔法则)给出了很好的建议:Be conservative in what you send, be liberal in what you accept(发送时要保守,接收时要开放)。按照伯斯塔尔法则的思想来设计和实现服务时,发送时要保守意味着要最小化地传送必要的信息,接收时更开放意味着要最大限度地容忍冗余数据,保证兼容性。
每当试图要将软件系统分解为各个组件时,就会面临这样的决策,即如何进行切分,我们决定切分应用系统时应该遵循的原则是什么?一个组件的关键属性是具有独立更换和升级的特点,这意味着,需要寻找这些点,即想象着能否在其中一个点上重写该组件,而无须影响该组件的其他合作组件。事实上,许多做微服务的团队会更进一步,他们明确地预期许多服务将来会报废,而不是守着这些服务做长期演进。这种强调可更换性的特点,是模块化设计一般性原则的一个特例,通过“变化模式”(pattern of change)来驱动并进行模块化的实现。大家都愿意将能在同时发生变化的东西放到同一个模块中。系统中很少发生变化的部分应该被放到不同的服务中,以区别于当前正在经历大量变动的部分。如果发现需要同时反复变更两个服务时,这就是它们两个需要被合并的一个信号。
把一个个组件放入一个个服务中,增大了更精细地实现软件发布计划的机会。对于一个单块系统,任何变化都需要做一次整个应用系统的全量构建和部署。然而,对于一个个微服务来说,只需要重新部署修改过的那些服务就够了,这能简化并加快发布过程。但缺点是必须要考虑当一个服务发生变化时,依赖它并对其进行消费的其他服务将无法工作。传统的集成方法是试图使用版本化来解决这个问题。但在微服务世界中,大家更喜欢将版本化作为最后万不得已的手段来使用。我们可以通过把各个服务设计得尽量能够容错,来应对其所依赖的服务所发生的变化,避免许多版本化的工作。
以上这9个特性,从侧面告诉我们微服务如何实现自治、独立,如何与别人沟通,如何按照自己的约定执行自己的版本计划,如何容错、自动化、去中心、关注产品、尽快响应。那么把一个单体服务按照业务需求或是数据分类直接拆分了,微服务就能上线了吗?答案是否定的,因为如果没有设计日志、监控、消息的管理,以及服务的发现机制和整个服务的负载均衡与配置,那么整个“拆合”的工作在“合”的层面都没有做好,在这种情况下我们无法实现微服务体系。
OpenStack 云计算
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。