nacos-一致性协议(1)

2023-11-08

Nacos支持CP+AP模式,即Nacos可以根据配置识别为CP模式或AP模式,默认是AP模式。如果注册Nacos的client节点注册时ephemeral=true,那么Nacos集群对这个client节点的效果就是AP,采用distro协议实现;而注册Nacos的client节点注册时ephemeral=false,那么Nacos集群对这个节点的效果就是CP的,采用raft协议实现。根据client注册时的属性,AP,CP同时混合存在,只是对不同的client节点效果不同。Nacos可以很好的解决不同场景的业务需求。

协议介绍

distro协议

1、阿里自研发

2、保证cp,保证最终一致性。

  • Nacos 每个节点是平等的都可以处理写请求,同时把新数据同步到其他节点。
  • 每个节点只负责部分数据,定时发送自己负责数据的校验值到其他节点来保持数据一致性。
  • 每个节点独立处理读请求,及时从本地发出响应。

Raft协议

1、国外论文

2、实现简单,使用方便,jRaft包支持。

3、能够保证强一致性

4、nacos采用jRaft包实现raft强一致性协议

nacos使用了raft优秀的生产级别的jraft包实现Raft协议。

  <dependency>
      <groupId>com.alipay.sofa</groupId>
      <artifactId>jraft-core</artifactId>
      <version>1.3.8</version>
      <scope>compile</scope>
      <exclusions>
        <exclusion>
          <artifactId>bolt</artifactId>
          <groupId>com.alipay.sofa</groupId>
        </exclusion>
        <exclusion>
          <artifactId>log4j-api</artifactId>
          <groupId>org.apache.logging.log4j</groupId>
        </exclusion>
        <exclusion>
          <artifactId>log4j-core</artifactId>
          <groupId>org.apache.logging.log4j</groupId>
        </exclusion>
        <exclusion>
          <artifactId>log4j-slf4j-impl</artifactId>
          <groupId>org.apache.logging.log4j</groupId>
        </exclusion>
        <exclusion>
          <artifactId>log4j-jcl</artifactId>
          <groupId>org.apache.logging.log4j</groupId>
        </exclusion>
      </exclusions>
    </dependency>

nacos强一致性协议初始化流程

一、通过@Configuration想spring注入一个协议bean:JRaftProtocol

@Bean(value = "strongAgreementProtocol")
    public CPProtocol strongAgreementProtocol(ServerMemberManager memberManager) throws Exception {
        final CPProtocol protocol = getProtocol(CPProtocol.class, () -> new JRaftProtocol(memberManager));
        return protocol;
    }

1、ServerMemberManager bean流程

1.1、感知本机服务的地址和端口号,将其维护到ServerMemberManager#serverList当中。

1.2、向通知中心注册事件MembersChangeEvent。由于MembersChangeEvent是Event的子类,所以他被注册到cNotifyCenter#publisherMap中。该事件的处理器类是ClusterRpcClientProxy,其在spring初始化调用@PostConstruct注解的时候将其注入到DefaultPublisher#subscribers列表当中。当调用registerToPublisher的时候会在subscribers列表当中找到执行该时间的监听器并返回。

1.3、初始化MemberLookup实例,并且读取本实例的conf文件,并写入本次集群节点信息到conf文件当中,并且启动job(WatchDirJob)监听该节点配置文件变化。

2、JRaftProtocol实例化

 public JRaftProtocol(ServerMemberManager memberManager) throws Exception {
        this.memberManager = memberManager;
        this.raftServer = new JRaftServer();
        this.jRaftMaintainService = new JRaftMaintainService(raftServer);
    }

JRaftProtocol实例化就是将memberManager注入进来,并实例化两个类JRaftServer和JRaftMaintainService。

二、JRaftProtocol初始化

1、spring注入PersistentConsistencyServiceDelegateImpl

@DependsOn("ProtocolManager")
@Component("persistentConsistencyServiceDelegate")
public class PersistentConsistencyServiceDelegateImpl implements PersistentConsistencyService {
    
    private final BasePersistentServiceProcessor persistentServiceProcessor;
    
    public PersistentConsistencyServiceDelegateImpl(ProtocolManager protocolManager) throws Exception {
        this.persistentServiceProcessor = createPersistentServiceProcessor(protocolManager);
    }

2、根据当前是单例还是集群,初始化BasePersistentServiceProcessor

   private BasePersistentServiceProcessor createPersistentServiceProcessor(ProtocolManager protocolManager)
            throws Exception {
        final BasePersistentServiceProcessor processor =
                EnvUtil.getStandaloneMode() ? new StandalonePersistentServiceProcessor()
                        : new PersistentServiceProcessor(protocolManager);
        processor.afterConstruct();
        return processor;
    }

3、PersistentServiceProcessor初始化会调用protocolManager的getCpProtocol方法

  public PersistentServiceProcessor(ProtocolManager protocolManager) throws Exception {
        this.protocol = protocolManager.getCpProtocol();
    }

4、protocolManager的getCpProtocol调用protocol.init方法

 private void initCPProtocol() {
        ApplicationUtils.getBeanIfExist(CPProtocol.class, protocol -> {
            Class configType = ClassUtils.resolveGenericType(protocol.getClass());
            Config config = (Config) ApplicationUtils.getBean(configType);
            injectMembers4CP(config);
            protocol.init(config);
            ProtocolManager.this.cpProtocol = protocol;
        });
    }

5、protocol.init方法流程

JRaft集成

jraft有几个特别重要的概念

1、地址 Endpoint

2、节点 PeerId,PeerId 表示一个 raft 协议的参与者

3、配置 Configuration,Configuration 表示一个 raft group 的配置,也就是参与者列表:

在JRaftServer初始化的时候会初始化以下信息:

1、raft协议的初始化配置

①、获取raft线程池核心线程

②、获取clientService核心线程数,

③、实例化核心线程池 ,名字开头为:com.alibaba.nacos.core.raft-core

④、实例化clientService线程池,名字开头为:com.alibaba.nacos.core.raft-cli-service

⑤、初始化普通线程池,线程数为8 名字开头为com.alibaba.nacos.core.protocol.raft-common

⑥、初始化快照线程池,名字开头为com.alibaba.nacos.core.raft-snapshot

2、将该节点地址信息包装成PeerId

localPeerId = PeerId.parsePeer(self);

3、设置节点参数nodeOptions

4、初始化类CliService

5、初始化类cliClientService

在JRaftServer启动的时候首先启动了rpcServer.rpcServer有两种方式,一种是GRpc,一种是Bolt rpc.

nacos使用的是Grpc的方式。

启动了rpc,接下来就是基于jraft做以下步骤

1、本地创建三个目录,并将三个目录设置到NodeOptions对象中。

2、初始化nacos的raft状态机NacosStateMachine。NacosStateMachine可以理解为raft协议在感知集群节点状态变化之后通知该节点。例如leader节点下线了,作为following节点加入raft协议。

所以这个状态机是实现jraft的核心。

3、启动组服务RaftGroupService。RaftGroupService主要是将本地包装节点node加入raft协议。

4、更新组信息到路由表groupConfTable

5、使用上文说的普通线程池(raftCommonExecutor)启动任务registerSelfToCluster。

该任务如下:

1、使用rpcExecutor线程池依次执行组内节点,取组节点的leaderId.如果找到就会跳出。如果找不到就抛异常。然后本节点跟leader节点连接,并发送获取组内节点信息。如果发现自己已经在组内节点信息内就返回,如果不在则调用CliService#addPeer,连接leader节点,并发送addPeer请求给leader节点。leader节点接到该请求会将节点加入到BaseCliRequestProcessor.CliRequestContext#node当中。客户端会根据leaderj节点返回的结果信息打印当前参与raft节点信息和节点变化信息。

registerSelfToCluster任务是1秒钟执行一次。

接下来我们通过几个问题来大概了解下nacos使用jraft实现raft的实现机理。

问题一、结点启动的时候是如何参与选举的?

PersistentConsistencyServiceDelegateImpl

spring注入该类的时候,调用了PersistentServiceProcessor的afterConstruct方法。

afterConstruct方法调用了JRaftProtocol的addRequestProcessors方法。

addRequestProcessors方法将PersistentServiceProcessor实例注入到了JRaftServer实例的processors字段中,并且调用了初始化raft节点,Node node = raftGroupService.start(false);

初始化raft节点的时候做了两件事儿

1、初始化节点

 final Node ret = createRaftNode(groupId, serverId);

2、调用节点的init方法。

ret.init(opts)

调用节点的init方法的时候做了很多事情,其中一个就是初始化了一个选举任务electionTimer。

 name = "JRaft-ElectionTimer-" + suffix;
        this.electionTimer = new RepeatedTimer(name, this.options.getElectionTimeoutMs(),
            TIMER_FACTORY.getElectionTimer(this.options.isSharedElectionTimer(), name)) {

            @Override
            protected void onTrigger() {
                handleElectionTimeout();
            }

            @Override
            protected int adjustTimeout(final int timeoutMs) {
                return randomTimeout(timeoutMs);
            }
        };

有了这个任务,在init快结束的时候发起了electSelf();操作。

问题二、结点下线的时候其它节点是如何感知的?

ProtocolManager实现了接口DisposableBean。当我们的服务由于某种原因下线会调用ProtocolManager的destroy方法。destroy方法会调用jraft协议的shutdown方法

 @PreDestroy
    @Override
    public void destroy() {
        if (Objects.nonNull(apProtocol)) {
            apProtocol.shutdown();
        }
        if (Objects.nonNull(cpProtocol)) {
            cpProtocol.shutdown();
        }
    }

备注kill -2 pid 能看到这个过程。

1、落数据到本地nacos-9100/data/protocol/raft/naming_persistent_service/meta-data。

问题三、节点很多的情况下,leader下线了如何选举?

leader节点正常要给每一个从节点发送心跳,如果心跳超时会重新发起选举

问题四、节点间心跳在哪里


 for (final PeerId peer : this.conf.listPeers()) {
            if (peer.equals(this.serverId)) {
                continue;
            }
            LOG.debug("Node {} add a replicator, term={}, peer={}.", getNodeId(), this.currTerm, peer);
            if (!this.replicatorGroup.addReplicator(peer)) {
                LOG.error("Fail to add a replicator, peer={}.", peer);
            }
        }

这段代码在electSelf()中,也就是选举自己作为leader的过程中,如果发现除了自己还有其他节点在集群当中会调用addReplicator将该节点以Follower的形式加入。

final ThreadId rid = Replicator.start(opts, this.raftOptions);

启动Replicator要启动一个复制线程并且设置了一个超时任务

 r.id = new ThreadId(r, r);
        r.id.lock();
        notifyReplicatorStatusListener(r, ReplicatorEvent.CREATED);
        LOG.info("Replicator={}@{} is started", r.id, r.options.getPeerId());
        r.catchUpClosure = null;
        r.lastRpcSendTimestamp = Utils.monotonicMs();
        r.startHeartbeatTimer(Utils.nowMs());

心跳延时任务使用的是延迟队列实现。心跳的发送

private void startHeartbeatTimer(final long startMs) {
        final long dueTime = startMs + this.options.getDynamicHeartBeatTimeoutMs();
        try {
            this.heartbeatTimer = this.timerManager.schedule(() -> onTimeout(this.id), dueTime - Utils.nowMs(),
                TimeUnit.MILLISECONDS);
        } catch (final Exception e) {
            LOG.error("Fail to schedule heartbeat timer", e);
            onTimeout(this.id);
        }
    }

触发探活的入口在读Leader这个方法NodeImpl#readLeader。

当心跳任务执行的时候,会根据远程结果动态再次触发下一次心跳任务。

问题五、选举超时

选举一旦超时就执行以下任务

this.voteTimer = new RepeatedTimer(name, this.options.getElectionTimeoutMs(), TIMER_FACTORY.getVoteTimer(
            this.options.isSharedVoteTimer(), name)) {

            @Override
            protected void onTrigger() {
                handleVoteTimeout();
            }

            @Override
            protected int adjustTimeout(final int timeoutMs) {
                return randomTimeout(timeoutMs);
            }
        };

问题六、节点之间数据同步是如何做的?

通过jRaft的Replicator来做。

小结

本文通过对nacos一致性协议之一的raft做一个简单的源码介绍,带大家粗略查看了下nacos是如何集成jraft通过raft协议实现CP模式的。集成jraft的关键类是JRaftServer和JRaftProtocol,通过仔细研读这两个类一定能体会一些集成jraft的方法,至于底层jraft实现大家可以先参考文档JRaft 用户指南 · SOFAStack,能够领略raft各个模块的隔离性在代码实现简洁性方面的巨大作用。

参考文献

Nacos 2.0原理解析(一):Distro协议_zyxzcr的博客-CSDN博客_distro协议

SOFAJRaft 源码分析二(日志复制、心跳)_大远哥的博客-CSDN博客_jraft grcp pipeline

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

nacos-一致性协议(1) 的相关文章

随机推荐

  • 小程序自动化测试框架Minium——框架介绍和环境搭建

    一 Minium 简介 minium 是微信团队为小程序专门开发的自动化框架 我们可以用它来做小程序的UI自动化测试 但是它的能力却不仅仅在于UI自动化 正是得益于官方订制打造 所以对于小程序很多原生内容和特性的支持自然也是非常独到的 比如
  • 重点必考题:区块链百万年薪人才招聘,必会这58个知识点

    2018年区块链行业热度不减 投资人青睐 创业者追捧 而区块链人才成了稀缺性物种 拉勾网发布的 2018年区块链高薪清单 中 腾讯 阿里等大厂将区块链岗位的月薪最高开到了100K 有相关数据统计 2月份北京区块链岗位增加了3万多个 技术类2
  • ovirt简介

    Ovirt 调研 简介 架构 功能模块和列表 虚拟化所用的技术 kvm 是否支持分布式存储以及所用的技术 支持 gluster 虚拟网络能够实现哪些功能 VLAN 在虚拟机上最小化部署一套 简介 oVirt是一种开源分布式虚拟化解决方案 旨
  • wang_ys121账号停用及博客迁移至wonyoungsen的说明!

    从今天起 本账号wang ys121停止使用 不再更新博文 并将此账号博文逐步迁移到新的账号wonyoungsen中去 请喜欢我的同学继续关注我 谢谢
  • 远程链接redis遇到的坑

    1 安装redis参考 https cloud tencent com developer article 1653709 安装完后一直无法启动redis Dec 28 13 12 05 hecs 22391 systemd 1 Start
  • 【可视化开发】echarts点击事件

    echarts提供了点击事件方法 首先需要确定点击的范围 以横向柱状图为例 若不设置属性 添加点击事件后默认为柱型 设置可点击属性 需添加以下配置 yAxis 其余属性 silent false 坐标轴是否是静态无法交互 triggerEv
  • linux 中 安装 redis

    cd usr mkdir redis cd redis wget http download redis io releases redis 3 2 4 tar gz tar xzf redis 3 2 4 tar gz cd redis
  • PyTorch 矩阵乘法的方法总结 & 问题解答

    在机器学习和深度学习中时 矩阵运算是最常见 有效提高计算效率的方法 因为特征和权重都以向量储存 矩阵运算就显得尤为重要 诸如梯度下降 反向传播 矩阵因子分解等重要的机器学习方法 都需要基于矩阵运算 在深度学习中 神经网络将权重储存在矩阵当中
  • Java连接MySQL

    目录 1 JDBC简介 2 使用 JDBC 连接数据库 2 1 使用流程 3 详细操作 3 1 导入 jar 包 4 通过 java 语言操作数据库 4 1 连接 Mysql 数据库 4 2 增 删 改 查操作 5 部分问题 5 1 数据库
  • Spring-Data-JDBC 自定义枚举类型 转换遇到的bug (搞了半天,在 insuess中看到)

    解决思路 bug描述 issuse地址 解决方法
  • qt中如何模拟按钮点击_qt模拟键盘的三种实现方式(思路+demo)

    按照我自己的理解 qt模拟键盘分三个层次的模拟 1 单线程的模拟 2 qt框架内的模拟 可以跨线程的 3 在操作系统的图形界面内模拟 相当于模拟了一个输入法 第一种 一般就是添加多个按键 QToolButton QPushButton都可以
  • RTThread:静态线程&动态线程

    一 静态线程创建 rt thread init rt err t rt thread init struct rt thread thread const char name void void parameter entry void p
  • ❀资源帖❀ResNet,ConvNeXt,Transformer预训练模型等

    1 说在前面 部分输入网页直接开始下载 一些是自己训练后的模型 2 resnet权重文件下载地址 2 1resnet18 https download pytorch org models resnet18 5c106cde pth 2 2
  • 关于OPC UA TSN中TSN

    近日 TTTech和英特尔联合发表了一份白皮书 为寻求在工业自动化系统中实现TSN网络技术的客户提供指导 白皮书概述了所有的TSN标准 优点和特点 并描述了TTTech和英特尔今天可用的产品如何可用于开发优化的TSN设备和系统 TSN将重塑
  • 巧用WINRAR和DOS命令处理压缩数据

    开展计算机审计 首先要取得被审计单位的电子数据 然后将数据标准化 即将被审计单位提供的数据转换成便于审计人员使用的 SQL Server 或其他数据库格式的数据 因此 计算机审计首先要解决的是数据标准化问题 许多商业银行采取按天备份的策略
  • 记一次Tomcat日志分析:一个或多个listeners启动失败,更多详细信息查看对应的容器日志文件

    1 问题 我将一个应用 MicroStrategy 11 3 0000 13515 部署到Tomcat 然后 我点击start后报错 FAIL Application at context path MicroStrategy 11 3 0
  • 网络图库Cytoscape.js的使用(二):图表常用操作

    文章目录 图形实例与集合 选择器 常用函数 获取指定元素 集合 视口常用操作 数据 全局常用方法 元素常用数据 操作方法 修改元素样式常用方法 迭代 构建 过滤常用方法 图形实例与集合 为了使用Cytoscape js 架构中有两个部分是程
  • Git安装教程

    文章目录 Git安装教程 一 Git安装与下载 二 Git 的功能介绍 三 测试Git是否安装成功 Git安装教程 一 Git安装与下载 浏览器搜索Git官方下载或https git scm com downloads 官网可能下载慢 阿里
  • Spring Cloud是什么?

    Spring Cloud 是一款基于 Spring Boot 实现的微服务框架 Spring Cloud 源自 Spring 社区 主要由 Pivotal 和 Netflix 两大公司提供技术迭代和维护 随着微服务的火爆流行 国内外各大互联
  • nacos-一致性协议(1)

    Nacos支持CP AP模式 即Nacos可以根据配置识别为CP模式或AP模式 默认是AP模式 如果注册Nacos的client节点注册时ephemeral true 那么Nacos集群对这个client节点的效果就是AP 采用distro