[译]如何走钢丝:掌握微服务架构中的四个决策

原文:Walking the wire: Mastering the Four Decisions in Microservices Architecture
作者:Srinath Perera

胖达注:这篇文章发布于2016年3月10日,虽然将近三年了,但是方法论的东西并不会过时。

任何对此文感兴趣的读者请花费50美元/年或5美元/月的价格参加Medium会员计划,否则每月只能查看三篇文章。
一个月一餐盒饭钱,我觉得并不贵,你说呢?

以下为本人对文章的翻译。不对内容负责,也不对任何翻译错误负责,同时不包含任何图片内容,如需查看图片请付费查看原文链接,谢谢。


微服务是使用简单,轻量,松散耦合的服务构建系统的新架构风格,可以相互独立地开发和发布。

如果您不熟悉微服务,我建议您阅读Martin Fowler的帖子。如果您想将其与SOA进行比较,请观看Don Ferguson的演讲。此外,Martin Fowler的“ trade-off of microservices ”和“ when it is worth doing microservices ” 时,可以帮助您确定微服务何时有用。

假设你听过,了解并且相信微服务。微服务架构的追随者面临着一些实际挑战。本文讨论了处理其中一些挑战的问题。我将在其余讨论中使用MSA来表示微服务架构。

决定1:不共享数据库

此处有图

每个微服务都应该有自己的数据库,数据绝对不能通过同一个数据库共享。此规则消除了导致服务之间紧密耦合的常见原因。例如,如果两个服务共享同一个数据库,则第二个服务将在第一个服务更改了数据库Schema时离线。所以,在更改数据库之前,团队必须互相沟通,导致延迟,一切和不使用MSA时一样。

我认为这条规则很好,不应该被打破。

但是,有一个问题。当两个服务共享相同的数据(例如银行帐户数据,购物车)并且需要使用数据库事务来强制一致地更新数据时,我们经常共享数据库。

任何其他解决方案都很难。我们来探讨一下。

解决方案1:如果仅会在一个微服务中发生数据更新(例如贷款审批流程检查余额),则可以使用异步消息(消息队列)来共享数据。

此处有图

解决方案2:如果两个服务都会发生数据更新,请考虑合并这两个服务或使用事务。帖子 Microservices: It’s not (only) the size that matters, it’s (also) how you use them 描述了第一个选项。下一节将详细介绍事务。

处理更新的一致性

我们已经讨论过如何在单个服务更新数据时处理这种情况。让我们讨论当多个服务更新数据时如何对它们进行数据处理。我们在前面的部分讨论过一个例子。

通常,我们使用事务来解决此问题,这在分布式设置中使用时会造成系统缓慢,繁重且昂贵。但是,我们有时可以在不引入事务的情况下解决问题。

有几种选择。

选项1:将所有更新都放在同一个微服务中

如果可能,请避免跨越微服务边界的多个更新。然而,有时通过这样做,你最终会得到较少的服务或糟糕情况下的一个巨大的独石应用,又退回去了。因此,有时候,这是不可能的。

选项2:使用补偿和其他低保证措施

正如著名的帖子“ Starbucks Does Not Use Two-Phase Commit ”所描述的那样,普通世界没有事务。例如,星巴克的咖啡师不会等到您的交易完成。相反,他们同时处理多个客户,同时补偿任何发生的错误。如果你愿意做更多的工作,你也可以这样做。

一个关键点是,如果一个动作失败,你可以补偿。例如,如果您要运送这本书,首先扣除这笔钱,然后运送书籍,如果运费失败,则退还款项。

另一个简单的想法是给用户一个按钮,如果他可以判断它已经过时,就强行刷新页面。此外,有时最终的一致性或超时就足够了。你咬紧牙关,并接受较低的一致性(例如Vogel的帖子是一个很好的起点)。

最后,Life Beyond Distributed Transactions:Anpostate's Opinion 是对所有技巧的详细讨论。

话虽如此,一些用例需要事务以获得正确的结果。它们就必须使用交易。请参阅 Microservices and transactions-an update,权衡利弊并明智地选择。

决定2:微服务安全性

在微服务之前,服务通过在收到请求时调用数据库或身份服务器进行身份验证。

此处有图

在MSA中,我们可以用微服务替换身份服务器,在我看来,这会导致一个大的复杂的依赖关系。

相反,我喜欢“构建微服务”一书中描述的基于令牌的方法,并由下图描述。

此处有图

客户端与身份或SSO服务器通信,对自身进行身份验证,接收使用SAML或OpenIDConnect描述用户及其角色的签名令牌,并在每次请求时将令牌发送到微服务。每个微服务都验证令牌并根据令牌中描述的用户角色授权调用。此模型将身份验证推送到客户端,并在微服务上进行访问控制,同时简化依赖关系。例如,对于此模型,对于同一查询,具有角色“publisher”的用户可能会看到与具有角色“admin”的用户不同的结果,因为他们具有不同的权限。

值得注意的是,客户端可以获取令牌一次并重复使用它,每次session只会发生一次。因此,额外调用的开销很小。

How To Control User Identity Within Microservices?提供有关此方法的更多信息。

决定3:处理微服务组合

这里,“组合”意味着“如何将多个微服务连接到一个流程以提供最终用户的需求”。

大多数使用SOA的组合看起来如下。有一个运行工作流程的中央服务器。

此处有图

SOA组合使用集中式服务器(例如ESB或工作流引擎)。MSA不鼓励使用ESB(例如 Top 5 Anti-ESB Arguments for DevOps Teams)。另一方面,Do Good Microservices Architectures Spell the Death of the Enterprise Service Bus也提供反驳论据。

我不打算在这篇文章中深入ESB太多。但是,我想讨论是否需要一个中央服务器来进行微服务组合。有几种方法可以完成微服务组合。

方法1:从客户端驱动流程

下图显示了在没有中央服务器的情况下执行微服务的方法。客户端浏览器处理流程。帖子, Domain Service Aggregators: A Structured Approach to Microservice Composition,就是这种方法的一个例子。

此处有图

这种方法存在几个问题。

  1. 现在需要由客户端触发多个呼叫。因此,如果客户端位于慢速网络后面,这是最常见的情况,则执行速度很慢。
  2. 可能会在浏览器中运行某些逻辑时添加安全问题(我可以破解我的应用程序给我自己发布一笔贷款)
  3. 以上示例专注于网站,但大多数复杂的组合通常来自其他用例。因此,在客户端组合微服务对其他用例的普遍适用性尚待证明。
  4. 在哪里保持状态?可以信任客户端以保持工作流的状态吗?使用REST维护状态是可能的。但是,它很复杂。

方法2:协同

从中心位置指定工作流称为编制。但是,这不是协调多个合作伙伴开展某项工作的唯一方法。例如,在舞蹈中,没有一个人指导表演。相反,每个舞者跟随谁靠近就跟他同步。协同将相同的想法应用于业务流程。

典型的实现包括事件系统,其中该过程中的每个参与者收听不同的事件并执行他或她的部分。每个操作都会生成异步事件,从而在流中触发参与者。像RxJava或Node.js这样的环境是使用它的编程模型。

例如,假设贷款流程包括请求,信用检查,其他未偿还贷款检查,经理批准和决策通知。下图显示了如何使用编排实现此功能。请求被放置在队列中,由下一个节点接收,他将结果放入下一个队列,并且该过程一直持续到完成为止。

此处有图

协同就像一个舞蹈。两者都很复杂,需要练习才能正确执行。例如,程序员不知道进程何时完成,是否发生错误,或进程是否卡住。协同需要广泛的监控,来跟踪进度,通知错误或从中恢复。

另一方面,协同创建了松散耦合的系统,这是它的主要优点。例如,您可以在不更改其他节点的情况下向流程添加新节点。您可以在Scaling Microservices with an Event Stream中找到更多信息。

方法3:集中服务器

最后但最简单的选择是集中式服务器(即编制)。

SOA使用两种方法实现组合:ESB或业务流程。MSA提出了一个API网关(例如,Microservices: Decomposing Applications for Deployability and Scalability)。我猜API网关更轻量级,并使用REST / JSON等技术。但是,在纯粹的架构意义上,所有这些都使用中央服务器完成编制。

集中式服务器的另一种变体是“服务于前端的后端”(BEF),它为每个客户端类型构建服务器端API(一个用于桌面,一个用于iOS等)。此模型为每个客户端类型创建不同的API,并针对每个用例进行了优化。有关详细信息,请参阅模式:Backends For Frontends

我建议不要对这里的所有选项操心太多,直接从API网关开始,因为这是最简单的方法。您可以根据需要增加切换到更复杂的选项。

决定4:避免依赖地狱

MSA的目标是使服务可以独立发布和部署。要做到这一点,你必须避免依赖地狱。

让我们考虑使用API“A1”的微服务“A”并将升级到API“A2”。现在有两种情况。

  1. 微服务B可能会将用于A1的消息发送到A2。需要向后兼容性来支持这种情况。
  2. 微服务A可能必须回滚到A1,微服务C可能继续发送A2版本的消息到A1。

如果要独立发布微服务,则必须处理上述情况。否则,你所有的建立MSA的努力都被浪费了。

通常,处理这些情况的办法是添加可选参数而永不重命名或删除现有参数。然而,更复杂的情况是可能的。

帖子 “Taming Dependency Hell” within Microservices with Michael Bryzek 详细讨论了这一点。Ask HN: How do you version control your microservices? 也是另一个很好的来源。

最后,应该通过时间限制向后和向前兼容性支持,以避免并发症。例如,您可以有一条规则,即微服务不应该依赖超过三个月的API。这将让微服务开发人员最终放弃支持旧版本的一些代码分支。

最后,我想谈谈你的依赖图在微服务架构中应该是什么样子。

一种选择是在需要时自由调用其他微服务。这将创建一个前ESB时代的意大利面建筑。我不是那种模型的粉丝。

另一个极端是说微服务不应该调用其他微服务,所有连接都应该通过API网关或消息总线完成。这将导致一个树形结构。例如,不是微服务A调用B,而是将微服务A的结果带到网关,网关再根据结果调用B. 这是编制模型。现在,大多数业务逻辑都将存在于网关中。是的,这使网关变重了。

我的建议是要么选编制模型,要么辛苦的实现协同。是的,不要选意大利面模式。

结论

微服务的目标是松耦合。精心设计的微服务架构允许您使用一组微服务实现项目,其中每个微服务都是独立管理,开发和发布的。

当您使用微服务进行设计时,您必须密切关注关键,即“松耦合”。有很多挑战,这篇文章回答了以下问题。

  1. 如何处理需要在两个微服务之间共享数据的场景?
  2. 如何在保持松散耦合的同时迭代微服务API?
  3. 如何处理安全问题?
  4. 如何协同微服务?

谢谢!欢迎评论。

如果您喜欢这篇文章,您可能还会发现以下内容有用。