Nacos架构与原理 - 寻址机制

news2024/11/22 17:06:41

文章目录

  • 前提
  • 设计
  • MemberLookup
  • 内部实现
    • 单机寻址 StandaloneMemberLookup
    • 文件寻址 FileConfigMemberLookup
    • 地址服务器寻址 AddressServerMemberLookup
  • 未来可扩展点

在这里插入图片描述


前提

Nacos 支持单机部署以及集群部署

  • 针对单机模式,Nacos 只是自己和自己通信;
  • 对于集群模式,则集群内的每个 Nacos 成员都需要相互通信。

因此这就带来⼀个问题,该以何种方式去管理集群内的 Nacos 成员节点信息,而这,就是 Nacos 内部的寻址机制。


设计

无论是单机模式,还是集群模式,其根本区别只是 Nacos 成员节点的个数是单个还是多个

  • 要能够感知到节点的变更情况:节点是增加了还是减少了;

  • 当前最新的成员列表信息是什么;

  • 以何种方式去管理成员列表信息;

  • 如何快速的支持新的、更优秀的成员列表管理模式等等。


MemberLookup

针对上述需求点,抽象出了⼀个 MemberLookup 接口

在这里插入图片描述

package com.alibaba.nacos.core.cluster;

import com.alibaba.nacos.api.exception.NacosException;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;

/**
 * Member node addressing mode.
 *
 * @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
 */
public interface MemberLookup {
    
    /**
     * start.
     *
     * @throws NacosException NacosException
     */
    void start() throws NacosException;
    
    /**
     * is using address server.
     *
     * @return using address server or not.
     */
    boolean useAddressServer();
    
    /**
     * Inject the ServerMemberManager property.
     *
     * @param memberManager {@link ServerMemberManager}
     */
    void injectMemberManager(ServerMemberManager memberManager);
    
    /**
     * The addressing pattern finds cluster nodes.
     *
     * @param members {@link Collection}
     */
    void afterLookup(Collection<Member> members);
    
    /**
     * Addressing mode closed.
     *
     * @throws NacosException NacosException
     */
    void destroy() throws NacosException;
    
    /**
     * Some data information about the addressing pattern.
     *
     * @return {@link Map}
     */
    default Map<String, Object> info() {
        return Collections.emptyMap();
    }
    
}

  • ServerMemberManager 存储着本节点所知道的所有成员节点列表信息,提供了针对成员节点的增删改查操作,同时维护了⼀个 MemberLookup 列表,方便进行动态切换成员节点寻址方式。

  • MemberLookup 接口非常简单,核心接口就两个— injectMemberManager 以及afterLookup ,前者用于将 ServerMemberManager 注入到 MemberLookup 中,方便利用ServerMemberManager 的存储、查询能力,后者 afterLookup 则是⼀个事件接口,当 MemberLookup 需要进行成员节点信息更新时,会将当前最新的成员节点列表信息通过该函数进行通知给ServerMemberManager,具体的节点管理方式,则是隐藏到具体的 MemberLookup 实现中。


内部实现

在这里插入图片描述

单机寻址 StandaloneMemberLookup

单机模式的寻址模式很简单,其实就是找到自己的 IP:PORT 组合信息,然后格式化为⼀个节点信息,调用 afterLookup 然后将信息存储到 ServerMemberManager 中。

package com.alibaba.nacos.core.cluster.lookup;

import com.alibaba.nacos.core.cluster.AbstractMemberLookup;
import com.alibaba.nacos.core.cluster.MemberUtil;
import com.alibaba.nacos.sys.env.EnvUtil;
import com.alibaba.nacos.sys.utils.InetUtils;

import java.util.Collections;

/**
 * Member node addressing mode in stand-alone mode.
 *
 * @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
 */
public class StandaloneMemberLookup extends AbstractMemberLookup {
    
    @Override
    public void doStart() {
        String url = InetUtils.getSelfIP() + ":" + EnvUtil.getPort();
        afterLookup(MemberUtil.readServerConf(Collections.singletonList(url)));
    }
    
    @Override
    public boolean useAddressServer() {
        return false;
    }
}


文件寻址 FileConfigMemberLookup

文件寻址模式是 Nacos 集群模式下的默认寻址实现

文件寻址模式很简单,其实就是每个 Nacos节点需要维护⼀个叫做 cluster.conf 的文件。

192.168.16.101:8847
192.168.16.102
192.168.16.103

该文件默认只需要填写每个成员节点的 IP 信息即可,端口会自动选择 Nacos 的默认端口 8848,如过说有特殊需求更改了 Nacos 的端口信息,则需要在该文件将该节点的完整网路地址信息补充完整(IP:PORT)。

当 Nacos 节点启动时,会读取该文件的内容,然后将文件内的 IP 解析为节点列表,调用 afterLookup 存入 ServerMemberManager


/**
 * Cluster.conf file managed cluster member node addressing pattern.
 *
 * @author <a href="mailto:liaochuntao@live.com">liaochuntao</a>
 */
public class FileConfigMemberLookup extends AbstractMemberLookup {
    
    private static final String DEFAULT_SEARCH_SEQ = "cluster.conf";
    
    private FileWatcher watcher = new FileWatcher() {
        @Override
        public void onChange(FileChangeEvent event) {
            readClusterConfFromDisk();
        }
        
        @Override
        public boolean interest(String context) {
            return StringUtils.contains(context, DEFAULT_SEARCH_SEQ);
        }
    };
    
    @Override
    public void doStart() throws NacosException {
        readClusterConfFromDisk();
        
        // Use the inotify mechanism to monitor file changes and automatically
        // trigger the reading of cluster.conf
        try {
            WatchFileCenter.registerWatcher(EnvUtil.getConfPath(), watcher);
        } catch (Throwable e) {
            Loggers.CLUSTER.error("An exception occurred in the launch file monitor : {}", e.getMessage());
        }
    }
    
    @Override
    public boolean useAddressServer() {
        return false;
    }
    
    @Override
    public void destroy() throws NacosException {
        WatchFileCenter.deregisterWatcher(EnvUtil.getConfPath(), watcher);
    }
    
    private void readClusterConfFromDisk() {
        Collection<Member> tmpMembers = new ArrayList<>();
        try {
            List<String> tmp = EnvUtil.readClusterConf();
            tmpMembers = MemberUtil.readServerConf(tmp);
        } catch (Throwable e) {
            Loggers.CLUSTER
                    .error("nacos-XXXX [serverlist] failed to get serverlist from disk!, error : {}", e.getMessage());
        }
        
        afterLookup(tmpMembers);
    }
}

如果发现集群扩缩容,那么就需要修改每个 Nacos 节点下的 cluster.conf 文件,然后 Nacos 内部的文件变动监听中心会自动发现文件修改,重新读取文件内容、加载 IP 列表信息、更新新增的节点 (FileWatcher)

但是,这种默认寻址模式有⼀个缺点——运维成本较大,可以想象下,当你新增⼀个 Nacos 节点时,需要去手动修改每个 Nacos 节点下的 cluster.conf 文件,这是多么辛苦的⼀件工作,或者稍微高端⼀点,利用 ansible 等自动化部署的工具去推送 cluster.conf 文件去代替自己的手动操作,虽然说省去了较为繁琐的人工操作步骤,但是仍旧存在⼀个问题——每⼀个 Nacos 节点都存在⼀份cluster.conf 文件,如果其中⼀个节点的 cluster.conf 文件修改失败,就造成了集群间成员节点列表数据的不⼀致性,因此,又引申出了新的寻址模式——地址服务器寻址模式


地址服务器寻址 AddressServerMemberLookup

地址服务器寻址模式是 Nacos 官方推荐的⼀种集群成员节点信息管理,该模式利用了⼀个简易的web 服务器,用于管理 cluster.conf 文件的内容信息,这样,运维人员只需要管理这⼀份集群成员节点内容即可,而每个 Nacos 成员节点,只需要向这个 web 节点定时请求当前最新的集群成员节点列表信息即可。

在这里插入图片描述

public class AddressServerMemberLookup extends AbstractMemberLookup {
    
    private final GenericType<String> genericType = new GenericType<String>() { };
    
    public String domainName;
    
    public String addressPort;
    
    public String addressUrl;
    
    public String envIdUrl;
    
    public String addressServerUrl;
    
    private volatile boolean isAddressServerHealth = true;
    
    private int addressServerFailCount = 0;
    
    private int maxFailCount = 12;
    
    private final NacosRestTemplate restTemplate = HttpClientBeanHolder.getNacosRestTemplate(Loggers.CORE);
    
    private volatile boolean shutdown = false;
    
    private static final String HEALTH_CHECK_FAIL_COUNT_PROPERTY = "maxHealthCheckFailCount";
    
    private static final String DEFAULT_HEALTH_CHECK_FAIL_COUNT = "12";
    
    private static final String DEFAULT_SERVER_DOMAIN = "jmenv.tbsite.net";
    
    private static final String DEFAULT_SERVER_POINT = "8080";
    
    private static final int DEFAULT_SERVER_RETRY_TIME = 5;
    
    private static final long DEFAULT_SYNC_TASK_DELAY_MS = 5_000L;
    
    private static final String ADDRESS_SERVER_DOMAIN_ENV = "address_server_domain";
    
    private static final String ADDRESS_SERVER_DOMAIN_PROPERTY = "address.server.domain";
    
    private static final String ADDRESS_SERVER_PORT_ENV = "address_server_port";
    
    private static final String ADDRESS_SERVER_PORT_PROPERTY = "address.server.port";
    
    private static final String ADDRESS_SERVER_URL_ENV = "address_server_url";
    
    private static final String ADDRESS_SERVER_URL_PROPERTY = "address.server.url";
    
    private static final String ADDRESS_SERVER_RETRY_PROPERTY = "nacos.core.address-server.retry";
    
    @Override
    public void doStart() throws NacosException {
        this.maxFailCount = Integer.parseInt(EnvUtil.getProperty(HEALTH_CHECK_FAIL_COUNT_PROPERTY, DEFAULT_HEALTH_CHECK_FAIL_COUNT));
        initAddressSys();
        run();
    }
    
    @Override
    public boolean useAddressServer() {
        return true;
    }
    
    private void initAddressSys() {
        String envDomainName = System.getenv(ADDRESS_SERVER_DOMAIN_ENV);
        if (StringUtils.isBlank(envDomainName)) {
            domainName = EnvUtil.getProperty(ADDRESS_SERVER_DOMAIN_PROPERTY, DEFAULT_SERVER_DOMAIN);
        } else {
            domainName = envDomainName;
        }
        String envAddressPort = System.getenv(ADDRESS_SERVER_PORT_ENV);
        if (StringUtils.isBlank(envAddressPort)) {
            addressPort = EnvUtil.getProperty(ADDRESS_SERVER_PORT_PROPERTY, DEFAULT_SERVER_POINT);
        } else {
            addressPort = envAddressPort;
        }
        String envAddressUrl = System.getenv(ADDRESS_SERVER_URL_ENV);
        if (StringUtils.isBlank(envAddressUrl)) {
            addressUrl = EnvUtil.getProperty(ADDRESS_SERVER_URL_PROPERTY, EnvUtil.getContextPath() + "/" + "serverlist");
        } else {
            addressUrl = envAddressUrl;
        }
        addressServerUrl = "http://" + domainName + ":" + addressPort + addressUrl;
        envIdUrl = "http://" + domainName + ":" + addressPort + "/env";
        
        Loggers.CORE.info("ServerListService address-server port:" + addressPort);
        Loggers.CORE.info("ADDRESS_SERVER_URL:" + addressServerUrl);
    }
    
    @SuppressWarnings("PMD.UndefineMagicConstantRule")
    private void run() throws NacosException {
        // With the address server, you need to perform a synchronous member node pull at startup
        // Repeat three times, successfully jump out
        boolean success = false;
        Throwable ex = null;
        int maxRetry = EnvUtil.getProperty(ADDRESS_SERVER_RETRY_PROPERTY, Integer.class, DEFAULT_SERVER_RETRY_TIME);
        for (int i = 0; i < maxRetry; i++) {
            try {
                syncFromAddressUrl();
                success = true;
                break;
            } catch (Throwable e) {
                ex = e;
                Loggers.CLUSTER.error("[serverlist] exception, error : {}", ExceptionUtil.getAllExceptionMsg(ex));
            }
        }
        if (!success) {
            throw new NacosException(NacosException.SERVER_ERROR, ex);
        }
        
        GlobalExecutor.scheduleByCommon(new AddressServerSyncTask(), DEFAULT_SYNC_TASK_DELAY_MS);
    }
    
    @Override
    public void destroy() throws NacosException {
        shutdown = true;
    }
    
    @Override
    public Map<String, Object> info() {
        Map<String, Object> info = new HashMap<>(4);
        info.put("addressServerHealth", isAddressServerHealth);
        info.put("addressServerUrl", addressServerUrl);
        info.put("envIdUrl", envIdUrl);
        info.put("addressServerFailCount", addressServerFailCount);
        return info;
    }
    
    private void syncFromAddressUrl() throws Exception {
        RestResult<String> result = restTemplate
                .get(addressServerUrl, Header.EMPTY, Query.EMPTY, genericType.getType());
        if (result.ok()) {
            isAddressServerHealth = true;
            Reader reader = new StringReader(result.getData());
            try {
                afterLookup(MemberUtil.readServerConf(EnvUtil.analyzeClusterConf(reader)));
            } catch (Throwable e) {
                Loggers.CLUSTER.error("[serverlist] exception for analyzeClusterConf, error : {}",
                        ExceptionUtil.getAllExceptionMsg(e));
            }
            addressServerFailCount = 0;
        } else {
            addressServerFailCount++;
            if (addressServerFailCount >= maxFailCount) {
                isAddressServerHealth = false;
            }
            Loggers.CLUSTER.error("[serverlist] failed to get serverlist, error code {}", result.getCode());
        }
    }
    
    class AddressServerSyncTask implements Runnable {
        
        @Override
        public void run() {
            if (shutdown) {
                return;
            }
            try {
                syncFromAddressUrl();
            } catch (Throwable ex) {
                addressServerFailCount++;
                if (addressServerFailCount >= maxFailCount) {
                    isAddressServerHealth = false;
                }
                Loggers.CLUSTER.error("[serverlist] exception, error : {}", ExceptionUtil.getAllExceptionMsg(ex));
            } finally {
                GlobalExecutor.scheduleByCommon(this, DEFAULT_SYNC_TASK_DELAY_MS);
            }
        }
    }
}

因此,通过地址服务器这种模式,大大简化了 Nacos 集群节点管理的成本,同时,地址服务器是⼀个非常简单的 web 程序,其程序的稳定性能够得到很好的保障。


未来可扩展点

集群节点自动扩缩容

目前,Nacos 的集群节点管理,还都是属于人工操作,因此,未来期望能够基于寻址模式,实现集群节点自动管理的功能,能够实现新的节点上线时,只需要知道原有集群中的⼀个节点信息,就可以在⼀定时间内,顺利加入原有 Nacos 集群中;同时,也能够自行发现不存活的节点,自动将其从集群可用节点列表中剔出。这⼀块的逻辑实现,其实就类似 Consul 的 Gossip 协议。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/628597.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hbase---hfile

逻辑数据组织格式 Scanned block section&#xff1a;表示顺序扫描HFile时&#xff08;包含所有需要被读取的数据&#xff09;所有的数据块将会被读取&#xff0c;包括Leaf Index Block和Bloom Block&#xff1b;Non-scanned block section&#xff1a;HFile顺序扫描的时候该…

list容器会了吗?一文搞定它

这里写目录标题 list 数据链式存储大小操作插入操作list数据存取list容器的反转和排序 list 数据链式存储 list的优点 1.采用动态存储分配&#xff0c;不会造成内存浪费和溢出 2.链表执行插入和删除操作十分简单&#xff0c;修改指针即可&#xff0c;不需要移动大量元素 缺点…

低代码开发与数智制造:数字转型的无缝结合

随着工业4.0的到来&#xff0c;数智制造已经成为制造业发展的趋势&#xff0c;而低代码开发技术则是近几年兴起的一种轻量级开发方式&#xff0c;这两者之间的结合将为制造业的数字化转型打下坚实的基础。 低代码开发平台有什么特点&#xff1f; 首先让我们来厘清一下&#xff…

STC12C5A60S2最小系统板/51单片机温度显示和温度控制风扇

STC12C5A60S2最小系统板/51单片机温度显示和温度控制风扇&#xff0c;板载有DS18B20和TM16504位数码管。 项目简介 单片机课课程设计&#xff0c;要做一个智能温控风扇&#xff0c;达到设定的下限温度值就开启风扇&#xff0c;温度在上限和下限之间就按比例输出PWM占空比控制…

基于jsp+mysql+Spring+mybatis的Springboot旅游网站管理系统

运行环境: 最好是java jdk 1.8&#xff0c;我在这个平台上运行的。其他版本理论上也可以。 IDE环境&#xff1a; Eclipse,Myeclipse,IDEA或者Spring Tool Suite都可以&#xff0c;如果编译器的版本太低&#xff0c;需要升级下编译器&#xff0c;不要弄太低的版本 tomcat服务器环…

AI回答:绩效考核有哪些方法,基本靠谱

AI回答&#xff1a;绩效考核有哪些方法&#xff0c;基本靠谱 建立有效的绩效考核机制&#xff0c;比较难 但没有考核机制&#xff0c;就无法评价优劣 趣讲大白话&#xff1a;赏罚两字&#xff0c;千古大事 【趣讲信息科技191期】 **************************** 互联网企业现在流…

11 GMM——高斯混合模型

文章目录 11 GMM——高斯混合模型11.1 模型介绍11.2 通过MLE估计参数11.3 EM求解 11 GMM——高斯混合模型 11.1 模型介绍 从几何角度来说&#xff1a; 高斯混合模型表示&#xff1a;加权平均——由多个高斯分布混合叠加而成&#xff0c;如图 公式可以表达为&#xff1a; p…

【Jmeter】提取和引用Token

1.执行获取token接口 在结果树这里&#xff0c;使用$符号提取token值。 $根节点&#xff0c;$.data.token表示提取根节点下的data节点下的token节点的值。 2.使用json提取器&#xff0c;提取token 变量路径就是把在结果树提取的路径写上。 3.使用BeanShell取样器或者BeanShell后…

(数组) 941. 有效的山脉数组 ——【Leetcode每日一题】

❓941. 有效的山脉数组 难度&#xff1a;简单 给定一个整数数组 arr&#xff0c;如果它是有效的山脉数组就返回 true&#xff0c;否则返回 false。 让我们回顾一下&#xff0c;如果 arr 满足下述条件&#xff0c;那么它是一个山脉数组&#xff1a; arr.length > 3在 0 &…

Redis指令-认识NoSQl和Redis常见的通用命令

1. 认识NoSQL 非关系型数据库 NoSQL是指一类非关系型数据库&#xff0c;它们采用的数据模型不同于传统的关系模型&#xff0c;它通常使用键值对、文档、图形等非传统的数据结构进行数据存储&#xff0c;不遵循预定义的模式和模型。NoSQL数据库通常分布式、高可扩展性&#xff0…

4.多线程之JUC并发编程1

1.List集合类不安全(以前单线程永远是安全的,多线程集合就不安全了) 都是ConcurrentModificationException并发修改异常,在有sout输出的情况下出来的,因为多线程边读边写 //并发情况下ArrayList是不安全的 可以用Vector在jdk1.0出来的,List1.2出来的加了同步锁 List listnew Ve…

瑞吉外卖业务开发

数据库 我电脑上的数据库登录指令&#xff1a;mysql -uroot -p123456常用指令&#xff1a;show databases、user 数据库名、show tables。 创建项目 创建完项目后&#xff0c;要及时检查maven仓库的配置&#xff0c;jdk的配置&#xff0c;项目的编码&#xff0c;如下图。 配…

数据同步智能化!利用ETLCloud自动化流程实现钉钉OA系统数据自动同步至数仓

钉钉数据同步需求 钉钉是一款企业级通讯和协同办公应用软件&#xff0c;钉钉为企业提供包括聊天、通讯录、日程安排、考勤打卡、审批、通知公告、文件共享、会议等功能&#xff0c;很多企业都在使用钉钉。 很多情况下我们需要把钉钉的数据拉取到数据库中&#xff0c;然后再通…

案例分享 | 汽车连接器焊锡质量检测

连接器是电脑、电视、汽车等常见产品中不可缺少的部件&#xff0c;主要由接触件、绝缘体与金属壳体组成&#xff0c;用于传输电流或信号。 汽车连接器约占全球连接器市场四分之一的份额&#xff0c;随着未来汽车产业的持续膨胀&#xff0c;市场前景广阔。连接器产品的微型化、…

RPC通用爬虫

文章目录 RPC通用爬虫一、项目案例二、Rpc原理解析三、Rpc代码分享四、自我总结 RPC通用爬虫 一、项目案例 测试网址: aHR0cDovL3d3dy5mYW5nZGkuY29tLmNuL3NlcnZpY2UvYnVsbGV0aW5fbGlzdC5odG1sP3R5cGVhPWI2N2MzYjhhZGJkY2U3NGQ 二、Rpc原理解析 图解关系: ​ 主要包括,爬虫程…

Dockerfile应用的容器化

文章目录 Dockerfile应用的容器化应用的容器化——简介应用的容器化——详解单体应用容器化获取应用代码分析Dockerfile容器化当前应用/构建具体的镜像推送镜像到仓库运行应用程序测试总结 最佳实践利用构建缓存合并镜像 命令总结 Dockerfile应用的容器化 Docker 的核心思想是…

为什么网络安全缺口大,招聘却很少?

2023 年我国网络空间安全人才数量缺口超过了 140 万&#xff0c;就业人数却只有 10 多万&#xff0c;缺口高达了 93%。这里就有人会问了&#xff1a; 1、网络安全行业为什么这么缺人&#xff1f; 2、明明人才那么稀缺&#xff0c;为什么招聘时招安全的人员却没有那么多呢&am…

头部大模型公司进京赶考,向量数据库成为应考神器

日前&#xff0c;由品玩主办的「模型思辨」国内大模型产业生态研讨会在北京举办&#xff0c;Zilliz 与 360 集团、阿里巴巴、昆仑万维等来自大模型产业链的头部公司及投资机构参会&#xff0c;会上 Zilliz 创始人兼首席执行官星爵、360 集团创始人周鸿祎、昆仑万维创始人方汉等…

C++ 类设计的实践与理解

前言 C代码提供了足够的灵活性&#xff0c;因此对于大部分工程师来说都很难把握。本文介绍了写好C代码需要遵循的最佳实践方法&#xff0c;并在最后提供了一个工具可以帮助我们分析C代码的健壮度。 1. 尽可能尝试使用新的C标准 到2023年&#xff0c;C已经走过了40多个年头。新…

如何进行微服务测试?

微服务测试是一种特殊的测试类型&#xff0c;因为它涉及到多个独立的服务。以下是进行微服务测试的一般性步骤&#xff1a; 1. 确定系统架构 了解微服务架构对成功测试至关重要。确定每个微服务的职责、接口、依赖项和通信方式。了解这些信息可以帮助您更好地规划测试用例和测…