微服务基础介绍之耦合
我们在之前的文章中对微服务进行了概述,相信大家对微服务也有了一定的了解。有很多人会问,我们该怎么来把一个系统拆分成多个微服务呢?要想回答这个问题,就需要知道各个微服务之间都有哪些类型的耦合,本文就来和大家聊一聊微服务的耦合类型。
Domain Coupling
所谓Domain的coupling其实很简单,就是一个微服务和另外一个微服务之间耦合的原因就是因为一个微服务需要使用另外一个微服务的功能。
我们来看一个简单的例子,我们下单购买某一个物品,这里涉及几个模块,一个是Order Processor,也就是下单的Processor,它会去仓库也就是Warehouse里面把这个下单的物品reserve了,然后再去Payment那边进行支付。如下图所示:
这里我们就说Order Processor和Warehouse以及Payment之间都存在Domain的coupling,因为需要使用它们的功能,而Warehouse和Payment之间没有相互的调用,因此它们之间不存在Domain coupling。
不难想象,在微服务的世界里,domain coupling几乎是不可避免的,因为你总是需要多个微服务之间合作才能完成一个完整的功能。当然,从经验的角度来看,假如有一个微服务和很多别的微服务之间都有domain的coupling,可能也需要去仔细分析一下是不是这个微服务做的事情太多了。
总的来说,Domain coupling被认为是最松散的耦合了。
直通耦合(pass-through coupling)
顾名思义,所谓直通耦合就是一个微服务需要一些数据来传递给它下面的微服务,但是它自己并没有这些数据,它需要它的上层微服务把这些数据传给它,那我们就认为他们之间是直通耦合的。这种情形下就意味着最上层的微服务不仅要知道它调用的微服务需要什么数据,甚至需要知道它调用的微服务下面一层的微服务需要的数据。
我们继续以一个例子来介绍这种耦合,我们同样使用Order Process来下了一单,这个时候我们通知了Warehouse去准备好发货,我们需要把后面Shipping需要的信息也发送给Warehouse,比如收件人的地址,联系电话等(Shipping Manifest)。其实Warehouse本身并不关心这些信息,它会调用Shipping的服务去进行发送,这些信息都是Shipping那边关心的,所以它会把这些Shipping Manifest的内容再传递给Shipping的服务。如下图所示:
不难看出,这种耦合最大的问题就是下面服务需要的数据改变的时候,我们就需要让上面多个服务进行改变,比如说Shipping需要一个快递类型(是发顺丰还是别的等),就需要Warehouse进行修改,然后Order Processor也需要修改,加入这个信息才行。
当然也有一些方法来解决这个问题,比如说我们能不能去除中间这些层,直接进行调用,在我们的例子中,我们直接通过Order Processor调用Shipping,把Shipping Mainifest传递给它就可以了,如下图所示:
这种修改有几个问题,一个问题是Order Processor的Domain coupling变多了,因为现在它需要调用Warehouse和Shipping了,不过因为Domain coupling是一个比较松散的耦合,所以还好。这里比较大的问题是整个操作变得复杂了,因为我们首先要Reserver stock,然后进行发货,发货完成之后就需要把相应的物品在stock中remove了,以前这些操作都可以在Warehouse中一步解决,现在需要分成上面图示的三步才能完成。
还有一种修改就是Order Processor其实不关心Shipping需要什么,它把它知道的所有信息都发送给Warehouse,Warehouse再根据Shipping需要的内容来进行构造,如下图所示:
注意这里和开始的差别就在于Order Processor不再传递Shipping需要的Shipping Manifest,而是传递各种元数据,这些数据和Shipping没有直接关系,Warehouse自己再根据这些信息来重新组织Shipping Manifest的数据。好处就是假如Shipping Manifest修改的内容在Warehouse中已经有了,那么上面Order Processor其实就不需要做任何修改了,当然假如需要一个完全新的数据,那么还是需要修改到上面的Order Processor的。
公用耦合
所谓公用耦合就是两个或者多个微服务在使用公用的数据。最常见的就是使用同样的数据库,当然也可以是使用同样的文件等等。这种耦合最大的问题就是当我们修改公用数据的结构的时候,相应的多个微服务也需要同步做修改。
我们继续看上面的例子,假如我们在order processor以及warehouse中都需要使用一些国家相关的信息(比如语言,货币等等),他们就需要访问同样的static的数据。所以假如这个Country相关的static的数据发生改变,那么Order Processor和Warehouse就都需要发生改变了。
当然这个例子可能不是那么合适,因为这种static的数据有时并不那么容易修改,我们再来看看下面这个例子,还是这两个微服务,这次他们要读取和更新一个order的状态,比如说Order processor可能会把这个order的状态改成PAID,Warehouse可能会把这个order的状态改成SHIPPED等等。如下图所示:
上面这个例子最大的问题就是Order Processor和Warehouse同时都在改变order的状态,我们如何才能保证Order的状态是按照我们认为合理的顺序在改变的,比如不会在SHIPPED之后再Cancel,或者只有PAID之后我们才运行SHIP等。比较直觉的方法就是我们可以使用一个专门的微服务来进行管理,这样Order Processor和Warehouse都只是发送相关的改变请求给这个微服务,这个微服务本身可以对status进行validation,从而避免各种不valid的状态的改变:
另外一个值得注意的是公用耦合有时也会成为性能的瓶颈,比如说同时访问一个文件系统或者数据库(尤其是比较heavy的访问)很可能会导致数据库或者文件系统变慢,从而出现性能问题。
所以通常来说我们认为公用的耦合不是一个好的事情,最好能够避免。
内容耦合
所谓内容耦合就是上游的微服务会访问下面微服务的内部,并修改它的内部状态。一个比较常见的情况就是一个微服务直接访问另外一个微服务的数据库部分,然后修改数据库内部的相关内容。从实现上来看,是不是和我们上面说的公用耦合类似,但是两者还是有差别的,在公用耦合中,我们知道我们访问的数据库应该是我们控制的,但是在内容耦合上则有所不同,有时我们很难讲清楚究竟这个数据库究竟是该归谁管。
我们继续基于上面的例子来解释这个内容耦合,Order Processor还是通过Order来进行状态的更新,但是Warehouse直接访问数据库了,可能它也有自己的一套逻辑来validate状态(即使有和Order service的也可能重复)。具体如下图所示:
这样一来,数据库中的Order table的修改就尤其需要注意了,因为它不仅仅被Order service控制,还需要考虑外面直接调用的Warehouse的服务。而这种暴露显然会增加修改和维护的复杂性。当然一个比较好的做法还是和上面提到的一样,让Warehouse也去调用Order,而不是直接访问数据库中的相关表。
对于内容耦合,能避免就不要使用。
总结
本文我们介绍了耦合的几个类型,总得来说按照我们的介绍的顺序,这些耦合越来越紧密,如下图所示:
当我们对这些耦合类型有了理解之后,后面再来讨论微服务的boundary就会方便很多。
Recent Comments