微服务之间的服务治理

170 阅读11分钟
  • 为什么需要服务治理?
  • 服务治理包含哪些内容?
  • 加入服务治理模块后,各个服务间的调用步骤是什么样子的?

为什么需要服务治理?

在没有引入服务治理模块之前,服务之间的通信是通过服务间直接发起调用来实现的。以外卖 APP 的订单功能为例,OrderService 直接向 FoodService 发起请求即可,如下图所示。

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

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

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

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

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

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

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

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

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

服务注册和服务发现

服务注册

在服务治理框架或者产品中,服务的注册中心是必不可少的,这里的注册中心也可以叫做 “服务中心”。已经部署的每个服务实例都会向这个注册中心发起请求,把自己的服务信息添加到注册中心里,比如服务名称、IP 地址、端口号、通信协议等等服务信息。注册中心会维护这份服务的实例清单,有了注册中心之后,就不用开发人员或者运维人员来维护各个服务的实例清单了,集群规模发生改变、服务实例的增加和删减、服务命名发生修改、服务实例部署的地址或者端口发生改变,这些情况发生时都会通知到注册中心,注册中心会自动修改实例清单中的内容。

比如订单微服务的实例运行在 IP 地址为 192.168.1.122 的 7010 端口和 IP 地址为 192.168.3.41 的 7020 端口上,菜品微服务的实例运行在 IP 地址为 192.168.1.102 的 9009 端口、IP 地址为 192.168.1.102 的 9010 端口和 IP 地址为 192.168.3.101 的 9020 端口上。当这些服务实例的进程全部启动时,会向注册中心依次发出通知,注册中心就会将这些服务的信息维护到实例清单中了,格式如下:

服务名称实例数量实例地址
订单服务2192.168.1.122:7010,192.168.3.41:7020
餐饮服务3192.168.1.102:9010,192.168.3.102:9010,192.168.3.101:9020

当然,不同的注册中心产品的实例清单的格式和记录的信息会有所不同,上述表格只是笔者做的一个简易版的总结。

每个服务的实例在启动时向注册中心发出通知,将自己的服务名称、服务地址等信息提交到注册中心,注册中心将这些信息维护到实例清单中,这个过程就是服务注册

服务发现

由于在服务治理相关框架工作,各个服务间的调用可以不再通过硬编码的方式指定具体的实例地址来发起通信,而是通过根据服务名称来发起通信请求。在注册中心的作用下,服务间的通信就变得简单了,由于注册中心维护了服务信息,减轻了开发人员与运维人员的负担。比如订单微服务想要与菜品微服务通信,因为两个微服务都与注册中心保持连接,调用方可以向注册中心查询目标服务并获取到目标服务的实例清单,之后就可以对具体的某个实例发起访问了。

订单微服务想要调用菜品微服务,就可以向注册中心发起查询服务的请求,服务中心会将名称为 food-service 的服务信息列表返回给订单微服务。此时有三个名称为 food-service 的可用实例,订单微服务会获取到这个清单,可用的实例地址为 192.168.1.102:9010、192.168.3.102:9010、192.168.3.101:9020。当订单微服务需要发起调用的时候,便会从这三个可用的地址列表中通过某种负载均衡策略选择一个地址,并直接发起请求。通过注册中心返回的清单信息,调用方就可以精准地定位到目标微服务,进而直接发起请求,不需要通过中间代理。

关于服务的负载均衡策略,后续会单独用一个章节来讲解。

服务调用方会从服务的注册中心获取被调用方的服务列表,或者由服务的注册中心将被调用方的服务列表变动信息推送给服务调用方,这个过程叫做服务发现

在引入服务治理后,服务的信息维护和调用过程变为了下图中的情形。

第一步,服务注册。所有的服务实例在启动时都会向注册中心发送通知,将自己的服务信息提交到注册中心。如上图所示,服务的注册中心可以选择 Eureka、Consul、Nacos 等产品,本课程所选择的服务中心是 Nacos。

第二步,服务发现。调用方会从服务中心获取到被调用方服务的所有实例信息,或者由注册中心将被调用方服务数据的变动推送给调用方,即调用方不会每次都向服务的注册中心发起请求去获取被调用方的实例信息。比如调用方可以在请求一次后就将可用的服务数据缓存到本地,直到注册中心主动将被调用方的服务实例清单变更信息推送过来,再进行本地缓存数据的变更,不同的产品或者应用场景在缓存和服务信息的更新机制上会有不同的实现策略。

第三步,服务通信。调用方会从可用的服务列表中选取某一个地址,直接发起服务调用。

健康检查

引入服务治理后,通过服务注册和服务发现已经解决了微服务架构下的不少问题。比如前文中提到的维护困难和加入代理后无法直接调用的问题,完善了服务间的通信过程。但是 “治理” 可不是简简单单地维护服务清单和保证通信正常,还需要有一定的纠错能力,保证服务清单中内容的可用性,也就是健康检查和服务纠错的机制。

如果被调用服务的集群中,某几个服务实例因为网络故障或者服务器问题无法提供稳定的服务。比如菜品服务集群中 192.168.1.102:9009、192.168.1.102:9010 这两个服务实例因为服务器断电导致无法继续提供服务,这时订单服务向菜品服务发起了多次服务调用,根据注册中心返回的实例清单访问了这两个实例,一定就会发生访问超时的异常情况。

在服务治理的产品和框架中是如何规避这种问题的呢?业界常用的解决方案是 “探活” 或者叫“心跳检查”。服务中心可以通过这种机制来筛选出不健康的服务实例或者异常的服务实例,然后将其踢出服务清单。等到服务实例正常后,再将其维护到服务清单中。这种机制能够尽可能地保证调用方在发送请求时能够直接到达正常的节点。

将健康检查的机制添加到服务注册和服务发现的流程后,就得出了下图中的步骤图。

这里,健康检查相关的步骤都使用虚线来标示。

所有的服务实例在注册中心注册成功后,每个服务实例都需要定时发送请求,告知注册中心自己的状态,业界一般称这个过程为 heartbeat,即 “心跳”。如果服务实例能够持续发送心跳信息,则表示一切正常,服务会被标示为可用的、可发现的。如果注册中心在一段时间内没有收到某个服务实例的心跳信息,就会将这个服务实例标记为不可用或者不可达的状态,进而从可用的服务列表中剔除将该服务实例的信息,在调用方查询可用的服务实例清单时,该服务实例的信息就不会返回给调用方。

健康检查和异常服务的剔除操作,这些不需要开发者太过关注,使用的开发框架和服务中心产品都会集成这部分功能并且自动处理的。

总结

读者如果有任何问题或者想要和笔者讨论的内容,都可以在评论区留下看法,笔者会根据读者的反馈和问题继续整理和完善本章节内容。介绍完服务通信与服务治理后,接下来笔者将会选择 Nacos 搭建服务中心,并通过实际的编码和项目案例,来讲解服务通信与服务治理在代码层面是如何体现的。

原文地址 juejin.cn