Nacos 内核设计
Nacos ⼀致性协议
为什么 Nacos 需要⼀致性协议
Nacos 在开源支持就定下了⼀个目标,尽可能的减少用户部署以及运维成本,做到用户只需要⼀个程序包,就可以快速以单机模式启动 Nacos 或者以集群模式启动 Nacos。而 Nacos 是⼀个需要存储数据的⼀个组件,因此,为了实现这个目标,就需要在 Nacos 内部实现数据存储。单机下其实问题不大,简单的内嵌关系型数据库即可;但是集群模式下,就需要考虑如何保障各个节点之间的数据⼀致性以及数据同步,而要解决这个问题,就不得不引入共识算法,通过算法来保障各个节点之间的数据的⼀致性。为什么 Nacos 选择了 Raft 以及 Distro
为什么 Nacos 会在单个集群中同时运行 CP 协议以及 AP 协议呢?这其实要从 Nacos 的场景出发的:Nacos 是⼀个集服务注册发现以及配置管理于⼀体的组件,因此对于集群下,各个节点之间的数据⼀致性保障问题,需要拆分成两个方面从服务注册发现来看
服务发现注册中心,在当前微服务体系下,是十分重要的组件,服务之间感知对方服务的当前可正常提供服务的实例信息,必须从服务发现注册中心进行获取,因此对于服务注册发现中心组件的可用性,提出了很高的要求,需要在任何场景下,尽最大可能保证服务注册发现能力可以对外提供服务;同时 Nacos 的服务注册发现设计,采取了心跳可自动完成服务数据补偿的机制。如果数据丢失的话,是可以通过该机制快速弥补数据丢失。
因此,为了满足服务发现注册中心的可用性,强⼀致性的共识算法这里就不太合适了,因为强⼀致性共识算法能否对外提供服务是有要求的,如果当前集群可用的节点数没有过半的话,整个算法直接“罢工”,而最终⼀致共识算法的话,更多保障服务的可用性,并且能够保证在⼀定的时间内各
个节点之间的数据能够达成⼀致。上述的都是针对于 Nacos 服务发现注册中的非持久化服务而言(即需要客户端上报心跳进行服务实
例续约)。而对于 Nacos 服务发现注册中的持久化服务,因为所有的数据都是直接使用调用 Nacos服务端直接创建,因此需要由 Nacos 保障数据在各个节点之间的强⼀致性,故而针对此类型的服务数据,选择了强⼀致性共识算法来保障数据的⼀致性。从配置管理来看
配置数据,是直接在 Nacos 服务端进行创建并进行管理的,必须保证大部分的节点都保存了此配置数据才能认为配置被成功保存了,否则就会丢失配置的变更,如果出现这种情况,问题是很严重的,如果是发布重要配置变更出现了丢失变更动作的情况,那多半就要引起严重的现网故障了,因此对于配置数据的管理,是必须要求集群中大部分的节点是强⼀致的,而这里的话只能使用强⼀致性共识算法。为什么是 Raft 和 Distro 呢
对于强⼀致性共识算法,当前工业生产中,最多使用的就是 Raft 协议,Raft 协议更容易让人理解,并且有很多成熟的工业算法实现,比如蚂蚁金服的 JRaft、Zookeeper 的 ZAB、Consul 的 Raft、百度的 braft、Apache Ratis;因为 Nacos 是 Java 技术栈,因此只能在 JRaft、ZAB、ApacheRatis 中选择,但是 ZAB 因为和 Zookeeper 强绑定,再加上希望可以和 Raft 算法库的支持团队随时沟通交流,因此选择了 JRaft,选择 JRaft 也是因为 JRaft 支持多 RaftGroup,为 Nacos 后面的多数据分片带来了可能。而 Distro 协议是阿里巴巴自研的⼀个最终⼀致性协议,而最终⼀致性协议有很多,比如 Gossip、Eureka 内的数据同步算法。而 Distro 算法是集 Gossip 以及 Eureka 协议的优点并加以优化而出来的,对于原生的 Gossip,由于随机选取发送消息的节点,也就不可避免的存在消息重复发送给同⼀节点的情况,增加了网络的传输的压力,也给消息节点带来额外的处理负载,而 Distro 算法引入了权威 Server 的概念,每个节点负责⼀部分数据以及将自己的数据同步给其他节点,有效的降低了消息冗余的问题。
接下来,我们从源码中揭晓他们是如何实现的
我们看源码首先看它的类和接口设计
nacos为了达到模块之间复用性和低耦合,所以设计不同的模块下来我们看是nacos是如何开始工作的
nacos启动首先需要读取配置文件,所以是从这个类开始调用的
/** * Nacos Factory. * * @author Nacos */ public class NacosFactory { /** * Create config service. * * @param properties init param * @return config * @throws NacosException Exception */ public static ConfigService createConfigService(Properties properties) throws NacosException { return ConfigFactory.createConfigService(properties); } /** * Create config service. * * @param serverAddr server list * @return config * @throws NacosException Exception */ public static ConfigService createConfigService(String serverAddr) throws NacosException { return ConfigFactory.createConfigService(serverAddr); } /** * Create naming service. * * @param serverAddr server list * @return Naming * @throws NacosException Exception */ public static NamingService createNamingService(String serverAddr) throws NacosException { return NamingFactory.createNamingService(serverAddr); } /** * Create naming service. * * @param properties init param * @return Naming * @throws NacosException Exception */ public static NamingService createNamingService(Properties properties) throws NacosException { return NamingFactory.createNamingService(properties); } /** * Create maintain service. * * @param serverAddr server address * @return NamingMaintainService * @throws NacosException Exception */ public static NamingMaintainService createMaintainService(String serverAddr) throws NacosException { return NamingMaintainFactory.createMaintainService(serverAddr); } /** * Create maintain service. * * @param properties server address * @return NamingMaintainService * @throws NacosException Exception */ public static NamingMaintainService createMaintainService(Properties properties) throws NacosException { return NamingMaintainFactory.createMaintainService(properties); }nacos所有的注册,订阅等接口
初始化代码/** * 主要方法实现 * @param properties * @throws NacosException */ private void init(Properties properties) throws NacosException { //校验文件参数 ValidatorUtils.checkInitParam(properties); //初始化命名空间 this.namespace = InitUtils.initNamespaceForNaming(properties); InitUtils.initSerialization(); initServerAddr(properties); InitUtils.initWebRootContext(properties); initCacheDir(); initLogName(properties); //服务地址代理具体实现,初始化线程池配置 this.serverProxy = new NamingProxy(this.namespace, this.endpoint, this.serverList, properties); //服务心跳 this.beatReactor = new BeatReactor(this.serverProxy, initClientBeatThreadCount(properties)); //获取host实现 this.hostReactor = new HostReactor(this.serverProxy, beatReactor, this.cacheDir, isLoadCacheAtStart(properties), isPushEmptyProtect(properties), initPollingThreadCount(properties)); }下来就是注册每个服务实例
@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); } @Override public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException { //校验名称是否合法 NamingUtils.checkInstanceIsLegal(instance); //获取namespce对应组 String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName); //是否是临时实例 if (instance.isEphemeral()) { //如果是临时实例,增加心跳检测 BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance); beatReactor.addBeatInfo(groupedServiceName, beatInfo); } //实例注册 serverProxy.registerService(groupedServiceName, groupName, instance); }服务下线,会进行实例销毁,将刚才初始化的线程池销毁
@Override public void shutDown() throws NacosException { beatReactor.shutdown(); hostReactor.shutdown(); serverProxy.shutdown(); }具体销毁过程参考
Nacos源码分析(一)之线程池的巧妙设计,可以薅到自己的项目里
本篇先讲到这里,下篇给大家分享nacos是如何实现分布式一致协议的
大家一定记得点赞,收藏,关注
更新下篇你第一时间学习
你们的支持是我持续创作的动力!!!