Nacos 服务注册与发现之源码解密

Nacos是阿里巴巴开源的微服务治理组件,兼具服务注册发现与配置中心功能。与Eureka相比,Nacos支持更广泛的服务类型,并在集群中采用Leader-Follower模式进行数据同步,基于Raft算法进行选举。本文详细介绍了Nacos服务注册与发现的流程,包括选举算法、数据同步及源码分析。
摘要由CSDN通过智能技术生成

什么是Nacos

Naocs 是Alibaba开源的微服务生态中的一个组件,可以实现微服务治理中的服务注册发现,配置中心功能。

项目地址: https://github.com/alibaba/Nacos

Service is a first-class citizen in Nacos. Nacos supports discovering, configuring, and managing almost all types of services:

Kubernetes Service

gRPC & Dubbo RPC Service

Spring Cloud RESTful Service

引用官方的话来说,在Naocs中服务的地位非常重要。所以Nacos支持服务的发现、配置和管理所有类型的服务,包括Kubernetes Service、gRPC 和 Dubbo RPC Service 以及 Spring Cloud RESTful Service。

  • 简单来说Naocs就是一个帮助开发者集中管理服务的一个基于Spring Boot 项目的中间件而已。它可以帮助开发者完成服务的 注册和发现以及对服务的配置的管理。
  • Nacos在微服务整体架构中充当注册中心和配置中心(相当于Eureka 和Spring Cloud Config的组合)

Nacos 和 Eureka 的区别

  • (在微服务整体架构中扮演的角色不同)Nacos 不仅仅是服务注册中心还是配置中心,Eureka 只是充当服务注册中心
  • Nacos 满足CPA 理论中的CP , Eureka 满足的是AP
  • (Server集群间的数据同步方式不同) Nacos Server 集群中各个实例实现数据同步采用Leader-Follower机制,Eureka Server集群采用实例间的相互注册。
  • (客户端获取服务信息的方式不同) Nacos 中采用了在客户端每隔10秒主动轮询Server集群的方式并且还实现了通过基于UDP 方式的Server端主动推送数据到客户端, Eureka中采用定时任务,每隔30秒中去拉去一次数据。

Nacos 服务注册与发现的流程

Nacos 注册中心流程图

在这里插入图片描述

Nacos 配置中心流程图

在这里插入图片描述

集群选举

我们已经搭建起来了Nacos的集群。

只要涉及到集群,那么就会涉及到主从的关系。在Nacos中采用了的是Leader-Follower模式。显然这种模式是存在者选举机制的,谁当老大谁当老二,还是要走走民主的过程滴。

选举算法 Raft (Raft算法的Nacos实现的源码后面在补充)

Raft算法演示

Nacos 集群采用Raft算法,来实现的Leader选举。

选举算法在RaftCore中,包括数据处理和同步。

Raft中,节点有三种角色:

  • Leader:负责接收来自客户端的请求 (所以的事务请求都走他这里)
  • Candidate:在选举的过程中产生的一种临时角色
  • Follower:负责响应Leader或Candidate的请求

选举过程在两个时间节点发送:

  • Nacos Server 服务启动的时候
  • Leader节点挂掉了的时候

选举的过程:

​ 所有节点在最开始的时候,都是Follower状态。如果在一段时间内没有收到Leader的心跳(可能是Leader挂了或者根本没有Leader),

就会将自己的状态改为Candidate,开始竞选,并且会增加一个叫term的票据。

  • follower会先投自己一票,并且给其他节点发送Vote,等待其他节点回复。
  • 约束条件在任一个term中,单个节点最能只能投一票
  • 选举成功后,Leader和Followr们会维持一个心跳连接。
  • 在这个过程中可能会,出现下面集中情况:
    • 收到了过半的票数,那么这个节点就成为了Leader
    • 收到了其他节点发给自己的她已经成为了Leader,那么自己变为Follower
    • 一段时间内没有收到投票,继续增加term然后重新发起选举。
数据同步

在上面的演示地址的动画中,我们可以清晰的看到。Raft算法实现数据同步的方式是采用了 2PC提交的方式保证了数据的一致性。

过程:

  • Leader 收到了来自客户端的事务请求(如果是客户端直接请求到了Follower节点,Follower节点也会将这个事务性请求转发给Leader节点),修改自己的值。
  • Leader修改过后,会发送给他得Follower们,Follower们收到Leader发送过来的数据,会先将这个数据写入他们的本地日志中不提交,然后返回给Leader一个ACK信息,表示他们已经收到了Leader传递过来的数据。
  • 如果Leader 收到Follower的ACK信息数量,超过了一半,直接返回给客户端修改成功,通知Follower提交数据。反之通知客户端修改失败,并且通知Follower删除日志中的数据。

结合源码分析Nacos的实现

源码分析 服务的注册于发现

服务注册 :

在程序启动过的时候,我们可以看到这个类 NacosDiscoveryProperties,有一个注解@ConfigurationProperties 去加载我们在application.properties 文件中的 spring.cloud.nacos.discovery 的相关配置

//加载配置类
@ConfigurationProperties("spring.cloud.nacos.discovery")
public class  {
   
	private String serverAddr;
    ...
    @PostConstruct
	public void init() throws SocketException {
   
       	...
		// 获取服务的Ip地址
		if (StringUtils.isEmpty(ip)) {
   
			// traversing network interfaces if didn't specify a interface
			if (StringUtils.isEmpty(networkInterface)) {
   
				ip = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
			}
			else {
   
            	...
            }	
		}
		//设置参数的默认值
		this.overrideFromEnv(environment);
    }

}

真正的服务注册类 NacosServiceRegistry

NacosServiceRegistry,在Spring 初始化加载 NacosServiceRegistryAutoConfigugration配置类的时候,会被创建

NacosServiceRegistryAutoConfiguretion

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
		matchIfMissing = true)
@AutoConfigureAfter({
    AutoServiceRegistrationConfiguration.class,
		AutoServiceRegistrationAutoConfiguration.class,
		NacosDiscoveryAutoConfiguration.class })
public class NacosServiceRegistryAutoConfiguration {
   

	@Bean
	public NacosServiceRegistry nacosServiceRegistry(
			NacosDiscoveryProperties nacosDiscoveryProperties) {
   
        //开始创建NacosServiceRegistry
        //nacosDiscoveryProperties在上面已经被初始化完成
		return new NacosServiceRegistry(nacosDiscoveryProperties);
	}

这是服务注册时的最主要类 算是源码入口了!!!!

NacosServiceRegistry

//实现了ServiceRegistry这个接口。这个接口是SpringCloud的关于服务注册提供的同一的接口,
public class NacosServiceRegistry implements ServiceRegistry<Registration> {
   
    
	private final NacosDiscoveryProperties nacosDiscoveryProperties;

	private final NamingService namingService;

	public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {
   
		this.nacosDiscoveryProperties = nacosDiscoveryProperties;
		this.namingService = nacosDiscoveryProperties.namingServiceInstance();
	}
	//服务注册 
    //很多同学可能会犹豫,这个方法到底是在哪里调用的呢?因为我们在这里并没有看到有那个方法传入了Registration这个参数
    //其实它是在一个名为 AbstractAutoServiceRegistration 这个类里面的 start()方法中调用的。
    //AbstractAutoServiceRegistration 实现了ApplicationListener接口 传入的泛型是WebServerInitializedEvent
    //监听web容器启动的时候执行 里面的bind()方法,在bind()中执行start()方法
	@Override
	public void register(Registration registration) {
   

		if (StringUtils.isEmpty(registration.getServiceId())) {
   
			log.warn("No service to register for nacos client...");
			return;
		}
		
		String serviceId = registration.getServiceId();
		String group = nacosDiscoveryProperties.getGroup();
		//使用配置文件,去创建一个Inctanse实例,包括服务提供者的Ip地址和端口号
		Instance instance = getNacosInstanceFromRegistration(registration);

		try {
   
            //注册一个实例到nacos server,这个resgisterInstance方法在 NacosNamingService类中
			namingService.registerInstance(serviceId, group, instance);
			log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
					instance.getIp(), instance.getPort());
		}
		catch (Exception e) {
   
			log.error("nacos registry, {} register failed...{},", serviceId,
					registration.toString(), e);
			// rethrow a RuntimeException if the registration is failed.
			// issue : https://github.com/alibaba/spring-cloud-alibaba/issues/1132
			rethrowRuntimeException(e);
		}
	}

NacosNamingSevice

public class NacosNamingService implements NamingService {
   
    ...
   public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
   
        if (instance.isEphemeral()) {
   
            //创建心跳信息
            BeatInfo beatInfo = new BeatInfo();
            beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
            beatInfo.setIp(instance.getIp());
            beatInfo.setPort(instance.getPort());
            beatInfo.setCluster(instance.getClusterName());
            beatInfo.setWeight(instance.getWeight());
            beatInfo.setMetadata(instance.getMetadata());
            beatInfo.setSch
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值