• 作者:老汪软件技巧
  • 发表时间:2024-11-04 00:02
  • 浏览量:

摘要:注册中心一直是微服务架构中重要组件,是众多服务相互通信构建的基础。本文以Nacos为例,着重讲解其服务注册中心的基础概念及原理。

通过本文,你可以了解微服务中注册的完整生命周期,并知晓服务注册中心在微服务架构中的作用,同时对Nacos中的服务发现原理有一个全面的认识。

前言

在介绍注册中心之前,我们不妨先来考虑这样一个场景。假设现在你所负责系统包含两个微服务即服务A 和服务B。由于服务并未采用原先的单体架构,这也就导致如果需要进行服务间的通信时,不能像之前那样通过Bean注入的方式来完成业务间的调用。这主要是因为在微服务的架构风格下,由于不同的服务间相互部署,所以服务间的通信需要通过Http来完成。

因此服务A和服务B的通信,需要借助网络来完成服务间的通信。进一步,根据网络通信的知识,我们知道如果要借助Http来进行网络通信的话,需要要知道目标服务的IP和端口信息。

换言之,如果要实现服务A和服务B的通信,我们必须在代码服务A或服务B中写死对方所在的IP和端口,然后通过RestTemplate来完成服务间的调用。 这样的方式在系统服务数较少时是完全可行的。但当系统服务增多时,通过硬编码方式维护服务IP方式的弊端也便显现。

此时,一旦当前服务所依赖的服务发生迁移或扩容时,当前服务就必须得修改源码并重启。其次,如果每个子服务都依赖另外的多个外部服务。 与此同时,由于服务间错综复杂的依赖关系,这会使得各个服务间维护的配置信息将会变得异常复杂,并且子系统之间的依赖维护也会变得异常困难。这明显不是微服务模式下最初的本意,而为了避免维护服务间这种错综的关系,注册中心的概念应运而生。

注册中心

正如前文所述,在注册中心未问世之前。我们如果要维护服务间信息时,只能通过写死代码的方式来维护所依赖服务的IP信息,进而来保证服务间通信的正常进行,而这种方式的最大弊端便在于当服务数增多时,依赖关系便会变得异常复杂,难于管理。

此时,如果能有这样一款组件,其可以帮助我们自动发现服务,并记录服务的信息。当服务间需要通信时,完全借助该组件记录服务信息来完成对应服务的调用。从而也就避免在代码中手动维护服务信息尴尬处境。而注册中心便是解决这一困境的组件。

事实上,注册中心解决的首要任务就是服务注册与服务发现。 通过服务注册与服务发现我们就可以将系统内的服务的进行统一化管理。如下的这张图便对此过程进行了详细的描述。

image.png

从上图中的步骤中我们可以看出,首先,服务提供者向注册中心发起了注册,进而将自己的地址信息上报到注册中心,这个过程就是服务注册。接下来,每隔一段时间服务消费者便会从注册中心获取服务提供者的的服务列表,或者由服务中心将服务列表的变动推送给服务消费者,这个过程便是服务发现。最后,服务提供者便可以根据从注册中心获取的服务信息来实现对服务提供者的调用。

现如今,微服务技术已经十分成熟,也随之诞生了许多优秀的开源注册中心,如Zookeeper、Eureka、Consul、Nacos等。接下来,笔者将主要用Nacos来搭建一个示例,从而对Nacos中服务注册的原理进行剖析,如果大家对其他注册中心感兴趣话可以自行研究~

接下来,笔者将会手把手带你完成服务中心搭建,同时构建两个微服务,用以实现服务的注册、发现和调用。

搭建注册中心,实现服务调用

Nacos服务注册中心的安装包可在Nacos官网进行下载,现在后进入对应bin目录通过 .\startup.cmd -m standalone 即可实现Nacos注册中心单机模式启动。

启动Nacos

image.png

访问控制台对应的:8848即可进入到Nacos的管理后台。具体如下所示:

访问Nacos后台管理

image.png

通过如上操作,我们便构建出了一个单机版的Nacos注册中心。完成注册中心的搭建后,接下来我们便开始相关服务的构建。此次,我们会通过SpringBoot项目构建一个cloud-provider来作为服务的提供者的,同时构建cloud-consumer来作为服务的消费者。

构建服务者

我们的服务者相对简单,我们仅提供一个UserController的服务层,其内部有一个getUserInfo方法可以通过传入的Id信息构建一个UserInfo对象。具体代码如下:

@RestController
@RequestMapping(("/user"))
public class UserController {
    @GetMapping("info/{id}")
    public String getUserInfo(@PathVariable("id") String id) {
        return JSONUtil.toJsonStr(new UserInfo(id,"cloud-provider"));
    }
}

构建消费者

我们的消费者通用类似,其内部也仅有一个UserConsumer的控制层,其内部会通过调用消费者提供的user/info路径,进而获取到服务者返回的用户信息,其代码如下:

_服务注册框架_服务注册有什么用

@RestController
@RequestMapping("userConsumers")
public class UserConsumer {
    @Autowired
    private RestTemplate restTemplate;
    @GetMapping("info/{id}")
    public ResponseEntity getUserInfo(@PathVariable("id") String id){
       return restTemplate.getForEntity("http://cloud-provider/user/info/" + id, String.class);
    }
}

整个项目我们通过Maven父子结构来进行搭建,服务者和消费者搭建完毕后的效果如下图所示。

image.png

整个项目中所用到的依赖如下:

<dependencies>
  <dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-dependenciesartifactId>
    <version>${spring-boot.version}version>
    <type>pomtype>
    <scope>importscope>
  dependency>
  <dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-dependenciesartifactId>
    <version>${spring-cloud.version}version>
    <type>pomtype>
    <scope>importscope>
  dependency>
    
  <dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-bootstrapartifactId>
    <version>${spring-cloud-bootstrap}version>
  dependency>
    
  <dependency>
    <groupId>cn.hutoolgroupId>
    <artifactId>hutool-allartifactId>
    <version>5.5.0version>
  dependency>
dependencies>

服务注册及调用

此时,我们分别启动SpringCloudProvider和SpringCloudConsumer来完成服务提供者和消费者的启动,启动后访问我们之前的Nacos管理页面,可以看到相关的服务已成功注册到Nacos上。

image.png

进一步,当我们调用服务提供者cloud-consumer中的userconsumers/info时,可以看到其成功请求到cloud-provider中所提供的接口路径,并成功返回相关内容信息。

image.png

如上,我们便从零开始搭建出了Nacos注册中心,同时利用SpringBoot构建出了cloud-provider和cluod-consumer两个微服务,并完成了两个服务间的通信。

作为一名开发者,单单会构建一个服务,并实现服务注册其实是远远不够的。接下来,我们便对Nacos服务注册原理进行深入剖析。

Ps: 如下内容可能需要对SpringBoot装配原理有一定基础,对此部分不了解的读者,可建议先补齐相关知识后~

Nacos服务注册原理

众所周知,当我们为项目引入Nacos-discovery后,其后自动注入一个名为NacosServiceRegistryAutoConfiguration的配置类。该类源码如下:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
      matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
      AutoServiceRegistrationAutoConfiguration.class,
      NacosDiscoveryAutoConfiguration.class })
public class NacosServiceRegistryAutoConfiguration {
   @Bean
   public NacosServiceRegistry nacosServiceRegistry(
         NacosServiceManager nacosServiceManager,
         NacosDiscoveryProperties nacosDiscoveryProperties) {
      return new NacosServiceRegistry(nacosServiceManager, nacosDiscoveryProperties);
   }
   @Bean
   @ConditionalOnBean(AutoServiceRegistrationProperties.class)
   public NacosRegistration nacosRegistration(
         ObjectProvider> registrationCustomizers,
         NacosDiscoveryProperties nacosDiscoveryProperties,
         ApplicationContext context) {
      return new NacosRegistration(registrationCustomizers.getIfAvailable(),
            nacosDiscoveryProperties, context);
   }
   @Bean
   @ConditionalOnBean(AutoServiceRegistrationProperties.class)
   public NacosAutoServiceRegistration nacosAutoServiceRegistration(
         NacosServiceRegistry registry,
         AutoServiceRegistrationProperties autoServiceRegistrationProperties,
         NacosRegistration registration) {
      return new NacosAutoServiceRegistration(registry,
            autoServiceRegistrationProperties, registration);
   }
}

通过上述代码,我们可以看到NacosServiceRegistryAutoConfiguration作为Nacos 服务注册的自动装配器。其内部会向Spring容器中分别注入如下三个Bean对象:

NacosAutoServiceRegistration:其是一个自动化服务注册类,主要用于简化服务的注册过程。它会在Spring Boot应用启动时自动注册服务到 Nacos。

NacosRegistration:其封装了服务的元数据(如服务名、实例 ID、主机地址等)。它代表了在Nacos中注册的具体服务实例。

NacosServiceRegistry:作为Nacos的核心服务注册类,主要负责与Nacos Server 进行通信,处理实际的服务注册、更新和注销操作。

总结来看,NacosAutoServiceRegistration 是启动时自动进行服务注册的入口,负责创建 NacosRegistration实例。而 NacosRegistration 封装了服务实例的详细信息,并被 NacosServiceRegistry 用于实际的注册过程。NacosServiceRegistry 则是与 Nacos服务器交互的主要组件,处理所有与服务注册相关的操作。通过这三个组件的协作Nacos才能够实现高效的服务注册和发现机制。如下这张图便准确的反映了NacosAutoServiceRegistration、NacosRegistration、NacosServiceRegistry三者间的关系。

image.png

经过上述分析,我们知道对于Nacos客户端服务的注册,主要通过NacosServiceRegistry这一对象来实现。更进一步,其实是通过内部的registry方法来实现。

NacosServiceRegistry#registry()

public void register(Registration registration) {
   
   // 获取服务属性信息
   NamingService namingService = namingService();
   String serviceId = registration.getServiceId();
   String group = nacosDiscoveryProperties.getGroup();
  // 构建服务实例
   Instance instance = getNacosInstanceFromRegistration(registration);
   // 服务注册
   namingService.registerInstance(serviceId, group, instance);
   // ....省略其他无关代码
}

不难发现,NacosServiceRegistry的register方法其实主要完成两件事。一件是获取服务的配置,并构建一个服务实例对象。另一件则是将构建的服务实例对象通过registerInstance进行注册。而registerInstance的内部逻辑如下: