集成是微服务相关技术中最重要的一个。做得好的话,微服务可以保持自治性,也可以独立的修改和发布它们。做的不好会带来灾难。
1、寻找理想的集成技术 :微服务之间通信方式有很多种,但是我们要考虑的是,我们到底希望从这些技术中得到什么。
避免破坏性修改 :例如一个微服务在一个响应中添加了一个字段,那么已有的消费方不应该受到影响。
保证API的技术无关性 :不要选择对微服务的具体实现技术有限制的集成方式。
使你的服务易于消费方使用 : 消费方应该能很容易的使用我们的服务。理想情况下,消费方应该可以使用任何技术来实现,另一方面来说,提供一个客户端库也可以简化消费方的使用。虽然客户端库对于消费方来说方便,但是会造成耦合的增加。
隐藏内部实现细节 : 服务的内部实现细节不应该和消费方绑定在一起。
2、为用户创建接口 :根据业务,指定不同服务的处理方式。
3、共享数据库:数据库集成就是其他服务要从一个服务获取信息,可以直接访问数据库。这种方式比较快,也比较简单,但是很难做到好的微服务的核心,高内聚和低耦合。很难做到无破坏性的修改。
4、同步与异步:同步可以知道事情到底成功与否,异步通信对于运行时间比较长的任务来说很有用。不同的通信模式有着各自的协作风格:请求/响应 或 基于事件。 请求/响应可以很好的匹配同步通信模式,异步需要发起请求,然后注册一个回调,当服务端操作结束后,会调用该回调。如果是基于事件的协作方式,客户端不是发起请求,而是发布一个事件,然后期待其他的协作者接收该消息,并且知道该怎么做。基于事件的系统天生是异步的,将业务逻辑平均的分不在不同的协作者中。这种方式耦合性低。这种风格能否很好的解决复杂问题,是一个协作风格选择很重要的因素。
5、编排与协同:在开始对越来越复杂的逻辑进行建模时,我们需要处理跨服务业务流程的问题,而使用微服务时这个问题会来的更快。编排和协同时两种架构风格,以下分开介绍。
编排:使用编排,我们会依赖于某个中心大脑来指导并驱动整个流程,就像管弦乐队中的指挥一样。编排的缺点是中心控制点承担了太多职责,它会称为网状结构的中心枢纽及很多逻辑的起点。
协同:使用协同,我们仅仅会告知系统中各个部分各自的职责,而把具体怎么做的细节留给它们自己,就想芭蕾舞中的每个舞者都有自己的方式,同时也会响应周围其他人。这种方式能很显著的消除耦合,但是缺点是看不到明显的业务流程视图。这样的话,需要做一些额外的工作来监控流程,例如根据业务流程匹配一套监控系统。
这里需要考虑好多因素。同步调用比较简单,而且容易指导整个流程的工作是否正常,如果想要请求/响应风格的语义,又想避免其在耗时业务上的困境,可以采用异步请求加回掉的方式。另一方面,使用异步方式有利于协同方案的实施,可以减少服务间的耦合。
针对请求/响应方式,可以考虑两种技术RPC远程过程调用,REST表述性状态转移。
6、远程过程调用:远程过程调用允许进行一个本地调用,但事实上结果是由某个远程服务器产生的。
技术的耦合:有一些RPC机制,如JAVA RMI,与特定的平台紧密绑定,这对于服务端和客户端的技术选型造成了一定限制。Thrift和 protocol buffers对于不同语言的支持很好,从而一定程度上减小了限制的影响。
本地调用和远程调用并不相同:RPC的核心想法是隐藏远程调用的复杂性。不能简单的将本地API改成跨服务的远程API,要使用不同的思路来设计远程和本地API。
脆弱性:一些很流行的RPC由一些脆弱性,例如修改、删除字段会引起服务端和客户端共同的变化,就会比较脆弱。有一个办法解决这类问题,即使用一个字典类型作为参数进行传递。但是这样就失去了自动生成桩的好处。
RPC很糟糕吗?更现代的RPC机制,比如Thrift和 protocol buffers 会通过避免对客户端和服务端的lock-step发布来消除上面提到的一些问题。如果要选用RPC这种方式,要注意一些问题:不要对远程调用过度抽象,以至于网络因素完全被隐藏起来;确保你可以独立地升级服务端地接口而不用强迫客户端升级,所以在编写客户端代码时要注意这方面地平衡;在客户端中一定不要隐藏我们是做网络调用这个事实;在RPC的方式中经常会在客户端使用库,但是这些库如果在结构上组织得不够好,也可能会带来一些问题。RPC是请求/响应协作方式中的一种,相比使用数据库做集成的方式,RPC有巨大的进步。
7、REST:REST是受Web启发而产生的一种架构风格。RPC的一种替代方案。其中最重要的一点是资源的概念。资源的对外显示方式和内部存储方式之间没有什么耦合。REST风格很多,可以看看Richardson的成熟度模型,有对REST不同风格的比较。
REST和HTTP:HTTP本身提供了很多功能,这些功能对于实现REST风格非常有用。
超媒体作为程序状态的引擎:HATEOAS 超媒体作为程序状态的引擎。客户端应该与服务端通过那些指向其他资源的链接进行交互,而这些交互有可能造成状态转移,客户端根据链接导航到它想要的东西。让客户端自行遍历和发现API这种形式,自行发现和解耦的好处非常大,但是会增加客户端和服务端的通信次数,并且投入也比较多。
json、xml还是其他:json无论从形式上还是从使用方法上来说都更简单。xml可使用链接来进行超媒体控制。
基于HTTP的REST的缺点:易用性上来讲,HTTP的REST无法生成客户端桩代码。有些Web框架无法很好的支持所有的HTTP动词。性能上,在要求低延迟的场景下,每个HTTP请求的封装开销可能是个问题。对于低延迟通信来说,要考虑TCP和UDP协议的技术,WebSockets。HTTP的REST虽然有缺点,但是在选择服务之间的交互方式上,还是比较合理的默认选择。
8、实现基于时间的异步协作方式
技术选择:主要有两个部分需要考虑:微服务发布事件机制和消费者接收事件机制。
传统上来说,消息代理RabbitMQ这种能够处理上述的两个方面的问题。生产者使用API向代理发布事件,代理页可以向消费者提供订阅服务,并且在事件发生时通知消费者。这种系统通常具有较号的可伸缩性和弹性,但是会增加开发流程的复杂度。一旦做好了,它可以实现松耦合、事件驱动架构。尽量让中间件保持简单,而把业务逻辑放在自己的服务中。
另一种方法是使用HTTP来传播事件。ATOM是一个符合REST规范的协议,可以通过它提供资源聚合(feed)的发布服务,而且有很多现成的客户端库可以用来消费该聚合。
有一种场景需要避免,即多个工作者处理了同一条消息,从而造成浪费。如果使用消息代理,一个标准的队列就可以很好的处理这种场景。而使用ATOM的话,就需要自己在所有的工作者中间维护一个共享的状态来减少这种情况的发生。
异步架构的复杂性:事件驱动架构和异步编程会带来一定的复杂性,所以要很谨慎地选用这种技术。要确保各个流程有很好地监控机制,并考虑使用关联ID,这种机制可以帮助你对跨进程地请求进行追踪。
9、服务即状态机:服务应该根据限界上下文进行划分。当消费者想要对客户做修改时,它会向客户服务发送一个合适地请求。客户服务根据自己的逻辑决定是否接受该请求。客户服务控制了所有与客户生命周期相关的事件。如果出现了在客户服务之外与其进行相关的修改的情况,那么就失去了内聚性。
10、响应式扩展:响应式扩展Rx提供了一种机制,在此之上,可以把多个调用的结果组装起来并在此基础上执行操作。Rx改变了传统的流程,可以简单的对操作的结果进行观察,结果会根据相关数据的改变自动更新。
11、微服务世界中的DRY和代码重用的危险:DRY为避免重复代码,避免系统行为和知识的重复。在微服务内部不要违反DRY,在跨服务的情况下可以适当违反DRY。服务之间引入大量的耦合会比重复代码带来更糟糕的问题。
12、按引用访问:微服务应该包含核心领域实体(比如客户)全生命周期的相关操作。例如:客户服务应该是关于客户信息的唯一可靠来源。
13、版本管理
系统中的每个模块都应该“宽进严出”,即对自己发送的东西要严格,对接受的东西要宽容。
及早发现破坏性修改:及早意识到破坏性修改,一旦意识到,要尽量避免。
使用语义化的版本管理:语义化版本管理是一种仅通过查看服务的版本号,就知道它是否能集成的规格说明。这种格式:MAJOR.MINOR.PATCH。MAJOR的改变意味着其中包含向后不兼容的修改;MINOR的改变意味着有新功能增加,但应该是向后兼容的;PATCH的改变代表对已有功能的缺陷修复。例如:1.2.0
不同的接口共存:当避免不了破坏性修改的时候,可以考虑新接口和老接口共存的方式。对于HTTP的系统来说,可以在请求中添加版本信息。
同时使用多个版本的服务:这种方式可以支持老版本,也可以支持新版本。但是长时间这样,会比较复杂。
14、用户界面
走向数字化:对数字化策略做全局考虑,即如何让客户更好地使用我们的服务。通过把服务的功能进行不同的组合,可以为桌面应用程序、移动端设备、可穿戴设备的客户提供不同的体验。
约束:尽管核心服务可能是一样的,不同的应用场景,有着不同的约束。例如Web应用需要考虑用户浏览器及屏幕分辨率的约束;移动端需要考虑移动应用与服务器之间不同的通信方式等等。
API组合:UI调用多个API的方式的问题和解决思路。很难为不同的设备定制不同的响应,这个问题的解决方案是允许客户指定它想要哪些字段,但这需要每个服务都支持这种交互方式。
UI片段的组合:让服务直接暴露出一部分UI,然后只需要简单地把这些片段组合在一起就可以创建出整体UI。这种方式的优势是修改服务团队的同时可以维护这些UI片段。也可以保证用户体验的一致性。
为前端服务的后端:保证一个后端只为一个应用或者用户界面服务。这种模式叫做BFF。它允许团队在专注于给定UI的同事,也会处理与之相关的服务端组件。这种方法的风险在于包含不该包含的逻辑。
15、与第三方软件集成:在一些情境下,我们需要与第三方软件集成。但是也会受到限制。
缺乏控制:我们缺乏对第三方软件的控制,尽量把集成和定制化的工作放在自己能够控制的部分。
在自己可控的平台进行定制化:任何定制化都只在自己可控的平台上进行,并限制工具的消费者的数量。
当遇到移除或绕过某些服务时,可能会捕获并重定向这些原始调用,但是这样很复杂,可以考虑用一个代理来解决这种问题。
16、小结
什么样的选择能够最大程度的保证微服务之间的低耦合?
因篇幅问题不能全部显示,请点此查看更多更全内容