专栏名称: 狗厂
目录
相关文章推荐
51好读  ›  专栏  ›  狗厂

响应式微服务架构-分布式系统设计原则

狗厂  · 掘金  ·  · 2018-06-07 11:10

正文

O’Reilly的电子书《Reactive Microservices Architecture》讲述了微服务/分布式系统的一些设计原则,本文是笔者阅读完此书后的理解。书籍地址: info.lightbend.com/COLL-20XX-R…

微服务相比传统的单体应用能够带来快速的响应,以小的系统产生大的影响。而随着网络加速、磁盘成本降低、RAM成本降低、多核技术的发展、云架构技术的爆发,微服务不再受这些客观条件的限制,已经开始大规模的应用。

与SOA架构,微服务和它都具有相同的初衷:解耦、隔离、组合、集成、分散以及自主,但是SOA经常被误解和误用,尤其是使用ESB来支持对多个单体系统的协议(复杂、低效、不稳定)调用,使得系统变得非常复杂。而随着这些年硬件以及软件架构理念的发展,所有的系统基本都已经变成分布式架构,也带来了很多新的挑战。也就需要新的思路和理念来面对这些问题,其中本书所讲述的响应式原则(Reactive principles)即一种解决分布式系统的思路。响应式原则也并非一个新的东西,Erlang中的Actor模型即一种响应式设计。微服务是响应式原则的一个架构设计,其借鉴了SOA架构中好的理念,并使用了现代的基础服务设施(云服务、自动化工具等)。

响应式微服务定义

使用微服务架构最关键的一个原则就是将系统划分成一个个相互隔离、无依赖的子系统,这些子系统通过定义良好的协议进行通信。其中隔离是实现弹性、可伸缩系统的前提,并且需要在服务间建立异步通信边界,因此要在以下两方面进行解耦:

  • 时间:允许并发。
  • 空间:允许分布式和移动性,即服务能够随时移动。

此外,微服务还需要消除共享状态从而最小化相互协作、联结的成本,要尽量达到“不共享任何东西”。

隔离任何东西

隔离是微服务架构中最重要的特性。不仅仅是微服务带来的很多优势的基础,也是对设计和架构影响最大的方面。如康威定律所说,它还对组织架构有非常大的影响,

系统的结构是对团队组织架构的反映。

失败隔离是一种与“舱壁”(船舱的隔板)相关的设计模式:隔离错误、失败以防止其蔓延至所有服务,导致更大面积的失败。

“舱壁”这种模式已经在轮船上使用了几个世纪:创建一个个密封不漏水的空间以防止船的外壳破损或者其他泄漏。这些空间是完全互相隔离的,这样即使一个隔离区充满了水,也不会蔓延流到其他隔离空间中,从而使得船整体仍然能够运作。

弹性(从失败中恢复的能力)即依赖于这种舱壁和失败隔离的设计,并且需要打破同步通信机制。由此,微服务一般是在边界之间使用异步消息传输,从而使得正常的业务逻辑避免对捕获错误、错误处理的依赖。

进一步的,服务之间的隔离使得“持续交付”变得很容易,能够随时地部署服务而无需担心影响正常的业务。而且隔离的单个服务很容易监控、调试、测试和部署,非常便于扩展。

自主地行动

上面所讲的隔离是自主性的前提。只有当服务之间是完全隔离的,那么才可能实现完全的自主,包括独立的决策、独立的行动以及与其他服务协调合作来解决问题。

一个自主的服务仅仅保证其对外公布的协议/API的正确性即可。如此,不仅能够让我们更好地了解协作的这些系统以及对他们的建模,也能够在面对冲突、失败状况时,只在一个服务内进行排查、修复即可。

使用自主服务能够给服务编排、工作流管理以及服务合作上带来很大灵活性,同时也带来可扩展性、可用性、运行时管理等优势。但其付出的代价就是需要花心思在定义良好的可组合的API设计上,这个是有一定挑战性的。

只做一件事,并且做好

如Unix编程哲学所说:程序应该只做一件事,并且做好它。然后让他们一起工作完成任务。这也类似于面向对象编程语言中软件开发单一职责原则(SRP)的描述。

而在微服务中一个很大的问题就是如何正确地确定服务的大小。比如怎样的粒度才能被认为是“微”(micro)?多少行代码还能被认为是微服务。但这里“micro”其实是和职责范围有关的,就如Unix的SRP原则:只做一件事并且做好。

每一个服务都应该只有一个存在的原因,提供了一组相关的功能,业务和职责不会糅杂在一起。所有服务组织在一起整体上能够便于扩展、具有弹性、易理解和维护。

拥有自己的私有状态

微服务中有一个很关键的部分就是状态(state),很多微服务也都是有状态的实体,包括对状态和行为的封装。而在“无状态”的设计理念下,很多服务都把自己的状态下沉到一个大的共享数据库中,这也是很多传统的Web框架的做法。如此就造成了在扩展性、可用性以及数据集成上很难做好把控。而本质上,一个有着共享数据库的微服务架构本质还是一个单体应用。

合理的方式是一个服务既然具有单一职责,那么就应该拥有自己的状态和持久化机制,建模成一个边界上下文,有自己的域名和语言。这些也都是DDD(Domain-Drivern Design)里面的技术。微服务受DDD影响很大,其中很多微服务的上下文的概念都来自于DDD。

当访问一个服务时,也只能是客气的请求其状态而并不能强制其一定具有状态。如此,每个服务都能够通过事件溯源(Event Sourcing)和CQRS(Command Query Responsibility Segreation)自定义自己的状态表示和实现(RDBMS、NoSQL、Time-Series、EventLog)。

去中心化的数据管理和持久化(多语言持久化)能够带来很多优势。数据的存储媒介可以根据服务自己的需要选择,服务包括其数据都可以看做一个单独的单元。同时并不允许一个服务直接去访问另一个服务的数据库,如果要访问只能通过API(通过指定规范、策略和Code Review来保证)。

Event Log是一种消息的存储方式。我们可以以消息进入服务的形式存储(发送到服务的Commnds),即命令溯源(Command Sourcing)。我们也可以忽略命令,让命令先执行对服务产生一些作用,如果触发了状态变更,那么我们捕获此次变动并用事件溯源(Event Sourcing)将此次Event存储到EventLog中。

消息有序存储,能够提供服务所有的交互历史。同时消息也保存了服务的事务,也就能够对这些事务日志进行查询、审计、重放从而用于弹性伸缩、调试以及冗余等。

命令溯源和事件溯源是不同的语义。重放命令意味着会重放其带来的副作用。而重放事件则是执行状态的改变。需要根据具体场景的不同选择使用哪种溯源技术。

使用EventLog可以避免”对象关系不匹配”的问题(ORM中经常出现)。而由于其自身天然适合异步消息传输,因此绝大多数情况下,Event Log是微服务中最佳的持久化模型。

拥抱异步消息传输

微服务之间的通信的最佳机制就是消息传输。如上文所说,服务之间的异步边界能够在时间和空间两方面进行解耦,能够提升整体系统的性能。

异步非阻塞执行以及IO都是对资源的高效操作,能够最小化访问共享资源时的阻塞消耗(扩展性、低延迟以及高吞吐的最大障碍)。简单的例子:如果要发起对10个服务的访问,其中每一个请求需要耗时100ms,那么如果使用同步模式,则完成所有请求则需要10*100=1000ms。而如果使用异步模式,同时发起10个线程,则一共就需要100ms。

异步消息传输还能够让我们注重网络编程的限制,而不是假装这些限制不存在,尤其是在失败场景下。还能够让我们更关注工作流以及服务间的数据流、协议、交互是怎样进行的。

然而目前微服务的默认通信协议以REST为主,其本质是同步通信机制,比较适用于可控的服务调用或者紧耦合的服务调用上。

此外,使用异步消息传输的另一个需求在于对消息的持续流处理(可能是无界的)。也是我们从“data at rest”到”data in motion”的理念的改变。之前的数据是离线被使用的,而现在的数据是被在线处理的。应用对数据变更的响应需要达到实时级别:当变动发生,需要实时进行持续的查询、聚合并反馈给最终的应用。这个理念的形成经历了三个主要阶段:

  1. “data at rest”: 将大量数据存储在HDFS类似的数据存储媒介中,然后使用离线批处理技术去处理这些数据,一般会有数个小时的延迟。

  2. 意识到了“data in motion”正变得越来越重要:在数秒内捕获数据、处理数据并反馈给运行中的系统。Lambda即此时出现的一种架构: 加速层用来做实时在线计算;批处理层用来做复杂的离线处理。加速层实时处理的结果后续被批处理层的结果合并。这个模型解决了某些场景需要数据即时响应的问题,但其架构的复杂使得不容易维护。

  3. “data in motion”: 全面拥抱移动数据的概念。传统的面向批处理的架构都在逐渐向纯流处理的架构转变。这种模型作为通信协议和持久化方案(通过Event Logging)也能够给微服务带来“data in motion”和流处理的能力。

保持移动,但可寻址

如上述所讲,异步消息传输带来了对时间和空间的解耦。其中,对于空间的解耦也被称为“位置透明”:在多核或者多结点上的微服务在运行时无须改变结点即可以动态扩展的能力。这也决定了系统的弹性和移动性。要实现这些需要依赖云计算带来的一些特性和其“按需使用”模型。

而可寻址则是说服务的地址需要是稳定的,从而可以无限地引用此服务,而无论服务目前是否可以被定位到。当服务在运行中、已经停止、被挂起、升级中、已经崩溃等等情形下,地址都应该是可用的,任意客户端能够随时发送消息给一个地址。实际中,这些消息有可能进入队列排队、重提交、代理、日志记录或者进入死信队列。此外,地址需要是虚拟的,可以代表一组实例提供的服务。

  • 在无状态的服务间做负载均衡:如果服务是无状态的,那么请求被哪一个服务实例处理都是没任何问题的。也有很多种的路由算法供使用,如:轮训、广播或者基于度量信息。
  • 在有状态的服务之间构建Active-Passive的冗余设计:如果一个服务是有状态的,那么可以使用sticky路由算法(同一个客户端的请求都会发送给同一个服务实例)。冗余一个passive实例是为了在主实例挂的时候接管上面的请求。因此,服务的每一个状态变动都需要同步到passive实例上。
  • 有状态的服务的重定位:将一个服务实例从一个位置移动到另一个位置可以提高引用的本地性(让数据和计算靠近)和资源利用率。

使用虚拟地址能够让服务消费方无须关心服务目前是如何配置操作的,只要知道地址即可。

微服务系统实现

一个微服务并非真正的“微服务”,一系列微服务通过通信、合作才能够解决问题,才能组成一个完整的业务系统。实现一个服务是相对简单的,困难的是其他基础设施的实现:服务发现、协作、安全、冗余、数据一致性、容错、部署以及与其他系统的集成。

系统需要利用现实

微服务架构带来的一个很大优势就在于它提供了一套工具,能够利用现实,模仿真实的世界来创建系统,包括真实世界的限制和机会。

首先根据“康威定律”,微服务的部署是和现实中工程组织/部门如何工作是相适应的。此外,还需要注意的是现实不是一致的,任何事情都是相对的,即使是时间和“现在”这个概念。

信息的传播速度不可能比光快,甚至大部分是很慢的,这也意味着信息通信是有延迟的。信息都是来自过去的,我们稍微思考一下可以知道信息承载的都是我们观察到的东西。而我们观察/学习到的事实至少都是很短时间之前发生的,也就是说我们总是在看过去,“现在”只是旁观者的视角。

每一个微服务都可以看做一个安全的小岛,提供了确定性和强一致性,上面的时间和“目前”都是绝对的。但是当离开一个微服务的边界时,就进入了一片充满非确定性的大海-分布式系统的世界。如很多人所说,构建分布式系统是困难的。但现实世界同时也提供了如何解决诸如弹性、可伸缩、隔离性等分布式问题的解决思路。因此,即使构建分布式系统是困难的,但是我们也不应该退化为单体应用,而是学习如何使用一系列的设计原则、抽象概念和工具来管理它。

正如Pat Helland在《Data on the Outside versus Data on the Inside.”》对”data on the inside”和“data on the outside”的对比所说:内部的数据就是我们本地的“目前”,而外部数据-事件即是来自过去的信息,服务之间的命令则是“对未来的希望”。







请到「今天看啥」查看全文