聊聊微服务之间的几种调用形式

大家好,我是不才陈某~

在微服务架构中,须要调用很多服务才干成功一项性能。服务之间如何相互调用就变成微服务架构中的一个关键疑问。

服务调用有两种形式,一种是RPC形式,另一种是事情驱动(Event-driven)形式,也就是发信息形式。

信息形式是松耦合形式,比紧耦合的RPC形式要优越,但RPC形式假设用在适宜的场景也有它的一席之地。

我们总在谈耦合,那么耦合究竟象征着什么呢?

耦合的种类

期间耦合:客户端和服务端必定同时上线才干上班。发信息时,接受信息队列必定运转,但后盾处置程序临时不上班也不影响。

容量耦合:客户端和服务端的处置容量必定婚配。发信息时,假设后盾处置才干无余也不要紧,信息队列会起到缓冲的作用。

接口耦合:RPC调用有函数标签,而信息队列只是一个信息。例如买了商品之后要调用发货服务,假设是发信息,那么就只需发送一个商品被买信息。

发送形式耦合:RPC是点对点形式,须要知道对方是谁,它的好处是能够传回前往值。信息既可以点对点,也可以用广播的形式,这样缩小了耦合,但也使前往值比拟艰巨。

上方我们来逐个剖析这些耦合的影响。第一,期间耦合,关于少数运行来讲,你宿愿能马上失掉回答,因此即使经常使用信息队列,后盾也须要不时上班。关注群众号:码猿技术专栏,回复关键词:“1111” 失掉阿里外部Java性能调优手册

第二,容量耦合,假设你对回复有期间要求,那么信息队列的缓冲性能作用不大,由于你宿愿及时照应。

真正须要的是智能伸缩(Auto-scaling),它能智能调整服务端处置才干去婚配恳求数量。第三和第四,接口耦合和发送形式耦合,这两个确实是RPC形式的软肋。

事情驱动(Event-Driven)形式

Martin Fowler把事情驱动分红四种形式(What do you mean by “Event-Driven”),简化之后实质上只要两种形式。一种就是我们相熟的的事情通知(Event Notification),另一种是事情溯源(Event Sourcing)。

事情通知就是微服务之间不间接调用,而是经过发信息来启动协作。事情溯源有点像记账,它把一切的事情都记载上去,作为终身存储层,再在它的基础之上构建运行程序。关注群众号:码猿技术专栏,回复关键词:“1111” 失掉阿里外部Java性能调优手册

实践上从运行的角度来讲,它们并不应该分属一类,它们的用途完全不同。事情通知是微服务的调用(或集成)形式,应该和RPC分在一同。事情溯源是一种存储数据的形式,应该和数据库分在一同。

事情通知(Event Notification)形式

让我们用详细的例子来看一下。在上方的例子中,有三个微服务,“Order Service”, “Customer Service” 和“Product Service”。

先说读数据,假定要创立一个“Order”,在这个环节中须要读取“Customer”的数据和“Product”数据。

假设用事情通知的形式就只能在“Order Service”本地也创立只读“Customer”和“Product”表,并把数据用信息的形式同步过去。

再说写数据,假设在创立一个“Order”时须要创立一个新的“Customer”或要修正“Customer”的信息,那么可以在界面上跳转到用户创立页面,而后在“Customer Service”创立用户之后再发”用户已创立“的信息,“Order Service”接到信息,更新本地“Customer”表。

这并不是一个很好的经常使用事情驱动的例子,由于事情驱动的优势就是不同的程序之间可以独立运转,没有绑定相关。但如今“Order Service”须要期待“Customer Service”创立完了之后才干继续运转,来成功整个创立“Order”的上班。关键是由于“Order”和“Customer”自身从逻辑过去讲就是紧耦合相关,没有“Customer”你是不能创立“Order”的。

在这种紧耦合的状况下,也可以经常使用RPC。你可以建设一个更上层级的治理程序来治理这些微服务之间的调用,这样“Order Service”就不用间接调用“Customer Service”了。

当然它从实质过去讲并没有解除耦合,只是把耦合转移到了上一层,但至少如今“order Service”和“Customer Service”可以互不影响了。之所以不能根除这种紧耦合相关是由于它们在业务上是紧耦合的。关注群众号:码猿技术专栏,回复关键词:“1111” 失掉阿里外部Java性能调优手册

再举一个购物的例子。用户选好商品之后启动“Checkout”,生成“Order”,而后须要“payment”,再从“Inventory”取货,最后由“Shipment”发货,它们每一个都是微服务。这个例子用RPC形式和事情通知形式都可以成功。

当用RPC形式时,由“Order”服务调用其余几个服务来成功整特性能。用事情通知形式时,“Checkout”服务成功之后发送“Order Placed”信息,“Payment”服务收到信息,接纳用户付款,发送“Payment received”信息。

“Inventory”服务收到信息,从仓库里取货,并发送“Goods fetched”信息。“Shipment”服务失掉信息,发送货物,并发送“Goods shipped”信息。

对这个例子来讲,经常使用事情驱动是一个不错的选用,由于每个服务发信息之后它不须要任何反应,这个信息由下一个模块接纳来成功下一步举措,期间上的要求也比上一个要宽松。用事情驱动的好处是降落了耦合度,坏处是你如今不能在程序里找到整个购物环节的步骤。

假设一个业务逻辑有它自己相对固定的流程和步骤,那么经常使用RPC或业务流程治理(BPM)能够更繁难地治理这些流程。在这种状况下选哪种打算呢?在我看来好处和坏处是大抵相当的。从技术过去讲要选事情驱动,从业务过去讲要选RPC。不过如今越来越多的人驳回事情通知作为微服务的集成形式,它仿佛曾经成了微服务之间的标椎调用形式。

事情溯源(Event Sourcing)

这是一种具备推翻性质的的设计,它把系统中一切的数据都以事情(Event)的形式记载上去,它的耐久存储叫Event Store, 普通是建设在数据库或信息队列(例如Kafka)基础之上,并提供了对事情启动操作的接口,例如事情的读写和查问。事情溯源是由畛域驱动设计(Domain-Driven Design)提进去的。

DDD中有一个很关键的概念,有界高低文(Bounded Context),可以用有界高低文来划分微服务,每个有界高低文都可以是一个微服务。上方是有界高低文的示例。下图中有两个服务“Sales”和“Support”。

有界高低文的一个关键是如何处置共享成员, 在图中是“Customer”和“Product”。在不同的有界高低文中,共享成员的含意、用法以及他们的对象属性都会有些不同,DDD倡导这些共享成员在各自的有界高低文中都区分建自己的类(包含数据库表),而不是共享。可以经过数据同步的手腕来坚持数据的分歧性。上方还会详细解说。

事情溯源是微服务的一种存储形式,它是微服务的外部成功细节。因此你可以选择哪些微服务驳回事情溯源形式,哪些不驳回,而不用一切的服务都变成事情溯源的。通常整个运行程序只要一个Event Store, 不同的微服务都经过向Event Store发送和接受信息而相互通讯。

Event Store外部可以分红不同的stream(相当于信息队列中的Topic), 供不同的微服务中的畛域实体(Domain Entity)经常使用。

事情溯源的一个短板是数据查问,它有两种形式来处置。第一种是间接对stream启动查问,这只适宜stream比拟小并且查问比拟繁难的状况。

查问复杂的话,就要驳回第二种形式,那就是建设一个只读数据库,把须要的数据放在库中启动查问。数据库中的数据经过监听Event Store中相关的事情来更新。关注群众号:码猿技术专栏,回复关键词:“1111” 失掉阿里外部Java性能调优手册

数据库存储形式只能保留形态,而事情溯源则存储了一切的历史形态,因此能依据须要回放到历史上马何一点的形态,具备很大优势。但它也不是一点疑问都没有。

第一,它的程序比拟复杂,由于事情是一等公民,你必定把业务逻辑依照事情的形式整顿进去,而后用事情来驱动程序。第二,假设你要想修闲事情或事情的格局就比拟费事,由于旧的事情曾经存储在Event Store里了(事情就像日志,是只读的),没有方法再改。

由于事情溯源和事情通知外表上看起来很像,不少人都搞不分明它们的区别。事情通知只是微服务的集成形式,程序外部是不经常使用事情溯源的,外部成功依然是传统的数据库形式。

只要当要与其余微服务集成时才会发信息。而在事情溯源中,事情是一等公民,可以不要数据库,所有数据都是依照事情的形式存储的。

虽然事情溯源的践行者有不同的意见,但有不少人都以为事情溯源不是微服务的集成形式,而是微服务的一种外部成功形式。因此,在一个系统中,可以某些微服务用事情溯源,另外一些微服务用数据库。

当你要集成这些微服务时,你可以用事情通知的形式。留意如今有两种不同的事情须要区分开,一种是微服务的外部事情,是颗粒度比拟细的,这种事情只发送到这个微服务的stream中,只被事情溯源经常使用。

另一种是其余微服务也关心的,是颗粒度比拟粗的,这种事情会放到另外一个或几个stream中,被多个微服务经常使用,是用来做服务之间集成的。这样做的好处是限度了事情的作用范围,缩小了不相关事情对程序的搅扰。详见"Domain Events vs. Event Sourcing"。

事情溯源发生曾经很长期间了,虽然热度不时在回升(尤其是这两年),但总的来说十分缓慢,议论的人不少,但消费环境经常使用的不多。究其要素就是应为它对如今的体系结构推翻太大,须要更改数据存储结构和程序的上班形式,还是有必定危险的。

另外,微服务曾经构成了一整套体系,从程序部署,服务发现与注册,到监控,服务韧性(Service Resilience),它们基本上都是针对RPC的,虽然也允许信息,但成熟度就差多了,因此有不少上班还是要自己来做。

无心思的是Kafka不时在推进它作为事情驱动的工具,也取得了很大的成功。但它却没有失掉事情溯源圈内的认可。

少数事情溯源都经常使用一个叫evenstore的开源Event Store,或是基于某个数据库的Event Store,只要比拟少的人用Kafka做Event Store。

但假设用Kafka成功事情通知就一点疑问都没有。总的来说,对大少数公司来讲事情溯源是有必定应战的,运行时须要找到适宜的场景。假设你要尝试的话,可以先拿一个微服务试水。

虽然如今事情驱动还有些生涩,但从久远来讲,还是很看好它的。像其余全新的技术一样,事情溯源须要大规模的实用场景来推进。例如容器技术就是由于微服务的盛行和推进,才走向干流。

事情溯源以前的实用场景只限于记账和源代码库,局限性较大。区块链或者会成为它的下一个机会,由于它用的也是事情溯源技术。

另外AI今后会渗入到详细程序中,使程序具备学习性能。而RPC形式注定没有自顺应性能。事情驱动自身就具备对事情启动反响的才干,这是自我学习的基础。因此,这项技术久远来讲定会大放异彩,但短期内(3-5年)大略不会成为干流。

RPC形式

RPC的形式就是远程函数调用,像RESTFul,gRPC, DUBBO 都是这种形式。它普通是同步的,可以马上失掉结果。在实践中,大少数运行都要求立刻失掉结果,这时同步形式更有优势,代码也更繁难。

服务网关(API Gateway)

相熟微服务的人或者都知道服务网关(API Gateway)。当UI须要调用很多微服务时,它须要了解每个服务的接口,这个上班量很大。

于是就用服务网关创立了一个Facade,把几个微服务封装起来,这样UI就只调用服务网关就可以了,不须要去对付每一个微服务。上方是API Gateway示例图:

服务网关(API Gateway)不是为了处置微服务之间调用的紧耦合疑问,它关键是为了简化客户端的上班。其实它还可以用来降落函数之间的耦合度。

有了API Gateway之后,一旦服务接口修正,你或者只须要修正API Gateway, 而不用修正每个调用这个函数的客户端,这样就缩小了程序的耦合性。

服务调用

可以自创API Gateway的思绪来缩小RPC调用的耦合度,例如把多个微服务组织起来构成一个完整性能的服务组合,并对外提供一致的服务接口。这种想法跟上方的API Gateway有些相似,都是把服务集中起来提供粗颗粒(Coarse Granular)服务,而不是细颗粒的服务(Fine Granular)。

但这样建设的服务组合或者只适宜一个程序经常使用,没有多少共享价值。因此假设有适宜的场景就驳回,否侧也不用强求。虽然我们不能降落RPC服务之间的耦合度,却可以缩小这种紧耦合带来的影响。

降落紧耦合的影响

什么是紧耦合的关键疑问呢?就是客户端和服务端的更新不同步。服务端总是先更新,客户端或者有很多,假设要求它们同时更新是不事实的。它们有各自的部署期间表,普通都会选用在下一次性部署时顺带更新。

普通有两个方法可以处置这个疑问:

同时允许多个版本:这个上班量比拟大,因此大少数公司都不会驳回这种形式。

服务端向后兼容:这是更通用的形式。例如你要加一个新性能或有些客户要求给原来的函数参与一个新的参数,但别的客户不须要这个参数。这时你只好新建一个函数,跟原来的性能差不多,只是多了一个参数。这样新旧客户的需求都能满足。它的好处是向后兼容(当然这取决于你经常使用的协定)。

它的坏处是当新的客户来了,看到两个差不多的函数就懵懂了,不知道该用那个。而且期间越长越重大,你的服务端或者性能参与的不多,但相似的函数却越来越多,无法选用。

它的处置方法就是经常使用一个允许向后兼容的RPC协定,如今最好的就是Protobuf gRPC,尤其是在向后兼容上。

它给每个服务定义了一个接口,这个接口是与编程言语有关的中性接口,而后你可以用工具生成各个言语的实现代码,供不同言语经常使用。函数定义的变量都有编号,变量可以是可选类型的,这样就比拟好地处置了函数兼容的疑问。

就用上方的例子,当你要参与一个可选参数时,你就定义一个新的可选变量。由于它是可选的,原来的客户端不须要提供这个参数,因此不须要修正程序。

而新的客户端可以提供这个参数。你只需在服务端能同时处置这两种状况就行了。这样服务端并没有参与新的函数,但用户的新需求满足了,而且还是向后兼容的。

微服务的数量有没有下限?

总的来说微服务的数量不要太多,不然会有比拟重的运维累赘。有一点须要明白的是微服务的盛行不是由于技术上的翻新,而是为了满足治理上的须要。单体程序大了之后,各个模块的部署期间要求不同,对主机的提升要求也不同,而且团队人数泛滥,很难协调治理。

把程序拆分红微服务之后,每个团队担任几个服务,就容易治理了,而且每个团队也可以依照自己的节拍启动翻新,但它给运维带来了渺小的费事。所以在微服务刚进去时,我不时感觉它是一个进化,弊大于利。但由于治理上的疑问没有其余处置打算,只要硬着头皮上了。

值得庆幸的是微服务带来的费事都是可解的。直到起初,微服务建设了全套的智能化体系,从程序集成到部署,从全链路跟踪到日志,以及服务检测,服务发现和注册,这样才把微服务的上班量降了上去。

虽然微服务在技术上一无是处,但它的盛行还是大大推进了容器技术,服务网格(Service Mesh)和全链路跟踪等新技术的开展。不过它自身在技术上还是没有发现任何优势。

直到有一天,我看法到单体程序其实性能调试是很艰巨的(很难分别出瓶颈点),而微服务性能了全链路跟踪之后,能很快找到症结所在。看来微服务从技术来讲也不全是缺陷,总算也有好的中央。但微服务的颗粒度不宜过细,否则上班量还是太大。

普通规模的公司十几个或几十个微服务都是可以接受的,但假设有几百个甚至上千个,那么绝不是普通公司可以治理的。虽然现有的工具曾经很完全了,而且与微服务有关的整个流程也曾经基本上所有智能化了,但它还是会参与很多上班。

Martin Fowler几年以前倡导先从单体程序开局(详见 MonolithFirst),而后再逐渐把性能拆分进来,变成一个个的微服务。然而起初有人推戴这个倡导,他也有些松口了。

假设单体程序不是太大,这是个好主意。可以用数据额库表的数量来权衡程序的大小,我见过大的单体程序有几百张表,这就太多了,很难治理。反常状况下,一个微服务可以有两、三张表到五、六张表,普通不超越十张表。但假设要缩小微服务数量的话,可以把这个规范放宽到不要超越二十张表。

用这个做为大抵的目的来创立微程序,假设经常使用一段期间之后还是感觉太大了,那么再逐渐拆分。当然,依照这个规范建设的服务更像是服务组合,而不是单个的微服务。不过它会为你缩小上班量。只需不影响业务部门的翻新进展,这是一个不错的打算。

究竟应不应该选用微服务呢?假设单体程序曾经没法治理了,那么你别无选用。假设没有治理上的疑问,那么微服务带给你的只要疑问和费事。其实,普通公司都没有太多选用,只能驳回微服务,不过你可以选用建设比拟少的微服务。假设还是没法选择,有一个折中的打算,“外部微服务设计”。

外部微服务设计

这种设计外表上看起来是一个单体程序,它只要一个源代码存储仓库,一个数据库,一个部署,但在程序外部可以依照微服务的思维来启动设计。它可以分红多个模块,每个模块是一个微服务,可以由不同的团队治理。

用这张图做例子。这个图里的每个圆角方块大抵是一个微服务,但我们可以把它作为一个单体程序来设计,外部有五个微服务。

每个模块都有自己的数据库表,它们都在一个数据库中,但模块之间不能跨数据库访问(不要建设模块之间数据库表的外键)。

“User”(在Conference Management模块中)是一个共享的类,但在不同的模块中的名字不同,含意和用法也不同,成员也不一样(例如,在“Customer Service”里叫“Customer”)。

DDD(Domain-Driven Design)倡导不要共享这个类,而是在每一个有界高低文(模块)中都建一个新类,并领有新的名字。

虽然它们的数据库中的数据应该大抵相反,但DDD倡导每一个有界高低文中都建一个新表,它们之间再启动数据同步。

这个所谓的“外部微服务设计”其实就是DDD,但过后还没有微服务,因此外表看起来是单体程序,但外部曾经是微服务的设计了。

它的书在2003就出版了,过后就很有名。但它更侧重于业务逻辑的设计,践行起来也比拟艰巨,因此大家议论得很多,真正用的较少。

直到十年之后,微服务进去之后,人们发现它其实外部就是微服务,而且微服务的设计须要用它的思维来指点,于是就又从新焕发了青春,而且这次更猛,曾经到了每个议论微服务的人都不得不议论DDD的境地。不过一本软件书籍,在十年之后还能指点新技术的设计,十分令人敬仰。

这样设计的好处是它是一个单体程序,省去了多个微服务带来的部署、运维的费事。但它外部是按微服务设计的,假设要拆分红微服务会比拟容易。至于什么时刻拆分不是一个技术疑问。

假设担任这个单体程序的各个团队之间不能在部署期间表,主机提升等方面达成分歧,那么就须要拆分了。

当然你也要应答随之而来的各种运维费事。外部微服务设计是一个折中的打算,假设你想试水微服务,但又不情愿冒太大危险时,这是一个不错的选用。

论断

微服务之间的调用有两种形式,RPC和事情驱动。事情驱动是更好的形式,由于它是松耦合的。但假设业务逻辑是紧耦合的,RPC形式也是可行的(它的好处是代码更繁难),而且你还可以经过选取适宜的协定(Protobuf gRPC)来降落这种紧耦合带来的危害。

由于事情溯源和事情通知的相似性,很多人把两者弄混了,但它们实践上是完全不同的物品。微服务的数量不宜太多,可以先创立比拟大的微服务(更像是服务组合)。

假设你还是不能确定能否驳回微服务架构,可以先从“外部微服务设计”开局,再逐渐拆分。

您可能还会对下面的文章感兴趣: