REST 是微服务中最好的架构吗?
【编者的话】本文作者Craig Williams通过人们对微服务误解切入,探讨了微服务的各种架构,最终给出开发者的启示是根据自己的业务逻辑,构建适合自己的微服务架构。
在我的微服务的旅程中,明显表明大多数关于在线样例/如何类的文章只专集中在REST作为微服务相互通讯的手段。正是因为如此,你可能会误认为REST风格的微服务时事实的标准,并努力使用这种方法设计和实施一个基于微服务的系统。事实并非如此。
REST
基于REST服务的例子这么流行不仅仅因为它们的简单,服务直接通讯和在http之上的相互同步,而且因为它们不需要任何额外的底层架构。
比如考虑一个通知客户特定产品库存的系统。这可以通过RESTful实现如下:
- 一个外部实体发送一个清单更新请求到一个REST网关地址。
- 网关转发这个请求给清单管理服务。
- 清单管理服务基于它接收的请求做清单的更新然后发送一个请求给后端库存通知服务。
- 后台库存通知器发送一个请求给订阅管理器,请求所有的注册条目已经存储在后台库存中的用户。
- 接着emails通过email服务会轮流给每个用户发送一个email REST请求。
- 每个服务则依次进行响应,退绕回网关并返回结果给客户端。
应当指出的是,尽管通信是点至点,硬编码的服务地址将是一个非常糟糕的设计选择,这极大违背了微服务的设计初衷。至于服务发现机制的使用,像Eureka 或者 Consul,它们机制是服务注册它们可用的API到一个中心的服务器上,然后客户端请求一个指定的API从这个服务器。
更深入讨论,在这种方式的实现上,也有一些底层的缺陷或事情需要考虑。
阻塞
由于REST的同步的特性,更新库存操作将不返回,直到通知服务已完成通知所有相关客户的任务。想象一下这样做的效果,若果一个特定的产品非常受欢迎,别的库存的客户希望1000s被通知到。性能可能被严重影响并且可扩展性将会受到阻碍。
‘当一个产品到货,客户应当被通知’的认识被根深蒂固的认为应当由清单管理服务来做,但是我不这样认为。该服务的单一职责应及时更新系统库(库存的合并),别无其他。事实上,它不应该甚至不需要了解通知服务的存在。这两个服务紧密的耦合在这个模型中。
服务何时发布
未来服务失败时,在这些情况下,基于系统的微服务应该继续尽可能的发挥作用。由于上面描述的系统是紧耦合的,库存管理内部需要一个故障策略处理方案(比如)告知后台库存通知器实在哪里不可用。可能是库存更新失败?可能是服务重试?同样重要的是,请求到通知的失败应尽可能快,一些断路器模式(例如Hystrix)会对此有所帮助。尽管在失败的情况下将考虑通信的方法来处理,然而把所有逻辑封装到调用服务将会使调用服务变得臃肿。说回单个服务负责的问题,我的意见是不应该由清单管理负责处理,清单管理负责处理只会使通知器变黑。
管道
克服服务紧耦合的一个方法是把负责的程序从一个微服务移动到如下的企业模式的管道。我们的子系统现在是这样的:
通信可能一直是基于REST的,但是不在是点到点的;它现在是管道实体来编排的数据流,而不是服务本身负责。这克服了耦合的问题(通过一部管道,阻塞一些工作),它被认为是微服务通讯中的好的做法,尽可能争取服务的独立和一致性。采用这种方法,这些服务实体必须依靠第三方实体(管道编排),以便作为一个系统运行,因为没有特别的自给自足。
比如,通知管道将接受一个单独相应从后台订单通知器(即使有两个订阅者),但是必须配置这样一种方式,它可以解析响应,以便随后它可以为每个订阅者发送单个‘发送邮件’请求到邮件通知器。可以这样认为,电子邮件发送者可以通过单个请求批量修改发送邮件给许多不同的订阅者,但是如果以此为例,每一个用户名必须包括邮件体并且还要有某种令牌替换功能。在通知器有特定的知识关于邮件发送者的地方引入了额外的耦合行为。
异步消息
在一个基于消息传递系统中,来自服务的输入和输出都被定义为命令或者事件。每个服务订阅它们感兴趣的消耗事件,然后当事件通过其他服务被放到队列后,它通过一个机制像消息队列/代理可靠地接收事件。
通过这种方法,库存通知子系统现在能被重构如下:
通过队列名的共享信息获得凝聚力,并且提供一致的和众所周知的命令/事件格式;一个被事件或命令触发的服务应该能够被订阅服务消耗。在这个架构中,获得了很大的处理灵活性,服务的隔离性和自治性。
拿库存编目管理来说,它有一个单一的职责,仅仅负责更新编目,一旦它完成它执行的任务就不去关心别的触发的服务。因此附加服务可以增加消耗库存更新的事件,而无需修改清单管理服务,或任何管道编排器。
此外,它真的不关心(或者有任何的信息)是否早在后台订单通知之前已经死于可怕的死亡;库存已经更新,这对于库存管理而言这是一个出色的工作。对于库存管理服务故障的这种遗忘其实是一件好事;我们必须有一个处理后台通知器失败的方案,但是正如我之前说过的,可以说这不是库存管理本身的责任。
当设计一个基于系统的微服务时需要考虑的最重要的两件事是处理交易如添加,移除或修改服务不影响别的服务的操作或代码,以及适当的处理压力比如服务的失败。
但是在异步消息传递的世界上的一切并非完美无缺,目前仍有一些缺陷需要考虑:
设计/实现/配置困难
和同步编程模型比较起来异步编程模型通常更加复杂,这使得异步编程模型设计和实现起来更加复杂。这是因为有许多可能必须克服额外的问题,如消息排序,重复消息和消息幂等性。另外,消息代理的配置也需要一些考虑。例如,有相同服务的多个实例,那么消息被传递到两个服务或者仅仅只传递一个?两种方案都有使用案例。
异步消息的性质
未立即返回一个动作的结果的事实也可以增加的系统和用户界面设计的复杂性,在某些情况下它甚至不能清晰的划分逻辑的意义对于工作在异步方式的子系统。拿后台库存通知器为例,他和订阅管理的关系,如果没有没有关于订阅者应该被通知的消息它是不可能正常工作的,因此在这种情况下一个同REST调用才有意义。这和邮件发送任务是不同的,因此不需要直接发送邮件。
消息流的可视化
由于基于微服务的消息传递的分散和自治性,它很难获得一个完全的清晰的消息流图在系统内部。和管道的方法比起来,这使得调试起来更加困难,并且系统的业务逻辑也很难管理。
注意:基于时间的消息机制可以进一步扩展通过应用事件源和CQRS模式,但是这超出了本文的讨论范围。可以查看链接获取更多信息。
因此在设计微服务的时候哪种通讯方法是最好的?这与软件开发(和周期?)的大多数事情是一样的,取决于具体的需求!如果一个微服务实际需要同步响应,或者如果它自己需要接受一个同步响应,那么REST可能也就需要采用同步通讯方法。如果一个企业要求通过系统的消息流容易被监控和审计,或者考虑到能够修改和查看消息流从一个中心化的地方的优势,那么就可以考虑采用一个管道。但是异步基于系统的异步消息拥有松耦合和扩展的特性非常符合微服务的总体精神。通常情况下,尽管有一些显著的设计和实施的障碍,一个基于事件的消息传递方式将根据在微服务为基础的系统默认的通信机制作出决定时是一个不错的选择。
更多阅读
- Netflix OSS - The home of Eureka and Hystrix
- Consul
- Antifragile Software - By Russ Miles
- Event Sourcing - By Martin Fowler
- Building and Deploying Microservices with Event Sourcing, CQRS and Docker - By Chris Richardson
原文链接: is-rest-best-microservices (翻译:乔要周)
译者介绍
乔要周,目前就职于Allianz,sensior application administrator 崇尚开源精神,特别痴迷Docker。