什么是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:
引用官方的话来说,在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实现的源码后面在补充)
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