微服务之集成(四)下

8. 实现基于事件的异步协作方式

前面讲了一些与请求/响应模式相关的技术。那么基于事件的异步通信呢?

8.1 技术选择

主要有两个部分需要考虑:微服务发布事件机制和消费者接收事件机制。

方法一:使用消息代理

传统上来说,像RabbitMQ这样的消息代理能够处理上述两个方面的问题。生产者(producer)使用API向代理发布事件,代理也可以向消费者提供订阅服务,并且在时间发生时通知消费者。

不过需要注意的是,消息代理仅仅是中间件世界中的一个小部分而是。有一个原则需要谨记:尽量让中间件保持简单,而把业务逻辑放在自己的服务中。

方法二:使用HTTP来传播事件

ATOM是一个符合REST规范的协议,可以通过它提供资源聚合(feed)的发布服务,而且有很多现成的客户端可以用来消费该聚合。

如果你已经有了一个好的,具有弹性的消息代理的话,就用它来处理事件的订阅和发布吧。

8.2 异步架构的复杂性

事件驱动的系统看起来耦合度非常低,而且伸缩性很好。但是这种编程风格也会带来一定的复杂性,这种复杂性并不仅仅包括对消息的发布订阅操作。

当我们的系统因为代码中bug,工作者崩溃时,我们需要有灾难故障转移(catastrophic failover)。

而且,我们最好设置一个作业最大重试次数。但是我们还需要有一种方式来查看甚至重发这些有问题的消息。所以最后实现了一个消息医院(或者叫死信队列),所有失败的消息都会被发送到这里。我们还可以创建一个界面来显示这些消息,如果需要的话还可以触发一个重试。

事件驱动架构和异步编程会带来一定的复杂性,所以通常需要谨慎的选用。你需要确保各个流程有很好的监控机制,并考虑使用关联ID,这种机制可以帮助你对跨进程的请求进行跟踪。

9.服务即状态

前面已经提到过,服务应该根据限界上下文进行划分。我们的客户端微服务应该拥有与这个上下文中行为相关的所有逻辑。

把关键领域的生命周期显示建模出来非常有用。我们不但可以在唯一的一个地方处理状态冲突(比如,尝试更新已经被移除的用户),而且可以在这些状态变化的基础上封装一些行为。

10.响应式扩展

响应式扩展(Reactive extensions, Rx)提供了一种机制,在此之上,你可以把多个调用结果组装起来并在此基础上执行操作。

很多Rx实现都在分布式系统中找到了归宿。因为调用的细节被屏蔽了,所以事情也更容易处理。我们可以简单的对下游服务调用的结果进行观察,而不需要关心它是阻塞的还是非阻塞的,唯一需要做的就是等待结果并作出响应。其漂亮之处在于,我们可以把多个不同的调用组合起来,这样就可以更容易对下游服务的并发调用做处理。

11.微服务世界中的DRY和代码重用的危险

DRY, Don’t Repeat Yourself。

使用DRY可以得到重用性比较好的代码。把重复代码抽取出来,然后就可以在多个地方进行调用。比如说可以创建一个随处可用的共享库。但是这个方法在微服务的架构中可能是危险的。

我们想要避免微服务和消费者之间的过度耦合,否则对微服务任何小的改动都会引起消费方的改动。而共享代码就有可能导致这种耦合。

跨服务共用代码很有可能会引入耦合。但是使用像日志库这样的公共代码就没什么问题,因为它们对外是不可见的。

推荐的做法:在微服务内部不要违反DRY,但在跨服务的情况下可以适当违反DRY。服务之间引入大量的耦合会比重复代码带来更糟糕的问题。

客户端库

很多团队坚持在最开始的时候为服务开发一个客户端库。原因在于,这样不仅能简化对服务的使用,还能避免不同消费者之间存在重复的与服务交互的代码。

但是如果开发服务端API和客户端API的是同一批人,那么服务端的逻辑很有可能泄露到客户端中。潜入客户端库的逻辑越多,内聚性就越差,然后你必须在修复一个服务端问题的同时,也需对多个客户端进行修改。

如果你想要使用客户端库,一定要保证其中只包含处理底层传输协议的代码,比如服务发现和故障处理等。千万不要把与目标服务相关的逻辑放到客户端库中。想清楚你是否要坚持使用客户端库,或者你是否允许别人使用不同的技术栈来对底层API进行调用。最后,确保由客户端来负责何时进行客户端库的升级,这样才能保证每个服务可以独立于其他服务进行发布。

12.按引用访问

如何传递领域实体的相关信息是一个值得讨论的话题。

很重要的一个想法,微服务应该包含核心领域实体(比如客户)全生命周期的相关操作。在这种设计下,如果想要做任何与客户相关的改动,就必须向客户服务发起请求。它遵守了一个原则,即客户服务应该是关于客户信息的唯一可靠来源。

想象这样一个场景,你从客户服务获取了一个客户资源,那么就能看到该资源在你发起请求那一刻的样子。但是有可能在你发送了请求之后,其他人对该资源进行了修改,所以你所持有的其实是该客户资源曾经的样子。你持有这个资源的时间越久,其内容失效的可能性就越高。当然,避免不必要的数据请求可以让系统更高效。

有时候使用本地副本没什么问题,但在其他场景下你需要知道该副本是否已经失效。所以,当你持有一个本地副本时,请确保同时持有一个指向原始资源的引用,这样在你需要的时候就可以对本地副本进行更新。

当然在使用引用时也需要做一些取舍。如果总是从客户服务去查询给定客户的相关信息,那么客户服务的负载就会过大。如果在获取资源的同时,可以得到资源的有效性时限(即该资源再什么时间之前是有效的)信息的话,就可以进行相应的缓存,从而减小服务的负载。

另一个问题是,有些服务可能不需要知道整个客户资源,所以坚持进行查询这种方式会引入潜在的耦合。

原则上讲,应该在不确定数据是否能保持有效的情况下,谨慎的进行处理。

13.版本管理

13.1 尽可能推迟破坏性修改

首先,减小破坏性修改影响的最好办法就是尽量不要做这样的修改。比如在很多的集成技术中,你可以通过选择正确的技术来做到这一点。比如数据库集成很容易引入破坏性的修改;而REST就好很多,因为内部的修改不太容易引起外部服务接口的变化。

另一个延迟破坏性修改的关键是鼓励客户端的正确行为,避免过早的将客户端和服务端紧密的绑定起来。

客户端尽可能灵活的消费服务响应这一点符合Postel法则(也叫做鲁棒性原则),该法则认为,系统中的每个模块都应该“宽进严出”,即对自己发送的东西要严格,对接收的东西则要宽容。

13.2 及早发现破坏性修改

这里强烈建议使用消费者驱动的契约来及早定位这些问题。

如果你支持多种不同的客户端库,那么最好针对最新的服务对所有的客户端运行测试。一旦发现,你可能会对某个消费者造成破坏,那么可以选择要么尽量避免破坏性修改,要么接受它,并跟维护这些服务的人员好好聊一聊。

13.3 使用语义化的版本管理

如果一个客户端能够仅仅通过查看服务的版本号,就知道它能否与之进行集成,那就太好了。

语义化版本管理就是一种能够支持这种方式的规格说明。

语义化版本管理的每一个版本号都遵循这样的格式:MAJOR.MINOR.PATCH。其中MAJOR的改变意味着其中包含向后不兼容的修改;MINOR的改变意味着有新功能的增加,但应该是向后兼容的;最后,PATCH的改变代表对已有功能的缺陷修复。

13.4 不同的接口共存

我们可以在同一个服务上使新接口和老接口同时存在。所以,在发布一个破坏性修改时,可以部署一个同时包含新老接口的版本。

这可以帮助我们尽快发布新版本的微服务,其中包含了新的接口,同时也给了消费者时间做迁移。一旦消费者不再访问老的接口,就可以删除掉该接口及相关的代码。

这其实就是一个扩展/收缩的实例,它允许我们对破坏性修改进行平滑的过度。首先扩张服务的能力,对新老两种服务都进行支持。然后等到老的消费者都采用了新的方式,再通过收缩API去掉就的功能。

13.5 同时使用多个版本的服务

另一种经常被提起的版本管理的方法是,同时运行不同版本的服务,然后把老用户路由到老版本的服务,而新用户可以看到新版本的服务。

14.用户界面

最重要的其实是,考虑该界面是否能够很好的支持服务之间的集成。毕竟用户界面是连接各个微服务的工具,而只有把各个微服务集成起来才能真正的为客户创造价值。

14.1 走向数字化

我们不应该对网页端和移动端区别对待,相反应该对数字化策略做全局考虑,即如何让客户更好的使用我们的服务。

通过把服务的功能进行不同的组合,可以为桌面应用程序、移动端设备、可穿戴设备的客户提供不同的体验。

14.2 约束

在用户与系统之间,需要考虑不同的交互形式中存在的一些约束。比如在桌面Web应用中,需要考虑与用户浏览器及屏幕解析度相关的约束。

14.3 API组合

假设我们的服务彼此之间已经通过XML或者JSON通信了,那么可以让用户界面直接与这个API进行交互。

图4-7:使用多个API来表示用户界面

这种方式有一些问题。首先,很难为不同的设备定制不同的响应。另一个问题是,谁来创建用户界面?维护服务的人往往不是服务的使用者。

使用API入口(gateway)可以很好的缓解这一问题,在这种模式下多个底层的调用会被聚合成为一个调用,当然它也有一定的局限性。

14.4 UI片段的组合

相比UI主动访问所有的API,然后再将状态同步到UI控件,另一种选择是让服务直接暴露出一部分UI,然后只需要简单的把这些片段组合在一起就可以创建出整体UI。

14.5 为前端服务的后端

对与前端交互比较频繁的界面及需要给不同设备提供不同内容的界面来说,一个常见的解决方案是,使用服务端的聚合接口或API入口。该入口可以对多个后端调用进行编排,并为不同的设备提供定制化的内容。

图4-9 使用单块入口来处理与UI之间的交互

这样做会得到一个聚合所有服务的巨大层。由于所有的东西都被放在了一起,也就失去了不同用户界面之间的隔离性,从而限制了独立于彼此进行发布的能力。

还有一种模式,保证一个后端服务只为一个应用或者用户界面服务。

这种模式有时也叫做BFF(Backends For Frontends,为前端服务的后端)。

API认证和授权层可以处在BFF和UI之间。

与任何一种聚合层类似,使用这种方法的风险在于包含不该包含的逻辑。业务逻辑应该处在服务中,而不应该泄露到这一层。这些BFF应该仅仅包含与实现某种特定的用户体验相关的逻辑。

14.6 一种混合方式

前面提到的那些选择各自都有其使用的范围。一个组织会选择基于片段组装的方式来构建网站,但对于移动端应用来说,BFF可能是更好的方式。关键是要保持底层服务能力的内聚性。

15. 小结

前面了解了很多不同的集成选择,也谈了什么样的选择能够最大程度的保证微服务之间的低耦合:

  • 无论如何,避免数据库集成
  • 理解REST和RPC之间的取舍,但总是使用REST作为请求/响应模式的起点
  • 相比编排,优先选择协同
  • 避免破坏性修改、理解Postel法则、使用容错性读取器
  • 将用户界面视为一个组合层

原文地址:https://www.cnblogs.com/Vincent-yuan/p/11495172.html

时间: 2024-10-01 06:12:53

微服务之集成(四)下的相关文章

微服务(入门四):identityServer的简单使用(客户端授权)

IdentityServer简介(摘自Identity官网) IdentityServer是将符合规范的OpenID Connect和OAuth 2.0端点添加到任意ASP.NET核心应用程序的中间件,通常,您构建(或重新使用)一个包含登录和注销页面的应用程序(可能还包括同意,具体取决于您的需要),IdentityServer中间件向其添加必要的协议头,以便客户端应用程序可以使用这些标准协议与之对话. 托管应用程序可以像您希望的那样复杂,但我们通常建议通过只包含与身份验证相关的UI来尽可能地保持

基于docker部署的微服务架构(四): 配置中心

原文:http://www.jianshu.com/p/b17d65934b58%20 前言 在微服务架构中,由于服务数量众多,如果使用传统的配置文件管理方式,配置文件分散在各个项目中,不易于集中管理和维护.在 spring cloud 中使用 config-server 集中管理配置文件,可以使用 git.svn.本地资源目录 来管理配置文件,在集成了 spring cloud bus 之后还可以通过一条 post 请求,让所有连接到消息总线的服务,重新从config-server 拉取配置文

Spring Cloud构建微服务架构(四)分布式配置中心

Spring Cloud Config为服务端和客户端提供了分布式系统的外部化配置支持.配置服务器为各应用的所有环境提供了一个中心化的外部配置.它实现了对服务端和客户端对Spring Environment和PropertySource抽象的映射,所以它除了适用于Spring构建的应用程序,也可以在任何其他语言运行的应用程序中使用.作为一个应用可以通过部署管道来进行测试或者投入生产,我们可以分别为这些环境创建配置,并且在需要迁移环境的时候获取对应环境的配置来运行. 配置服务器默认采用git来存储

京东、宅急送的微服务实践分享(下)| 架构师小组交流会

架构师小组交流会是由国内知名公司技术专家参与的技术交流会,每期选择一个时下最热门的技术话题进行实践经验分享. 第一期:来自沪江.滴滴.蘑菇街.扇贝架构师的 Docker 实践分享 第二期:来自滴滴.微博.唯品会.魅族.点评关于高可用架构的实践分享 第三期:京东.宅急送的微服务实践分享(上)第三期小组交流会邀请到了国内电商领头羊京东.宅急送技术负责人,在上篇京东.宅急送的微服务实践分享(上)中他们介绍了各自产品的微服务实践,本次他们就 API 网关的设计.服务的 Docker 化.服务测试.持续集

.Net微服务实践(四)[网关]:Ocelot限流熔断、缓存以及负载均衡

目录 限流 熔断 缓存 Header转化 HTTP方法转换 负载均衡 注入/重写中间件 后台管理 最后 在上篇.Net微服务实践(三)[网关]:Ocelot配置路由和请求聚合中我们介绍了Ocelot的配置,主要特性路由以及服务聚合.接下来,我们会介绍Ocelot的限流.熔断.缓存以及负载均衡. 限流 我们先来看限流的配置 Reroute节点中的配置如下: { "DownstreamPathTemplate": "/api/orders", "Downstr

基于Spring Boot和Spring Cloud实现微服务架构学习(四)

Spring Cloud介绍 Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理.服务发现.断路器.智能路由.微代理.控制总线.全局锁.决策竞选.分布式会话和集群状态管理等操作提供了一种简单的开发方式. Spring Cloud与Dubbo对比 提到Dubbo,我想顺便提下ESB,目前央视新华社也在用ESB来做任务编排,这里先比较下Dubbo和ESB: ESB(企业数据总线),一般采用集中式转发请求,适合大量异构系统集成,侧重任务

Spring Boot微服务如何集成fescar解决分布式事务?

什么是fescar? 关于fescar的详细介绍,请参阅fescar wiki. 传统的2PC提交协议,会持有一个全局性的锁,所有局部事务预提交成功后一起提交,或有一个局部事务预提交失败后一起回滚,最后释放全局锁.锁持有的时间较长,会对并发造成较大的影响,死锁的风险也较高. fescar的创新之处在于,每个局部事务执行完立即提交,释放本地锁:它会去解析你代码中的sql,从数据库中获得事务提交前的事务资源即数据,存放到undo_log中,全局事务协调器在回滚的时候直接使用undo_log中的数据覆

浅谈微服务架构(四)——容错模式2

3. 限流模式 服务的容量和性能是有限的,在第3章中会介绍如何在架构设计过程中评估服务的最大性能和容量,然而,即使我们在设计阶段考虑到了性能压力的问题,并从设计和部署上解决了这些问题,但是业务量是随着时间的推移而增长的,突然上量对于一个飞速发展的平台来说是很常见的事情. 针对服务突然上量,我们必须有限流机制,限流机制一般会控制访问的并发量,例如每秒允许处理的并发用户数及查询量.请求量等. 有如下几种主流的方法实现限流. 1)计数器 通过原子变量计算单位时间内的访问次数,如果超出某个阈值,则拒绝后

微服务Kong(四)——添加插件

在本节中,您将学习到,如何配置使用KONG的插件来管理您的API.KONG的核心原则之一就是通过插件来实现API的扩展.插件可以使您更为简单的扩展和管理您的API. 在以下的步骤中,您将通过配置key-auth插件为您的API添加一个认证的功能.在添加此插件之前,您的所有API都被将代理到上游头.添加并配置此插件后,只有具有正确API密钥的请求会被代理 - 所有其他请求将被KONG拒绝,从而保护您的上游服务免受未经授权的使用,从而实现权限认证功能. 1. 为您的API配置 key-auth 插件