目前最火的服务注册中心+配置中心,阿里开源,真香。。(代码片段)

Java技术栈 Java技术栈     2023-02-19     659

关键词:

来源:cnblogs.com/wuzhenzhao/p/13625491.html

Nacos 是目前国内非常火的一个服务注册与发现的中间件,有不少公司都在采用 Nacos,因此面试中被问到的概率也是非常高的!

Nacos 服务注册需要具备的能力:

  • 服务提供者把自己的协议地址注册到Nacos server
  • 服务消费者需要从Nacos Server上去查询服务提供者的地址(根据服务名称)
  • Nacos Server需要感知到服务提供者的上下线的变化
  • 服务消费者需要动态感知到Nacos Server端服务地址的变化

作为注册中心所需要的能力大多如此,我们需要做的是理解各种注册中心的独有特性,总结他们的共性。

Nacos的实现原理:

下面我们先来了解一下 Nacos 注册中心的实现原理,通过下面这张图来说明。

图中的流程是大家所熟悉的,不同的是在Nacos 中,服务注册时在服务端本地会通过轮询注册中心集群节点地址进行服务得注册,在注册中心上,即Nacos Server上采用了Map保存实例信息,当然配置了持久化的服务会被保存到数据库中,在服务的调用方,为了保证本地服务实例列表的动态感知,Nacos与其他注册中心不同的是,采用了 Pull/Push同时运作的方式。通过这些我们对Nacos注册中心的原理有了一定的了解。我们从源码层面去验证这些理论知识。

Nacos的源码分析

结合spring-cloud-alibaba +dubbo +nacos 的整合。

「服务注册的流程:」

在基于Dubbo服务发布的过程中, 自动装配是走的事件监听机制,在 DubboServiceRegistrationNonWebApplicationAutoConfiguration 这个类中,这个类会监听 ApplicationStartedEvent 事件,这个事件是spring boot在2.0新增的,就是当spring boot应用启动完成之后会发布这个时间。而此时监听到这个事件之后,会触发注册的动作。

推荐一个 Spring Boot 基础教程及实战示例: https://github.com/javastacks/spring-boot-best-practice

@EventListener(ApplicationStartedEvent.class)
public void onApplicationStarted() 
    setServerPort();
    register();


private void register() 
    if (registered) 
        return;
    
    serviceRegistry.register(registration);
    registered = true;

serviceRegistry是 spring-cloud 提供的接口实现(org.springframework.cloud.client.serviceregistry.ServiceRegistry),很显然注入的实例是:NacosServiceRegistry。

然后进入到实现类的注册方法:

@Override
public void register(Registration registration) 

    if (StringUtils.isEmpty(registration.getServiceId())) 
        log.warn("No service to register for nacos client...");
        return;
    
    //对应当前应用的application.name
    String serviceId = registration.getServiceId();
    //表示nacos上的分组配置
    String group = nacosDiscoveryProperties.getGroup();
    //表示服务实例信息
    Instance instance = getNacosInstanceFromRegistration(registration);

    try 
        //通过命名服务进行注册
        namingService.registerInstance(serviceId, group, instance);
        log.info("nacos registry,   : register finished", group, serviceId,
                instance.getIp(), instance.getPort());
    
    catch (Exception e) 
        log.error("nacos registry,  register failed...,", serviceId,
                registration.toString(), e);
        // rethrow a RuntimeException if the registration is failed.
        // issue : https://github.com/alibaba/spring-cloud-alibaba/issues/1132
        rethrowRuntimeException(e);
    

接下去就是开始注册实例,主要做两个动作

  1. 如果当前注册的是临时节点,则构建心跳信息,通过beat反应堆来构建心跳任务
  2. 调用registerService发起服务注册
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException 
        是否是临时节点,如果是临时节点,则构建心跳信息
        if (instance.isEphemeral()) 
            BeatInfo beatInfo = new BeatInfo();
            beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
            beatInfo.setIp(instance.getIp());
            beatInfo.setPort(instance.getPort());
            beatInfo.setCluster(instance.getClusterName());
            beatInfo.setWeight(instance.getWeight());
            beatInfo.setMetadata(instance.getMetadata());
            beatInfo.setScheduled(false);

            //beatReactor, 添加心跳信息进行处理
        beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
        
          //调用服务代理类进行注册
          serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);

然后调用 NamingProxy 的注册方法进行注册,代码逻辑很简单,构建请求参数,发起请求。

public void registerService(String serviceName, String groupName, Instance instance) throws NacosException 

    NAMING_LOGGER.info("[REGISTER-SERVICE]  registering service  with instance: ",
        namespaceId, serviceName, instance);

    final Map<String, String> params = new HashMap<String, String>(8);
    params.put(CommonParams.NAMESPACE_ID, namespaceId);
    params.put(CommonParams.SERVICE_NAME, serviceName);
    params.put(CommonParams.GROUP_NAME, groupName);
    params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
    params.put("ip", instance.getIp());
    params.put("port", String.valueOf(instance.getPort()));
    params.put("weight", String.valueOf(instance.getWeight()));
    params.put("enable", String.valueOf(instance.isEnabled()));
    params.put("healthy", String.valueOf(instance.isHealthy()));
    params.put("ephemeral", String.valueOf(instance.isEphemeral()));
    params.put("metadata", JSON.toJSONString(instance.getMetadata()));

    reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);

往下走我们就会发现上面提到的,服务在进行注册的时候会轮询配置好的注册中心的地址:

public String reqAPI(String api, Map<String, String> params, List<String> servers, String method) 

        params.put(CommonParams.NAMESPACE_ID, getNamespaceId());

        if (CollectionUtils.isEmpty(servers) && StringUtils.isEmpty(nacosDomain)) 
            throw new IllegalArgumentException("no server available");
        

        Exception exception = new Exception();
        //如果服务地址不为空
        if (servers != null && !servers.isEmpty()) 
            //随机获取一台服务器节点
            Random random = new Random(System.currentTimeMillis());
            int index = random.nextInt(servers.size());
            // 遍历服务列表
            for (int i = 0; i < servers.size(); i++) 
                String server = servers.get(index);//获得索引位置的服务节点
                try //调用指定服务
                    return callServer(api, params, server, method);
                 catch (NacosException e) 
                    exception = e;
                    NAMING_LOGGER.error("request  failed.", server, e);
                 catch (Exception e) 
                    exception = e;
                    NAMING_LOGGER.error("request  failed.", server, e);
                
               //轮询
                index = (index + 1) % servers.size();
            
       // ..........

最后通过 callServer(api, params, server, method) 发起调用,这里通过 JSK自带的 HttpURLConnection 进行发起调用。我们可以通过断点的方式来看到这里的请求参数:

期间可能会有多个 GET 的请求获取服务列表,是正常的,会发现有如上的一个请求,会调用 http://192.168.200.1:8848/nacos/v1/ns/instance 这个地址。那么接下去就是Nacos Server 接受到服务端的注册请求的处理流程。需要下载Nacos Server 源码,源码下载可以参考官方文档,本文不做过多阐述。

「Nacos服务端的处理:」

服务端提供了一个InstanceController类,在这个类中提供了服务注册相关的API,而服务端发起注册时,调用的接口是:[post]: /nacos/v1/ns/instance ,serviceName: 代表客户端的项目名称 ,namespace: nacos 的namespace。

@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception 

        final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
        final String namespaceId = WebUtils
                .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
        // 从请求中解析出instance实例
        final Instance instance = parseInstance(request);

        serviceManager.registerInstance(namespaceId, serviceName, instance);
        return "ok";

然后调用 ServiceManager 进行服务的注册

public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException 
        //创建一个空服务,在Nacos控制台服务列表展示的服务信息,实际上是初始化一个serviceMap,它是一个ConcurrentHashMap集合
        createEmptyService(namespaceId, serviceName, instance.isEphemeral());
        //从serviceMap中,根据namespaceId和serviceName得到一个服务对象
        Service service = getService(namespaceId, serviceName);

        if (service == null) 
            throw new NacosException(NacosException.INVALID_PARAM,
                    "service not found, namespace: " + namespaceId + ", service: " + serviceName);
        
        //调用addInstance创建一个服务实例
        addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);

在创建空的服务实例的时候我们发现了存储实例的map:

public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
            throws NacosException 
        //从serviceMap中获取服务对象
        Service service = getService(namespaceId, serviceName);
        if (service == null) //如果为空。则初始化
      Loggers.SRV\\_LOG.info("creating empty service :", namespaceId, serviceName);
      service = new Service();
      service.setName(serviceName);
      service.setNamespaceId(namespaceId);
      service.setGroupName(NamingUtils.getGroupName(serviceName));
      // now validate the service. if failed, exception will be thrown
      service.setLastModifiedMillis(System.currentTimeMillis());
      service.recalculateChecksum();
      if (cluster != null) 
          cluster.setService(service);
          service.getClusterMap().put(cluster.getName(), cluster);
      
      service.validate();
      putServiceAndInit(service);
      if (!local) 
          addOrReplaceService(service);
      

在 getService 方法中我们发现了Map:

/*
* Map(namespace, Map(group::serviceName, Service)).
*/
private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

通过注释我们可以知道,Nacos是通过不同的 namespace 来维护服务的,而每个namespace下有不同的group,不同的group下才有对应的Service ,再通过这个 serviceName 来确定服务实例。

第一次进来则会进入初始化,初始化完会调用 putServiceAndInit

private void putServiceAndInit(Service service) throws NacosException 
    putService(service);//把服务信息保存到serviceMap集合
    service.init();//建立心跳检测机制
    //实现数据一致性监听,ephemeral(标识服务是否为临时服务,默认是持久化的,也就是true)=true表示采用raft协议,false表示采用Distro
    consistencyService
            .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);
    consistencyService
            .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);
    Loggers.SRV_LOG.info("[NEW-SERVICE] ", service.toJson());

获取到服务以后把服务实例添加到集合中,然后基于一致性协议进行数据的同步。然后调用 addInstance

public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
        throws NacosException 
    // 组装key
    String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
    // 获取刚刚组装的服务
    Service service = getService(namespaceId, serviceName);

    synchronized (service) 
        List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);

        Instances instances = new Instances();
        instances.setInstanceList(instanceList);
        // 也就是上一步实现监听的类里添加注册服务
        consistencyService.put(key, instances);
    

然后给服务注册方发送注册成功的响应,结束服务注册流程。以上内容,希望大家有一个大概的认识,收藏起来,后面慢慢多看几次,牢记心中,面试中肯定是加分项。

近期热文推荐:

1.1,000+ 道 Java面试题及答案整理(2021最新版)

2.别在再满屏的 if/ else 了,试试策略模式,真香!!

3.卧槽!Java 中的 xx ≠ null 是什么新语法?

4.Spring Boot 2.6 正式发布,一大波新特性。。

5.《Java开发手册(嵩山版)》最新发布,速速下载!

觉得不错,别忘了随手点赞+转发哦!

微服务-springcloud学习系列:注册中心consul

因为Eureka目前开源版本1.0不再更新(2.0版本没有开源),可以考虑使用其他开源的注册中心替代。1.下载安装Consul的服务端程序启动服务端,访问管理界面http://127.0.0.1:8500通过postman测试Consul提供的httpAPI2.将服务注册到Consul①添加... 查看详情

nacos——服务注册和发现中心配置中心(代码片段)

Nacos——服务注册和发现中心、配置中心Nacos是现在比较火的一款动态服务发现、管理和配置中心,配合一些云原生的组件很方便,下面简单介绍一下nacos和springcloud的使用。1.启动NacosServer服务下载源码或者安装包你可以通过源码... 查看详情

nacos真香,从零到一学起来(代码片段)

Nacos是阿里巴巴开源的微服务管理平台,可以帮助开发者快速实现动态服务发现、服务配置、服务元数据及流量管理。这篇文章主要来讲一下Nacos作为配置中心和注册中心的使用。1安装1.1linux下安装首先搭建一套单机版的Nacos... 查看详情

springboot整合nacos注册中心

...社区和超高的性能吸引了很多开发者和公司的青睐,笔者目前了解到的注册中心框架有Eureka、Consul、Nacos和ZK,这几种框架都各具特色,可根据公司业务和成本来具体选型。首先来看一下,SpringCloud、SpringCloudAlibaba和SpringBoot版本... 查看详情

springboot使用nacos配置中心

...cos是阿里巴巴集团开源的一个易于使用的平台,专为动态服务发现,配置和服务管理而设计。它可以帮助您轻松构建云本机应用程序和微服务平台。Nacos基本上支持现在所有类型的服务,例如,Dubbo/gRPC服务,SpringCloudRESTFul服务或... 查看详情

微服务架构中主流的配置中心对比分析?

...也是配置中心不可获取的一部分。开源配置中心基本介绍目前市面上用的比较多的配置中心有:(按开源时间排序)Disconf2014年7月百度开源的配置管理中心,同样具备配置的管理能力,不过目前已经不维护了,最近的一次提交是... 查看详情

nacos:服务注册及配置中心(代码片段)

SpringCloudAlibabaNacos:服务注册及配置中心一、Nacos简介1.Nacos简介​Nacos是由阿里巴巴提供的一款专门构建云本地应用的动态服务发现、配置中心和服务管理平台。在SpringCloudAlibaba中常使用Nacos作为注册中心和分布式配置中心。​Nac... 查看详情

使用springcloud搭建服务注册中心

我们在之前的博客中已经介绍过阿里的分布式服务框架dubbo【Linux上安装Zookeeper以及一些注意事项】【一个简单的案例带你入门Dubbo分布式框架】,但是小伙伴们应该也看到了,阿里的dubbo无法单独完成工作,我们还要借助于Apache... 查看详情

disconf原理及分布式配置中心的一般实现思路

...开发一套配置中心,所以就打算借助于开源的力量来满足目前的使用场景。因为现在的配置中心还是有一些开源实现的。像百度的Disconf,阿里的Diamond,携程的Apollo,还有基于Github的pull模式来实现。我为什么选择Disconf,主要有下面几个... 查看详情

献给nacos小白的一篇好文:注册中心

服务注册中心随着微服务的发展,出现了各种各样的服务注册与发现组件,市面使用较多的有eureka、consul、etcd、zookeeper以及本篇的主角nacos等。对于这些组件的优缺点及区别,网络上有很多说法,读者可自行网络... 查看详情

springcloud怎么使用nacos做注册中心+配置中心?

Nacos是阿里生态下,用于微服务注册与发现、配置管理实时刷新的微服务平台.默认使用嵌入式持久化,可支持MySQL数据库持久化安装Nacos服务1.下载nacos服务版本1.3.2Windows下下载zip包并解压2.可使用默认方式持久化,跳过此步骤.如果使... 查看详情

一、nacos介绍

...可用,2.0版本接入k8s、SpringCloud、ServiceMesh、ServerLess公司目前的项目都是Springcloud,由于eureka2.X的断更、以及Nacos面世,所以自然而然最近就进行了一次试水爬坑,虽然过程艰苦,但是最终效果似乎还不错。本文主要从以下几点来... 查看详情

rnacos——用rust重新实现的nacos开源配置注册中心服务(代码片段)

...定先写一个最小功能集给自己用。rnacos由此而开发出来。目前rnacos的资源占用情况rnacos打包成原生应用,不依赖其它组件,支持多平台。应用大小11M左右,压缩包不到5M,docker压缩包9.4M秒启动 查看详情

java基础学习总结(181)——nacosapolloconfig配置中心如何选型?

...配置中心不可获取的一部分。2、开源配置中心基本介绍目前市面上用的比较多的配置中心有:(按开源时间排序)Disconf2014年7月百度开源的配置管理中心,同样具备配置的管理能力,不过目前已经不维护了,最近的一次提交是... 查看详情

springcloudalibaba——服务注册与配置中心

...赞👍评论收藏⭐️👀专栏介绍【秒懂·云原生】目前主要更新微服务,一起学习一起进步。👀本期介绍主要介绍SpringCloudAlibaba——服务注册与配置中心 查看详情

.netcorewith微服务-consul配置中心

上一次我们介绍了ElasticAPM组件。这一次我们继续介绍微服务相关组件配置中心的使用方法。本来打算介绍下携程开源的重型配置中心框架apollo但是体系实在是太过于庞大,还是让我爱不起来。因为前面我们已经介绍了使用Con... 查看详情

springboot集成apollo配置中心,真香真强大!(代码片段)

...,程序的配置日益增多,各种功能的开关、参数的配置、服务器的地址……对程序配置的期望值也越来越高,配置修改后实时生效,灰度发布,分环境、分集群管理配置,完善的权限、审核机制……在这样的大环境下,传统的 查看详情

springcloud整合alibaba的nacos实现服务注册和配置中心

在微服务架构中,服务注册与发现、以及配置中心都是非常重要的组件,而Nacos正是一个可以同时实现服务注册发现和配置中心的解决方案。本篇博客将会介绍如何使用SpringCloud和Nacos来实现服务注册和配置中心的整合。1.概述首... 查看详情