有因必有果。导致服务通信出现的原因是什么?或者说为什么需要服务通信?
以创建外卖订单这个行为来举例。外卖APP的订单确认页面有如下信息:用户地址信息、菜品信息、红包信息、商家信息、配送信息、优惠券信息等。在一切信息确认之后就可以生成这个外卖订单了,在订单功能的实现方法中会根据这些信息来创建一条订单数据。
在未做服务化拆分时,各个功能模块间在同一个工程中可以直接进行方法或功能的调用。比如,当前所举例的外卖项目,当这些功能模块在同一个工程中时,直接在OrderService类中调用每个功能实现类的对应方法即可,如图5-1所示。
而微服务架构或类微服务架构的项目本质上是运行在多台机器上的分布式系统,每个服务都是独立的,一个服务如果想要调用另一个服务上的功能或方法,就要确保二者之间能够“通信”。
以外卖APP为例,上述所提到的服务如果都被服务化拆分并做成了一个个独立的服务,那么在创建订单时,OrderService类就无法做到本地调用UserService、FoodService、DeliveryService、CouponService等实现类中的方法了,如图5-2所示。此时,如果无法打通服务间的调用链路,订单生成功能就无从谈起了。
图5-1 外卖项目中的本地方法调用
图5-2 服务拆分后调用链路中断
在分布式架构或微服务架构中,服务是独立开发和独立部署的,物理层面是独立的。但是在具体的功能实现时,可能也需要各个服务的配合。
当然,服务通信并不是一个非常复杂的概念。
单体应用中可以直接进行本地方法调用,在最终的微服务架构项目实战中,服务通信的实现基于OpenFeign,“通信”协议为HTTP,使用Feign或OpenFeign就是基于HTTP的调用,需要发送HTTP请求和处理HTTP请求的回调。比如,在创建订单时,OrderService类需要调用FoodService类中的方法getFoodListByIds(),本地方法调用很简单,如图5-3所示。
图5-3 调用本地方法getFoodListByIds()
服务拆分后的OrderService类当然也需要调用FoodService类的方法getFoodListByIds()。虽然无法直接调用getFoodListByIds()方法,但是FoodService类会把getFoodListByIds()方法的结果通过REST接口的方式返回给调用端OrderService类,之后OrderService类需要处理请求回调,最终得到的依然是getFoodListByIds()方法的结果,调用过程及注意事项如图5-4所示。
类比到现实世界中,小张和小李两个人如果在一间房子里,是可以直接对话的,小张问小李:“你今天写了几个Bug?”小李说:“我怎么可能写Bug。”而如果两个人相隔很远,就只能通过通信工具来实现对话了,如打电话、发送IM消息、视频通话等,两个人依然可以进行沟通。
因此,服务通信这个概念中的“通信”本质上依然是方法调用,只是无法做到本地调用,需要借助其他技术来实现。在最终的项目实战中,服务通信的实现基于HTTP,就像小张和小李通话时选择了打电话这种方式。当然也可以选择其他的技术实现,如Dubbo、gRPC、Thrift等技术。HTTP、Dubbo、gRPC、Thrift这些技术属于服务通信中的同步调用,就是严格地遵循“一问一答”,调用端发起一次“通信”,被调用端处理后需要及时回应,可能导致阻塞。而除同步调用外,还有异步调用,常见的就是通过消息队列来实现,调用端与被调用端通过异步消息来通信和实现具体的功能,这种状态下,及时回应就不是必需的了,也不会导致阻塞。
图5-4 远程调用getFoodListByIds()方法