Nacos架构与原理 - 寻址机制

简介: Nacos架构与原理 - 寻址机制

前提


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 节点定时请求当前最新的集群成员节点列表信息即可。

7ac6a091fcf9497faec7ceebaf6032e2.png

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 协议。


相关文章
|
4月前
|
人工智能 安全 Cloud Native
Nacos 3.0 架构升级,AI 时代更安全的 Registry
随着Nacos3.0的发布,定位由“更易于构建云原生应用的动态服务发现、配置管理和服务管理平台”升级至“ 一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台 ”。
|
3月前
|
机器学习/深度学习 算法 文件存储
神经架构搜索NAS详解:三种核心算法原理与Python实战代码
神经架构搜索(NAS)正被广泛应用于大模型及语言/视觉模型设计,如LangVision-LoRA-NAS、Jet-Nemotron等。本文回顾NAS核心技术,解析其自动化设计原理,探讨强化学习、进化算法与梯度方法的应用与差异,揭示NAS在大模型时代的潜力与挑战。
786 6
神经架构搜索NAS详解:三种核心算法原理与Python实战代码
|
1月前
|
机器学习/深度学习 自然语言处理 监控
23_Transformer架构详解:从原理到PyTorch实现
Transformer架构自2017年Google发表的论文《Attention Is All You Need》中提出以来,彻底改变了深度学习特别是自然语言处理领域的格局。在短短几年内,Transformer已成为几乎所有现代大型语言模型(LLM)的基础架构,包括BERT、GPT系列、T5等革命性模型。与传统的RNN和LSTM相比,Transformer通过自注意力机制实现了并行化训练,极大提高了模型的训练效率和性能。
|
4月前
|
存储 监控 算法
园区导航系统技术架构实现与原理解构
本文聚焦园区导航场景中室内外定位精度不足、车辆调度路径规划低效、数据孤岛难以支撑决策等技术痛点,从架构设计到技术原理,对该系统从定位到数据中台进行技术拆解。
200 0
园区导航系统技术架构实现与原理解构
|
5月前
|
人工智能 安全 Cloud Native
Nacos 3.0 架构全景解读,AI 时代服务注册中心的演进
Nacos 3.0 正式发布,定位升级为“一个易于构建 AI Agent 应用的动态服务发现、配置管理和 AI 智能体管理平台”。架构上强化了安全性,引入零信任机制,并支持 MCP 服务管理、AI Registry 等新特性,助力 AI 应用高效开发与运行。
|
6月前
|
存储 人工智能 自然语言处理
为什么混合专家模型(MoE)如此高效:从架构原理到技术实现全解析
本文深入探讨了混合专家(MoE)架构在大型语言模型中的应用与技术原理。MoE通过稀疏激活机制,在保持模型高效性的同时实现参数规模的大幅扩展,已成为LLM发展的关键趋势。文章分析了MoE的核心组件,包括专家网络与路由机制,并对比了密集与稀疏MoE的特点。同时,详细介绍了Mixtral、Grok、DBRX和DeepSeek等代表性模型的技术特点及创新。MoE不仅解决了传统模型扩展成本高昂的问题,还展现出专业化与适应性强的优势,未来有望推动AI工具更广泛的应用。
3417 4
为什么混合专家模型(MoE)如此高效:从架构原理到技术实现全解析
|
5月前
|
存储 消息中间件 canal
zk基础—2.架构原理和使用场景
ZooKeeper(ZK)是一个分布式协调服务,广泛应用于分布式系统中。它提供了分布式锁、元数据管理、Master选举及分布式协调等功能,适用于如Kafka、HDFS、Canal等开源分布式系统。ZK集群采用主从架构,具有顺序一致性、高性能、高可用和高并发等特点。其核心机制包括ZAB协议(保证数据一致性)、Watcher监听回调机制(实现通知功能)、以及基于临时顺序节点的分布式锁实现。ZK适合小规模集群部署,主要用于读多写少的场景。
|
6月前
|
机器学习/深度学习 算法 测试技术
图神经网络在信息检索重排序中的应用:原理、架构与Python代码解析
本文探讨了基于图的重排序方法在信息检索领域的应用与前景。传统两阶段检索架构中,初始检索速度快但结果可能含噪声,重排序阶段通过强大语言模型提升精度,但仍面临复杂需求挑战
218 0
图神经网络在信息检索重排序中的应用:原理、架构与Python代码解析
|
6月前
|
Java 开发者 Spring
Spring框架 - 深度揭秘Spring框架的基础架构与工作原理
所以,当你进入这个Spring的世界,看似一片混乱,但细看之下,你会发现这里有个牢固的结构支撑,一切皆有可能。不论你要建设的是一座宏大的城堡,还是个小巧的花园,只要你的工具箱里有Spring,你就能轻松搞定。
258 9

热门文章

最新文章