返回 登录
0

三个简单要素让你的微服务更有弹性

阅读5071

基于Microservices(微服务)来构建分布式系统的优点之一是系统作为一个整体有能力能够承受错误和意外失败的组件、网络、计算资源等。这样的系统对错误更有弹性。容错性背后的原理很简单:假如我们的单体程序出错了,那么它所承担的所有功能都会随之出错;因此让我们把系统分解到更细小的粒度,让系统的任一独立模块出错时而不至于会影响到整个系统的运作。这在理论上听起来不错,但是是否意味着将系统简单的分解成小块给我们就是微系统?

其中一个测试方法是使用Netflix所说的“chaos monkey” 或类似的“混乱”策略:在系统中引入干扰的目的在于证明系统的弹性(或脆弱性)。只有在系统出错时才显示出它的本色,而不是当一切都一帆风顺时,所以人为的引入错误是一种非常好的方式来挖掘出我们所创建系统的真实面貌。实际上,你真正需要做的是建立antifragile的系统,但那不是这篇文章的主题,我们会在另外的文章中阐明。

将系统分解成小块可以给你的系统带来更多弹性,但并不意味着不需要对此深谋远虑。例如,系统模块的粒度分解越小,我们就越多的需要“编排”或依赖“下游”服务的数据,功能。假如这些下游服务宕机了,我们的服务又会如何表现?当这种相互依赖性越多时,我们的服务在运行时的相互联接就越多。假如有个我们所依赖的服务改变了它的接口、负载或是事件结构,这又将怎样的影响我们的服务?我们会被迫根据交互程序的变更而变更吗?假如如此,那么这种架构是非常脆弱的。服务提供者是否允许多个版本的服务共存为我们提供服务?

很多时候我们(或暗示)依靠一次并且只调用一个服务对于一个给定的事件。或者至少一次且仅一次处理。如果一个上游的服务体验网络延迟和重试?我们可能会有多个调用。我们做什么呢?很多时候,我们都依赖于或者说默认每个触发的事件都会导致一次且仅一次的服务调用,或者说这些事件会被一次且仅一次的处理。那么假如当上游服务发生网络延迟和重发时呢?我们可能会面对对同一事件的多重调用,我们该如何处理这种情况?

这些微服务的细节在各种会议及相关活动炒作时往往被忽略,但是它们是真实存在的。接下来我将努力介绍这些“微服务实现细节”,作为我建立一个不平凡的循序渐进的一个“微服务”范例(我将在我的Twitter@christianposta上发表它们)。仅仅是说我们将实施微服务并不会解决那些分布的问题。下面让我们来看三种相当容易理解的,你能用来实践构建你的微服务系统并增加弹性的模式。

图片描述

Promises(承诺)和Fallbacks(回退)

Promises(承诺)理论,由Mark Burgess首先提出,描述系统间如何相互作用,它表明,我们的系统可能并不是和我们所期望的行为一致。服务提供者发布“意图”给“执行系统”,事实上真正的执行结果并不一定是我们所期望的。在很多时候,就像是我们人类相互交互一样。我们越是把微服务看作一个复杂系统中独立、自治的代理,我们就越是需要尊重这种自治通过理解这些系统可能在某些时候自愿提供某些服务,而有时则不能。

所以当事情没有按计划进行时,会发生什么?让我们来看看一个非计算机的例子。假设我当前是一个顾问为我的客户提供服务。也许我是一个建筑师,帮助你建立微服务架构并且我承诺提供一个现场建筑工作室。这是我自愿为你提供这个服务。假如我到你公司的航班被取消了 (例如:我曾经尝试飞往O’Hare)?我是否会打给你说“对不起,不能提供现场工作室了,因为我的航班被取消了”?我想我会的。那么下次你问我提供现场工作室时可能考虑一些额外因素。比如我会说“对不起,我的航班被取消了,也许我能找到另一个航班?”或“也许我可以提供远程服务”,或“我们可以重新安排日程吗?“我是自愿承诺提供微服务的工作室,所以我只会在我力所能及的范围内提供这个服务。

在微服务架构中提供服务时,这种思考方式是很重要的。当合作者服务不可用怎么办?我的Fallback(回退)程序是可用的吗?很多时候这种回退服务可能是业务所必须的。也许你需要返回一个封装回应。也许你可以调用不同的服务作为备份。也许你自己做一个简化计算。无论如何,在面对一些意想不到的错误时,你应该考虑选择什么来帮助完成或部分完成服务承诺。Apache CamelNetflix Hystrix 也有助于此。

消费者契约

自从我们的SOA时代开始,我们就根深蒂固的认为服务合同就是定义了服务提供者应该提供的东西。在上面关于承诺的讨论中,就是关于服务提供者的“意图”。然而,从上述讨论我们也可以看到,提供者可能在某些情况下不能履行承诺,也许它会返回其他东西。这个时候,消费者应该如何反应?

服务提供者提供了某种形式的合同(即文档或模式描述请求的有效负载和制式响应)和提供者所规定的符合消费者需求的这些文档和实现其内部数据模型。然后消费者会将它分解,甚至验证这些服务交互的内容负载。现在如果提供者最终改变合同(如添加新字段)的分解和验证这些数据有效载荷可能被打破。这并不是个好方式,因为我们重视我们服务的自治权。我们应该能够改变某个服务而不至于强迫其他服务产生连锁反应。

一个解决方案是基于以下原则即“严出宽进”。基本上,我们只做“基本足够”的响应验证以及提取我们所需的数据,而不是去验证完整的数据。这意味着我们的数据编出逻辑应该足够聪明到解决的部分数据模型/响应,它不知道(或)关心数据的整体。此外,如果我们可以捕获的部分消费者真正关心的响应,我们就可以开始这一反馈循环返回给服务提供商,帮助他们理解实际上服务和消费者之间真正使用的服务由变化导致的变化。Thoughtworks的Ian Robinson在下面文章中有关于这个问题的阐述: Consumer Driven Contracts: A Service Evolution Pattern。Schema registries可以为此提供帮助。

幂等消费者(Idempotent Consumers)

当事情出错时,会发生什么?或者当服务失败?服务可能会卡在一个事务的中间。行为不端的服务请求可能无意中冲击我们的服务。消费服务网络中可能会经历延迟(假设部署在云上!)和可能超时并重试。一个系统,期望获得一次且仅一次传送消息的是脆弱的定义。如果你构建你的服务能够处理这些 “意想不到”类型的行为,那么它会更有弹性。因此我们需要幂等服务(idempotent services)。

一个例子不在“deltas”系统之间交换消息。这些都不是幂等消息,如果你多次收到一个消息说“X+20”,你可能会得到一个不一致的值。这里也许在消息中增加一个“当前值”是个好主意,这样无论你收到多少次消息,它们都不会导致一个不一致的值。

另一个选择是使用可以过滤掉重复消息的基础设施。例如,在故障场景中, Apache ActiveMQ可以过滤掉生产者发送给代理最终由于某些原因错误的终结在不同代理点上的重复消息。代理索引可以追踪和识别重复消息并丢弃它们。

另一个选择是在你的服务中跟踪消息的惟一标识符并拒绝接受那些已经成功处理的。将该标识符信息存储在一个LRU缓存帮助你快速诊断你是否已经处理过某个消息并返回一个制式响应,原始响应,或者忽略它。Apache Camel能够让你很容易使用这个idempotent-consumer模式的来构建服务。

虽然你如何实现这些模式并不重要,我们所需要的就是系统能够优雅的容错。所有这些都是久经考验的模式。没有什么想法是全新的,但我不认为它们经常被实现。恕我直言,它们应该被实现。上述三个建议将帮助你构建弹性服务,尽管它们不是唯一需要考量的。其他需要考虑的包括隔离、bulkhead模式、负载均衡、服务发现、apologies,事务一致性等等,都能帮助提高系统弹性。如果微服务的优点之一是弹性方面,我们应该基于这些最前沿的概念来设计我们的微服务架构。

译者:王旭敏,Nokia开发工程师,关注云计算、高性能及可用架构、容器等。
责编:魏伟,欢迎加入微服务技术交流群,搜索“k15751091376”,由群主拉入
原文链接: https://dzone.com/articles/3-easy-things-to-do-to-make-your-microservices-mor

评论