微服务设计-读书笔记4

集成

        集成是微服务相关技术中最重要的一个。集成做的好的话,微服务可以保持自治性,也可以独立修改和发布。集成做不好的话会带来灾难。
1、怎样寻找理想的集成技术
(1)、避免破坏性修改
        破坏性修改是指,对某个服务做一些修改会导致该服务的消费方也随之发生改变。在选用集成技术时要尽量避免这种情况的发生。
(2)、保证API的技术无关性
        微服务通信方式的技术无关性时指,通信的方式不应该限制微服务的具体实现技术。
(3)、使服务易于消费方的使用
        一方面不限制消费方的技术实现,另一方面提供客户端库简化消费方的使用。
(4)、隐藏服务内部的实现细节
        如果服务的内部实现细节与消费方绑定,那么当服务内部的实现改变时,消费方就需要跟着做出修改。这样增加了服务方与消费方的耦合度。因此,在选择通信方式时,那些暴露内部实现细节的技术都不应该被采用。

2、共享数据库集成形式怎么样?
        数据库集成,看起来非常简单,集成速度很快,不同服务都可以随时修改数据库,服务之间仅仅互相通知事件就可以了。
        但是,数据库集成方式,使得服务方对消费方暴露了内部的实现细节,因为所有服务都可以访问存储在数据库中的数据结构。导致服务方和消费方的紧耦合,增加业务逻辑的修改的复杂性;另外,数据库可以看做是一个巨大的共享API, 数据库集成方式也违背了“保证API技术无关性”的原则,因为当使用关系数据库集成后,可能发现消费方更适合非关系型数据库,这种集成方式导致消费方不能选择更合适的技术;还有,一部分业务行为应该是服务方负责,由于是数据库集成的原因,导致消费方也可以直接修改这部分行为,这就导致微服务“内聚性”的丧失。
        因此,微服务架构体系中,不建议数据库集成。
3、微服务之间如何协作?同步还是异步?
        同步通信是调用方发起一个远程调用后,调用方会阻塞自己并等待整个操作的完成;异步通信是调用方不需要等待远程调用的完成就可以返回。同步通信可以当场知道事情的结果,但对于长延时的调用来说,会由于阻塞而降低调用者的运行速度;异步通信则需要等待结果的到来,但对于长延时的调用来说,可以避免调用者的阻塞等待,提高了效率。
        这两种通信模式通常代表不同的协作风格:请求/响应 或者 基于事件。对于请求/响应风格,客户端发起一个请求,然后等待响应。(当然可以是异步响应);对于基于事件风格,客户端只需要分布一个事件就可以,然后期待其他协作者收到事件并自行处理。
        两种协作风格各有特点,如何选择取决于这种风格的协作能否很好地解决复杂问题。
4、微服务之间如何协作?编排还是协同?
        在处理跨服务业务流程问题时,有两种架构风格可以采用:编排(orchestration)、协同(choreography)。
        使用编排时,我们会依赖于某个中心大脑来指导并驱动整个流程;使用协同时,我们仅仅会告知系统中各个部分各自的职责,然后让该部分自己处理做的细节。
        例如:系统中创建客户,需要完成以下事情:
(1)在客户的积分账户中创建一条记录;
(2)通过邮政系统发送一个欢迎礼包;
(3)向客户发送欢迎电子邮件。
        通过编排架构风格来实现时如下图所示:
        编排方式,让客户服务作为中心大脑,在创建时它会跟积分账户、电子邮件服务及邮政服务通过请求/响应的方式进行通信,客户服务本身可以对当前进行到哪一步进行跟踪,如果是同步的请求/响应模式,甚至能知道每一步是否都成功了。
        编排方式的优点是能够监控到业务流程的每一步,缺点是客户服务作为中心控制点承担了太多职责,它会成为网络结构的中心枢纽及很多逻辑的起点。通常导致其他服务沦为基于CRUD的服务。
        通过协同架构风格来实现,如下图所示:
        协同方式,客户服务仅仅以异步方式发布一个触发事件“客户创建”,电子邮件服务、邮政服务及积分账户可以简单地订阅这些事件并且做相应处理。如果有额外的服务需要关心“客户创建”事件,它可以简单地订阅该事件即可。
        协同方式的优点是能够显著地消除耦合,缺点是没有明显的业务流程视图,需要额外的工作来监控流程,以保证其正确地进行。
        通常,协同方式可以降低系统的耦合度,可以更加灵活地对现有系统进行修改,虽然需要额外的工作来对业务流程做跨服务监控,但相对于编排方式带来的不稳定和高昂的修改代价,更倾向于选择协同方式来实现。
        同步方式比较简单,异步方式更有利于协同方案的实施,如果想要请求/响应风格的语义,又想避免耗时业务的困境,可以采用异步请求加回调的方式。针对请求/响应方式,可以考虑两种技术:RPC(Remote Procedure Call, 远程过程调用)、REST(REpresentational State Transfer, 表述性状态转移)。
5、远程过程调用怎么样?
        远程过程调用(RPC)允许我们就像调用本地方法一样进行远程调用。RPC的种类很多,比如SOAP、Thrift、protocol buffers、Java RMI等。有很多技术本质上是二进制的,比如Java RMI、Thrift、protocol buffers等,有些是基于XML的,比如SOAP。
         很多RPC的实现会帮助你生成服务器和客户端的桩代码,从而让你快速开始编码,因此易于使用时RPC的一个优点;然而有一些RPC实现确实存在一些问题:(1)技术的耦合,如Java RMI,与特定的平台紧密绑定,这对服务端和客户端的技术选型造成了一定的限制,从某种程度上讲,这种技术上的耦合也是暴露内部实现细节的一种方式;   (2)、对远程调用的过分隐藏,RPC的核心想法是隐藏远程调用的复杂性,但是很多RPC的实现隐藏过了头,没有考虑网络本身并不可靠,带来的问题;(3)、脆弱性,任何一个使用二进制桩生成机制的RPC都会面临客户端和服务器的部署无法分离的挑战(很容易导致lock-step发布),因为接口的改动,常常导致所有客户端重新生成桩代码,无论该客户端是否真的需要这种改变。
        相比使用数据库集成方式,RPC显然是一个巨大的进步,尽管有些RPC存在缺点,导致一些实现问题,但一些更先进的RPC机制会通过避免对客户端和服务端的lock-step发布来消除上提到的一些问题,比如protocol buffers 或者Thrift。如果决定选用RPC集成,建议不要对远程调用过度抽象,以至于网络因素完全被隐藏起来;确保能够独立升级服务器接口而不用强迫客户端升级;在客户端中一定不要隐藏做网络调用的事实。  
6、REST集成怎么样?
        REST是RPC的一种替代方案,REST本身并没有提到底层应该使用什么协议,尽管最常用的是HTTP。
        REST引入资源的概念,一个资源的对外显示方式和对内显示方式可以没有耦合,而HTTP中,HTTP的动词(如GET、POST和PUT)就能够很好地跟资源一起使用。GET使用幂等的方式获取资源、POST创建一个新资源。这意味着我们可以避免具体的接口如createCustomer及editCustomer方法,可以简单地POST一个customer的表示到服务端,然后服务端就可以创建一个新的资源,可以使用GET请求来获取一个Customer的表示,从概念上讲,对于一个Customer资源,访问接口只有一个,但是可以通过HTTP协议的不同动词对其进行不同的操作。
        REST引入了一个原则:超媒体作为程序状态的引擎(HATEOAS原则)。超媒体的概念是一块内容,该内容包含了指向其他内容的链接,HOTEOAS背后的想法是,客户端应该与服务器通过那些指向其他资源的链接来进行交互,而这些交互可能造成状态转移。这样做实现客户端和服务端的松耦合,资源的底层细节被很好地隐藏起来了。我们可以随意地改变链接的展现形式,只要客户端仍能找到它就可以了。
        REST标准文本形式的响应,可以是JSON、XML、HTML等等。JSON无论从形式上还是使用方式上都更简单,相比XML,JSON更加紧凑,但JSON需要使用HAL(超文本应用语言)来进行超媒体控制。XML在工具上有很好地支持,使用链接来进行超媒体控制。
        基于HTTP的REST的缺点
        从易用性角度来讲,基于HTTP的REST无法帮助我们生成客户端的桩代码;性能上也存在问题,虽然HTTP可用于大流量的通信场景,但对于低延时通信来说并不是最好的选择。
7、基于事件的异步协作方式
(1)、技术选择
        传统方式,使用RabbitMQ这样的消息代理,生产者使用API向代理发布事件,代理也可以向消费者提供订阅服务,并在事件发生时通知消费者。这种代理甚至可以跟踪消息的状态,标记哪些消息是被消费过的。使用消息中间件开发的系统具有较好的可伸缩性和弹性,但代价是增加开发流程的复杂度,因为你需要一个额外的系统(即消息代理)才能开发及测试服务,也需要一些额外的机器和专业知识来保持这些基础设施的正常运行。
        注意,使用传统消息代理方式,要记住尽量让中间件保持简单,而把业务逻辑放在自己的服务中。
        另一种方式,使用HTTP来传播事件。ATOM是一个符合REST规范的协议,可以通过它提供资源聚合(feed)的发布服务,而且很多现成的客户端库都可以用来消费该聚合。但是需要注意HTTP并不擅长处理低延迟的场景,而且使用ATOM的话,用户还需要自己追踪消息是否送达及管理轮询等工作。
(2)、异步架构的复杂性
        事件驱动架构和异步编程会带来一定的复杂性,复杂性不仅仅包括对消息的发布和订阅,还有异步流程的控制,此外还可能导致灾难性故障转移等问题。所以,要谨慎选择这种技术。
8、服务即状态
        服务即状态是一个很强大的原则,当消费者想要对客户服务做修改时,它会向客户服务发送一个合适的请求。客户服务根据自己的逻辑决定是否接受该请求。客户服务控制了所有与客户生命周期相关的事件。
9、响应式扩展
        响应式扩展(Reactive extensions, Rx)提供了一种机制,在此之上,可以把多个调用的结果组装起来并在此基础上执行操作。调用本身可以是阻塞或者非阻塞的。当需要做一些基于多个服务调用的操作时,可以尝试一下适合你的技术栈的响应式扩展。例如RxJava。
10、微服务中DRY和代码重用的危险
        DRY(Don't Repeat Yourself)。不仅仅是指避免重复代码,其更精确的含义是避免系统行为和知识的重复。单体系统中DRY是一个很好地原则。
        微服务系统中,要避免微服务和消费者之间的过度耦合,而共享代码可能导致这种耦合。
        所以,在微服务内部不要违反DRY,但在跨服务的情况下可以适当违反DRY。服务之间引入大量的耦合会比重复代码带来更糟糕的问题。
11、客户端库的使用
        为服务开发一个客户端库,不仅能简化对服务的使用,还能避免不同消费者之间存在重复的与服务交互的代码。这么做的问题时,服务端的逻辑可能泄露到客户端中,潜入客户端库的逻辑越多,内聚性就越差,然后你必须在修复一个服务端的同时,也需要对多个客户端进行修改。此外也限制了客户端的技术选择。
        正确地使用客户端库的方式是,一定要保证其中只包含处理底层传输协议的代码,比如服务发现和故障处理等,千万不要把与目标服务相关的逻辑放到客户端库中。(因为我们的目标是:确保每个服务都能够独立于其他服务进行发布)
12、按引用访问
        微服务应该包含核心领域实体全生命周期的相关操作,即客户服务应该是关于客户信息的唯一来源,消费者需要使用客户信息,就应该按引用访问。这里存在一个问题,消费者按引用从客户服务获取一个客户资源,可能在客户服务发出请求之后,客户资源被其他人修改了,此消费者持有的客户资源可能已经失效。合理的处理方式是:仅仅给消费者发送表示客户资源的URI,当消费者需要使用该资源时再向客户服务查询。
        当然在使用引用时也需要做一些取舍,频繁查询给定客户信息会使客户服务的负载过大。可以在获取资源时,得到资源的有效性时限信息,然后消费者自己缓存,从而减少服务的负载。
13、版本管理
        * 可以使用容错性读取器等方式,使得客户端尽可能灵活地消费服务响应(Postel法则,鲁棒性原则,该法则认为。系统中每个模块都应该“宽进严出”,即对自己发送的东西要严格,对接收的东西要宽容)。
        * 建议使用消费者契约来及早定位对消费者的破坏性修改。
        * 使用语义化的版本管理,MAJOR.MINOR.PATCH。
        * 尝试使不同的接口共存,来避免对接口的修改。如下图:某个接口在不同版本同时存在,允许消费者进行逐步迁移。
        这其实是一个扩展/收缩模式的实例,它允许我们对破坏性修改进行平滑的过度。首先扩张服务的能力,对新老两种方式都进行支持。然后等到老的消费者都采用了新的方式,在通过收缩API去掉旧的功能。
        如果采用这种不同版本接口共存的方式,需要一种方法来对不同的请求进行路由。
        * 同时使用多个版本的服务,这是一种版本管理方法,同时运行不同版本的服务,然后把老用户路由到老版本的服务,而新用户可以路由到新版本的服务,短时间同时使用两个版本的服务是合理的,升级消费者到新版本的时间越长,就越应该考虑在同一个微服务中暴露两套API的做法。
14、用户界面
        在Web时代,我们应该考虑让UI变得比较薄,而把更多的逻辑放在服务端;由于很难预测用户会怎样使用我们的API,所以很多公司会倾向于把API设计的比较细粒度化,通过把服务的功能进行不同的组合,可以为桌面应用程序、移动端设备、可穿戴设备的客户提供不同的体验;同时,在用户和系统之间,还需要考虑不同的交互形式中存在的一些约束,这样才能给用户更好的体验。
(1)、API组合
        假设服务彼此之间已经通过XML或者JSON通信,那么可以让用户界面直接与这个API进行交互,在这种模式下,UI主动访问API,然后再将状态同步到UI控件,如下图:
        这种方式有一定的问题,首先很难为不同的设备定制不同的响应,其次界面的维护涉及到多个服务(可能是多个团队)。这种通信模式也很繁琐,与服务之间过多的交互对移动设备来说比较吃力。使用API网关(gateway)可以很好地缓解这个问题。
(2)UI片段的组合
        相比API组合方式中UI主动访问所有API,再将状态同步到UI控件,UI片段组合方式则是让服务直接暴露出一部分UI,然后只需要简单地把这些片段组合在一起就可以创建出整体UI,如下图所示:
        这种方式有一个工作得很好地变种,即将一系列粗粒度的UI部分组装起来,不再创建小部件。这些粗粒度的片段由服务器端程序提供,而这些程序又会去调用相关API。当片段和团队所有权匹配得比较好时,这个模型可以很好地进行工作。
        这种设计也有一定的问题,UI片段的方式可能导致用户体验不一致,用户想要一个无缝的体验,而不是在应用的不同部分得到不同感受及设计语言。这些问题可以通过一些技术避免(比如,活样式指导 living style guides,即将HTML组件、CSS及图片等资源进行共享,从而使其具有一定程度的一致性);此外,原生应用(APP)和胖客户无法消费服务端提供的UI组件;还有的是有些服务功能不能够嵌入到小部件或页面中(交互越多就越难把一个服务做成控件的形式)。
(3)、为前端服务的后端
        对那些与后端交互频繁或者需要给不同设备提供不同内容的界面来说, 如果使用单块入口来处理与UI交互,该入口可能会变得太厚,包含逻辑太多,就会难以维护;如果使用服务端的聚合接口或API入口,可以对多个后端调用进行编排,并为不同的设备提供定制化的内容,可能很好地避免单块入口的弊端。
        如上图这样的单块入口会得到一个聚合所有服务的巨大的层。由于所有的东西都被放在一起,也就失去了不同用户界面之间的隔离性,从而限制了独立于彼此进行发布的能力。
        上图这种模式也叫做BFF(Backends For Frontends,为前端服务的后端)。它允许团队在专注于给定的UI的同时,也会处理与之相关的服务端组件。
        这种方法的风险是可能会包含不该包含的逻辑,业务逻辑应该放在服务中,而不应该泄露给BFF层。这些BFF层应该仅仅包含与实现某个特定的用户体验相关的逻辑。
15、与第三方软件集成
        使用一些商业的第三方软件是合情合理的。(如果某些软件是自己公司的战略性资产,就自己构建,如果不是就去购买)。缺乏控制是使用第三方软件集成的一大挑战,要尽量把集成和定制化的工作放在自己能够控制的部分。
(1)、在自己可控的平台进行定制化
        任何定制化都只在自己可控的平台上进行,并限制工具的消费者的数量。
例子1:在第三方的CMS软件之上包装一层,为Web前端服务,这种方式能最大程度地限制CMS的使用范围,并把定制化的工作移动到自己的技术栈中。
例子2:使用外观服务来隐藏庞大的第三方CRM软件,首先识别出需要自己控制的核心领域概念,比如“项目”,如图“项目(外观)”服务将项目已RESTful资源的形式暴露出来,外部系统可以把它们的集成点迁移到这个新的、易用的服务上来,而这个项目服务仅仅是隐藏了底层的集成细节而已。
注:绞杀者模式,绞杀者可以捕获并拦截对老系统的调用,然后决定把这些调用路由到现存的遗留代码中还是导向新写的代码中。这种方式可以帮助我们逐步对老系统进行替换,从而避免影响过大的重写。






猜你喜欢

转载自blog.csdn.net/xiaoxiaoyusheng2012/article/details/80784827