为什么需要服务治理

在未引入服务治理模块之前,服务之间的通信是服务间直接发起并调用来实现的。以外卖 APP 的订单功能为例,OrderService(订单服务)直接向 FoodService(菜品服务)发起请求,如图 5-10 所示。

image 2025 04 14 20 39 28 330
Figure 1. 图5-10 未引入服务治理模块服务通信的方式

只要知道了对应服务的服务名称、IP地址、端口号,就能够发起服务通信。FoodServiceIP 地址为 192.168.1.101:9001OrderService 直接向该 IP 地址发起请求就可以获取对应的数据。

然而,各个微服务实例为了满足高可用的需求,肯定会搭建集群,此时的调用链路可能就变成图 5-11 中的情形了。

image 2025 04 16 10 41 04 715
Figure 2. 图5-11 服务集群间的通信方式

如果微服务架构中独立的服务不多,则可以通过硬编码的方式对服务名称、IP 地址、端口号进行静态配置,进而完成对服务的调用。此时需要开发人员手动维护各个服务的实例清单,清单中包括服务名称、服务地址等信息。

随着业务的发展,系统功能越来越复杂,对应的微服务实例也在不断增多。图 5-11 是只有两个服务的调用链路示意图,如果系统中的服务有成百上千个,手动配置各个服务的实例清单就会变得越来越复杂,这时再使用硬编码配置的方式就非常愚蠢了。当集群规模发生改变、服务实例数量的增加和减少、服务名称发生改变、服务实例部署的 IP 地址或端口号发生改变,维护起来绝对是一个大工程,出错的概率也会大大增加,而且维护这种硬编码的配置内容需要消耗太多的开发资源。

当然,也有读者会问:使用代理技术把某个服务集群中的 IP 地址统一代理后暴露一个 IP 地址,不就能大大减少维护的配置内容了吗?比如,使用 Nginx 做反向代理、使用 LVS 技术或使用商用的 SLB 技术都可以实现。笔者画了一张图来描述此时的情形,如图 5-12 所示。

image 2025 04 16 10 42 52 007
Figure 3. 图5-12 增加反向代理后服务集群间的通信方式

OrderService 实例如果想要调用 FoodService,只要在代码中配置一个 food.newbee.ltd 调用地址即可。这个地址就是对 FoodService 的实例集群做完代理配置后暴露的一个虚拟 IP 地址或一个 Nginx 网关,OrderService 只需要向这个地址发起请求,之后在具体的网关层通过负载均衡策略把这次请求转发到菜品服务集群的某一个 FoodService 实例中进行处理。

这种方式确实能够减少服务实例清单的维护难度,但是又出现了两个新问题。加上一层中间代理后,OrderServiceFoodService 两个服务集群间并不能直接通信,两个服务的通信需要借助这一层的中间代理来完成,第一个问题就出现了:无法直连。进而导致调用成本的增加,因为增加了一次网络消耗。第二个问题是维护成本高仍然存在,如集群规模发生改变、服务实例数量的增加和减少、服务实例部署的 IP 地址或端口号发生改变,依然要把这些信息配置到代理中,原来是在代码层面维护各个服务的实例清单,加上这一层的中间代理后,实例清单维护变简单了,但是需要对具体的代理软件进行配置文件的维护,如对 Nginx 配置文件或虚拟 IP 池进行修改。

为了解决上述维护问题,市面上出现了大量的服务治理框架和产品。这些框架和产品的实现都围绕多服务实例的管理混乱、通信配置烦琐等问题来完成对微服应用实例的自动化管理,保证各微服务实例的快速上线、下线和正常通信。

当然,“服务治理” 其实是一个很宽泛的概念,而且可追溯的时间比较久远,针对不同架构也有着不同的理解和实现。笔者将结合具体的框架和产品,主要讲解服务治理中的三个核心问题:服务注册服务发现服务的健康检查机制