认识服务通信

为什么需要服务通信

有因必有果。导致服务通信出现的原因是什么?或者说为什么需要服务通信?

以创建外卖订单这个行为来举例。外卖 APP 的订单确认页面有如下信息:用户地址信息、菜品信息、红包信息、商家信息、配送信息、优惠券信息等。在一切信息确认之后就可以生成这个外卖订单了,在订单功能的实现方法中会根据这些信息来创建一条订单数据。

在未做服务化拆分时,各个功能模块间在同一个工程中可以直接进行方法或功能的调用。比如,当前所举例的外卖项目,当这些功能模块在同一个工程中时,直接在 OrderService 类中调用每个功能实现类的对应方法即可,如图 5-1 所示。

而微服务架构或类微服务架构的项目本质上是运行在多台机器上的分布式系统,每个服务都是独立的,一个服务如果想要调用另一个服务上的功能或方法,就要确保二者之间能够 “通信”。

以外卖 APP 为例,上述所提到的服务如果都被服务化拆分并做成了一个个独立的服务,那么在创建订单时,OrderService 类就无法做到本地调用 UserServiceFoodServiceDeliveryServiceCouponService 等实现类中的方法了,如图 5-2 所示。此时,如果无法打通服务间的调用链路,订单生成功能就无从谈起了。

image 2025 04 14 18 55 56 884
Figure 1. 图5-1 外卖项目中的本地方法调用
image 2025 04 14 18 56 13 099
Figure 2. 图5-2 服务拆分后调用链路中断

服务通信简介

当然,服务通信并不是一个非常复杂的概念。

单体应用中可以直接进行本地方法调用,在最终的微服务架构项目实战中,服务通信的实现基于 OpenFeign,“通信” 协议为 HTTP,使用 FeignOpenFeign 就是基于 HTTP 的调用,需要发送 HTTP 请求和处理 HTTP 请求的回调。比如,在创建订单时,OrderService 类需要调用 FoodService 类中的方法 getFoodListByIds(),本地方法调用很简单,如图 5-3 所示。

image 2025 04 14 18 57 07 861
Figure 3. 图5-3 调用本地方法 getFoodListByIds()

服务拆分后的 OrderService 类当然也需要调用 FoodService 类的方法 getFoodListByIds()。虽然无法直接调用 getFoodListByIds() 方法,但是 FoodService 类会把 getFoodListByIds() 方法的结果通过 REST 接口的方式返回给调用端 OrderService 类,之后 OrderService 类需要处理请求回调,最终得到的依然是 getFoodListByIds() 方法的结果,调用过程及注意事项如图 5-4 所示。

类比到现实世界中,小张和小李两个人如果在一间房子里,是可以直接对话的,小张问小李:“你今天写了几个 Bug?” 小李说:“我怎么可能写 Bug。” 而如果两个人相隔很远,就只能通过通信工具来实现对话了,如打电话、发送IM消息、视频通话等,两个人依然可以进行沟通。

因此,服务通信这个概念中的 “通信” 本质上依然是方法调用,只是无法做到本地调用,需要借助其他技术来实现。在最终的项目实战中,服务通信的实现基于 HTTP,就像小张和小李通话时选择了打电话这种方式。当然也可以选择其他的技术实现,如 DubbogRPCThrift 等技术。HTTPDubbogRPCThrift 这些技术属于服务通信中的同步调用,就是严格地遵循 “一问一答”,调用端发起一次 “通信”,被调用端处理后需要及时回应,可能导致阻塞。而除同步调用外,还有异步调用,常见的就是通过消息队列来实现,调用端与被调用端通过异步消息来通信和实现具体的功能,这种状态下,及时回应就不是必需的了,也不会导致阻塞。

image 2025 04 14 18 58 35 471
Figure 4. 图5-4 远程调用 getFoodListByIds() 方法