nacos启动失败_Nacos-分布式注册中心

0efaff6d4a7c1372fd1aef55c7b9c1e5.png

什么是 Nacos?

阿里出的分布式注册中心。

CAP理论:

  • 一致性(Consistency) (所有节点在同一时间具有相同的数据)
  • 可用性(Availability) (保证每个请求不管成功或者失败都有响应)
  • 分隔容忍--分区容错性(Partition tolerance) (系统中任意信息的丢失或失败不会影响系统的继续运作)

b3fce5e67f734d7225f4ee57d140d971.png
注册中心对比图

Nacos简介:

服务(Service)是 Nacos 世界的一等公民。Nacos 支持几乎所有主流类型的“服务”的发现、配置和管理:

  • Kubernetes Service
  • gRPC & Dubbo RPC Service
  • Spring Cloud RESTful Service

Nacos 的关键特性包括:

  • 服务发现和服务健康监测
  • 动态配置服务
  • 动态 DNS 服务
  • 服务及其元数据管理

dbb3762ce620dabb41d0a1f04c2f096d.png
Nacos 地图

2e7aef57495ed4632a39b670d578becd.png

Nacos服务领域模型主要分为命名空间、集群、服务。在下图的分级存储模型可以看到,在服务级别,保存了健康检查开关、元数据、路由机制、保护阈值等设置,而集群保存了健康检查模式、元数据、同步机制等数据,实例保存了该实例的ip、端口、权重、健康检查状态、下线状态、元数据、响应时间。

337ddce9e2519b91eec200d76b088469.png

868a0c34a3bce0ad6b41ee2a6a32c9fd.png

注册中心原理:

318f234ba27debadca7509ada7177516.png
服务注册方法:服务注册的策略的是每5秒向nacos server发送一次心跳,心跳带上了服务名,服务ip,服务端口等信息。同时 nacos server也会向client 主动发起健康检查,支持tcp/http检查。如果15秒内无心跳且健康检查失败则认为实例不健康,如果30秒内健康检查失败则剔除实例。

配置中心原理:

0833c24c8b76c7ec37bcc859c3cc0aad.png

Nacos使用方法:

  • 快速搭建:查看官方文档,路径在最底部 ,下个zip包执行 -。-~~
  • 默认控制台地址:http://localhost:8848/nacos/#
  • 默认账号/密码 nacos/nacos

创建命名空间:

不同的命名空间逻辑上是隔离的,不特殊设置的情况下,服务不会跨命名空间请求,命名空间主要的作用是区分服务使用的范围,比如开发、测试、生产、灰度可以分别设置四个命名空间来互相隔离。如下图,在控制台的 服务管理-命名空间-新建命名空间按钮可以创建新的命名空间,命名空间创建后,会在列表显示 命名空间ID,这个ID后面会用在服务的配置文件中

219c823fbd613f75dd57602fa3100e55.png

以springcloud为例,pom导入依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

启动类需要加上@EnableDiscoveryClient注解:

66f97234fae8e184f1d72b17942d4fff.png

配置文件bootstrap.yml 注意这里不要配置在application.yml/properties中 有加载顺序原因。

spring:
  application:
    name: nacos-test
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        group: test
        namespace: 命名空间ID

通过配置更改动态刷新参数:

@RefreshScope
public class TestController {
    @Value("${value}")
    private String value;
    public int getValue(){
        return this.value;
    }

普通application参数在配置中心直接配置皆可,如果需要可以动态刷新的配置,需要在相应类上加上@RefreshScope注解。

配置中心参数修改/设置:

在nacos控制台的 配置管理-配置列表中顶部选择相应的命名空间,点击列表右上角的加号新增配置,Data ID 为 项目名-{spring.profiles.active}.properties,Group如果在bootstrap.properties中不指定则填默认的DEFAULT_GROUP,描述写该配置的描述,配置内容填写Properties格式或者Yaml格式。

控制台手动上下线实例:

在控制台的 服务管理-服务列表选择一个服务点击详情,在下方的集群列表可以看到有上线/下线按钮,点击即可以对该实例执行上线/下线操作,下线后的实例不会被请求

配置实例权重:

可以通过手动配置权重来控制流量,当一个集群内两个实例,权重越高,到达该实例的请求比例越多。

配置保护阈值:

保护阈值的范围是0~1
服务的健康比例=服务的健康实例/总实例个数
当服务健康比例<=保护阈值时候,无论实例健不健康都会返回给调用方
当服务健康比例>保护阈值的时候,只会返回健康实例给调用方
服务管理-服务列表选择一个服务点击详情可以配置

简单搭建好了,看看服务注册源码(版本号1.3.2):

Nacos Client端:

定位到package com.alibaba.nacos.client.naming包中NacosNamingService的registerInstance方法最底层方法实现:

   @Override
    public void registerInstance(String serviceName, String groupName, String ip, int port, String clusterName)
            throws NacosException {
        
        Instance instance = new Instance();
        instance.setIp(ip);
        instance.setPort(port);
        instance.setWeight(1.0);
        instance.setClusterName(clusterName);
        
        registerInstance(serviceName, groupName, instance);
    }
这里的 registerInstance中:将ip和port放入这个 Instance对象就是一个实例,,而 serviceName以及 groupName参数是将实例注册到 Nacos中的分组名为 groupName服务名为 serviceName的服务中
   @Override
    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
        String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
        if (instance.isEphemeral()) {
            BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
            beatReactor.addBeatInfo(groupedServiceName, beatInfo);
        }
        serverProxy.registerService(groupedServiceName, groupName, instance);
    }

这里出现了BeatInfo对象,这个BeatInfo对象是Nacos用于心跳任务的心跳信息,首先要对实例进行判断是否是一个临时标签的实例对象,如果是的话需要为该Instance设置一个心跳任务信息,用于Client主动上报自己的健康状态信息。那为什么只有临时的实例才需要设置心跳任务信息呢?这里引用官方博客的解释:

如果是临时实例,则不会在Nacos服务端持久化存储,需要通过上报心跳的方式进行保活,如果一段时间内没有上报心跳,则会被Nacos服务端摘除。在被摘除后如果又开始上报心跳,则会重新将这个实例注册。持久化实例则会持久化到Nacos服务端,此时即使注册实例的客户端进程不在,这个实例也不会从服务端删除,只会将健康状态设为不健康

由于临时的实例不会在Nacos服务中心进行持久化存储,因此才需要一个心跳任务,将实例的信息放在心跳任务中不断的向Nacos服务中心上报。接着就是向Nacos Server段发送注册实例的Http请求了,在package http://com.alibaba.nacos.client.naming.net中的NamingProxy类registerService方法:

  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>(16);
        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", JacksonUtils.toJson(instance.getMetadata()));
        
        reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
        
    }

这个registerService就是将Instance实例的信息封装成Http的请求进行发送到Nacos Server端进行注册,下面就是真正注册的方法了。

public String reqApi(String api, Map<String, String> params, Map<String, String> body, List<String> servers,
            String method) throws NacosException {
        
        params.put(CommonParams.NAMESPACE_ID, getNamespaceId());
        
        if (CollectionUtils.isEmpty(servers) && StringUtils.isEmpty(nacosDomain)) {
            throw new NacosException(NacosException.INVALID_PARAM, "no server available");
        }
        
        NacosException exception = new NacosException();
        
        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, body, server, method);
                } catch (NacosException e) {
                    exception = e;
                    if (NAMING_LOGGER.isDebugEnabled()) {
                        NAMING_LOGGER.debug("request {} failed.", server, e);
                    }
                }
                index = (index + 1) % servers.size();
            }
        }
        
        if (StringUtils.isNotBlank(nacosDomain)) {
            for (int i = 0; i < UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT; i++) {
                try {
                    return callServer(api, params, body, nacosDomain, method);
                } catch (NacosException e) {
                    exception = e;
                    if (NAMING_LOGGER.isDebugEnabled()) {
                        NAMING_LOGGER.debug("request {} failed.", nacosDomain, e);
                    }
                }
            }
        }
        
        NAMING_LOGGER.error("request: {} failed, servers: {}, code: {}, msg: {}", api, servers, exception.getErrCode(),
                exception.getErrMsg());
        
        throw new NacosException(exception.getErrCode(),
                "failed to req API:" + api + " after all servers(" + servers + ") tried: " + exception.getMessage());
        
    }
可以看到如果 Nacos Server端的地址列表为空,那么 Nacos Server应该是单机模式部署的,因此直接到最后一个for循环,循环次数为默认设置的 Http请求可重试次数;如果 Nacos Serve是已集群模式部署的话,那么会采用随机策略选择一个 Nacos Server Addr作为进行 Instance注册的 Http请求地址;如果请求失败的话则再次重新选取一个 Nacos Server

Nacos Server端:

Nacos Server端负责处理Nacos Client的实例注册的Controllercom.alibaba.nacos.naming.controllers里面的InstanceController,可以找到处理Instance注册的控制器

   @CanDistro
    @PostMapping
    @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
    public String register(HttpServletRequest request) throws Exception {
        
        final String namespaceId = WebUtils
                .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
        final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
        checkServiceNameFormat(serviceName);
        
        final Instance instance = parseInstance(request);
        
        serviceManager.registerInstance(namespaceId, serviceName, instance);
        return "ok";
    }

com.alibaba.nacos.naming.core包中ServiceManager类的registerInstance方法:

 public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
        
        createEmptyService(namespaceId, serviceName, instance.isEphemeral());
        
        Service service = getService(namespaceId, serviceName);
        
        if (service == null) {
            throw new NacosException(NacosException.INVALID_PARAM,
                    "service not found, namespace: " + namespaceId + ", service: " + serviceName);
        }
        
        addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
    }

NacosModelservice->cluster->instance这样的,因此在注册实例时,需要先注册一个Service,具体的创建在createEmptyService方法,该方法底层为createServiceIfAbsent方法:

 public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
            throws NacosException {
        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);
            }
        }
    }
这里会先根据 namespaceId以及 serviceName去获取一个 Service对象,如果 service==null,则代表该 Service未在 Nacos Server端注册,因此需要先注册一个 Service,才可以进行接下来的注册 Instance;同时根据实例是否是临时的标识决定该实例是否需要持久化到 Nacos Server中,如果是一个临时的 Instance,则 Nacos Server会为其设置一致性 consistencyServicelistener,同时 Service开启一个 ClientBeatCheckTask,用于检查该 Service下的 Instance实例的健康状态。

Service创建之后就是注册Instance

public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
            throws NacosException {
        
        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);
        }
    }

先读到这里了

参考资料:

home​nacos.io
0c82e0f229e4fa220ecccded420455c6.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值