微服务之间的最佳调用方式

在微服务架构中,需要调用很多服务才能完成一项功能 。服务之间如何互相调用就变成微服务架构中的一个关键问题 。
服务调用有两种方式,一种是RPC方式,另一种是事件驱动(Event-driven)方式,也就是发消息方式 。
消息方式是松耦合方式,比紧耦合的RPC方式要优越,但RPC方式如果用在适合的场景也有它的一席之地 。
我们总在谈耦合,那么耦合到底意味着什么呢?
耦合的种类:时间耦合:客户端和服务端必须同时上线才能工作 。发消息时,接受消息队列必须运行,但后台处理程序暂时不工作也不影响 。
容量耦合:客户端和服务端的处理容量必须匹配 。发消息时,如果后台处理能力不足也不要紧,消息队列会起到缓冲的作用 。
接口耦合:RPC调用有函数标签,而消息队列只是一个消息 。例如买了商品之后要调用发货服务,如果是发消息,那么就只需发送一个商品被买消息 。
发送方式耦合:RPC是点对点方式,需要知道对方是谁,它的好处是能够传回返回值 。消息既可以点对点,也可以用广播的方式,这样减少了耦合,但也使返回值比较困难 。
下面我们来逐一分析这些耦合的影响 。第一,时间耦合,对于多数应用来讲,你希望能马上得到回答,因此即使使用消息队列,后台也需要一直工作 。
第二,容量耦合,如果你对回复有时间要求,那么消息队列的缓冲功能作用不大,因为你希望及时响应 。
真正需要的是自动伸缩(Auto-scaling),它能自动调整服务端处理能力去匹配请求数量 。第三和第四,接口耦合和发送方式耦合,这两个确实是RPC方式的软肋 。
事件驱动(Event-Driven)方式Martin Fowler把事件驱动分成四种方式(What do you mean by “Event-Driven”),简化之后本质上只有两种方式 。一种就是我们熟悉的的事件通知(Event Notification),另一种是事件溯源(Event Sourcing) 。
事件通知就是微服务之间不直接调用,而是通过发消息来进行合作 。事件溯源有点像记账,它把所有的事件都记录下来,作为永久存储层,再在它的基础之上构建应用程序 。
实际上从应用的角度来讲,它们并不应该分属一类,它们的用途完全不同 。事件通知是微服务的调用(或集成)方式,应该和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”可以互不影响了 。之所以不能根除这种紧耦合关系是因为它们在业务上是紧耦合的 。
再举一个购物的例子 。用户选好商品之后进行“Checkout”,生成“Order”,然后需要“payment”,再从“Inventory”取货,最后由“Shipment”发货,它们每一个都是微服务 。这个例子用RPC方式和事件通知方式都可以完成 。


推荐阅读