Nacos 服务注册需要具备的能力:

  • 服务提供者把自己的协议地址注册到Nacos server
  • 服务消费者需要从Nacos Server上去查询服务提供者的地址(根据服务名称)
  • Nacos Server需要感知到服务提供者的上下线的变化
  • 服务消费者需要动态感知到Nacos Server端服务地址的变化

  作为注册中心所需要的能力大多如此,我们需要做的是理解各种注册中心的独有特性,总结他们的共性。

Nacos的实现原理:

  下面我们先来了解一下 Nacos 注册中心的实现原理,通过下面这张图来说明。

  图中的流程是大家所熟悉的,不同的是在Nacos 中,服务注册时在服务端本地会通过轮询注册中心集群节点地址进行服务得注册,在注册中心上,即Nacos Server上采用了Map保存实例信息,当然配置了持久化的服务会被保存到数据库中,在服务的调用方,为了保证本地服务实例列表的动态感知,Nacos与其他注册中心不同的是,采用了 Pull/Push同时运作的方式。通过这些我们对Nacos注册中心的原理有了一定的了解。我们从源码层面去验证这些理论知识。

Nacos的源码分析(结合spring-cloud-alibaba +dubbo +nacos 的整合):

  服务注册的流程:

  在基于Dubbo服务发布的过程中, 自动装配是走的事件监听机制,在 DubboServiceRegistrationNonWebApplicationAutoConfiguration 这个类中,这个类会监听 ApplicationStartedEvent 事件,这个事件是spring boot在2.0新增的,就是当spring boot应用启动完成之后会发布这个时间。而此时监听到这个事件之后,会触发注册的动作。

@EventListener(ApplicationStartedEvent.class)
public void onApplicationStarted() {
setServerPort();
register();
} private void register() {
if (registered) {
return;
}
serviceRegistry.register(registration);
registered = true;
}

  this.serviceRegistry。 是spring-cloud提供的接口实现(org.springframework.cloud.client.serviceregistry.ServiceRegistry).很显然注入的实例是: NacosServiceRegistry

  然后进入到实现类的注册方法:

@Override
public void register(Registration registration) { if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
//对应当前应用的application.name
String serviceId = registration.getServiceId();
//表示nacos上的分组配置
String group = nacosDiscoveryProperties.getGroup();
//表示服务实例信息
Instance instance = getNacosInstanceFromRegistration(registration); try {
//通过命名服务进行注册
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);
}
}

  接下去就是开始注册实例,主要做两个动作

  1. 如果当前注册的是临时节点,则构建心跳信息,通过beat反应堆来构建心跳任务
  2. 调用registerService发起服务注册
@Override
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.setScheduled(false); //beatReactor, 添加心跳信息进行处理
       beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
}
//调用服务代理类进行注册
serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
}

  然后调用 NamingProxy  的注册方法进行注册,代码逻辑很简单,构建请求参数,发起请求。

public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {

        NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}",
namespaceId, serviceName, instance); final Map<String, String> params = new HashMap<String, String>(8);
params.put(CommonParams.NAMESPACE_ID, namespaceId);
params.put(CommonParams.SERVICE_NAME, serviceName);
params.put(CommonParams.GROUP_NAME, groupName);
params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
params.put("ip", instance.getIp());
params.put("port", String.valueOf(instance.getPort()));
params.put("weight", String.valueOf(instance.getWeight()));
params.put("enable", String.valueOf(instance.isEnabled()));
params.put("healthy", String.valueOf(instance.isHealthy()));
params.put("ephemeral", String.valueOf(instance.isEphemeral()));
params.put("metadata", JSON.toJSONString(instance.getMetadata())); reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST); }

  往下走我们就会发现上面提到的,服务在进行注册的时候会轮询配置好的注册中心的地址:

public String reqAPI(String api, Map<String, String> params, List<String> servers, String method) {

        params.put(CommonParams.NAMESPACE_ID, getNamespaceId());

        if (CollectionUtils.isEmpty(servers) && StringUtils.isEmpty(nacosDomain)) {
throw new IllegalArgumentException("no server available");
} Exception exception = new Exception();
//如果服务地址不为空
if (servers != null && !servers.isEmpty()) {
//随机获取一台服务器节点
Random random = new Random(System.currentTimeMillis());
int index = random.nextInt(servers.size());
// 遍历服务列表
for (int i = 0; i < servers.size(); i++) {
String server = servers.get(index);//获得索引位置的服务节点
try {//调用指定服务
return callServer(api, params, server, method);
} catch (NacosException e) {
exception = e;
NAMING_LOGGER.error("request {} failed.", server, e);
} catch (Exception e) {
exception = e;
NAMING_LOGGER.error("request {} failed.", server, e);
}
//轮询
index = (index + 1) % servers.size();
}
// ..........
}

  最后通过 callServer(api, params, server, method) 发起调用,这里通过 JSK自带的 HttpURLConnection 进行发起调用。我们可以通过断点的方式来看到这里的请求参数:

  期间可能会有多个 GET的请求获取服务列表,是正常的,会发现有如上的一个请求,会调用 http://192.168.200.1:8848/nacos/v1/ns/instance 这个地址。那么接下去就是Nacos Server 接受到服务端的注册请求的处理流程。需要下载Nacos Server 源码,

  源码下载可以参考 :https://www.cnblogs.com/wuzhenzhao/p/11384266.html

  Nacos服务端的处理:

  服务端提供了一个InstanceController类,在这个类中提供了服务注册相关的API,而服务端发起注册时,调用的接口是: [post]: /nacos/v1/ns/instance ,serviceName: 代表客户端的项目名称 ,namespace: nacos 的namespace。

@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception { final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
final String namespaceId = WebUtils
.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
// 从请求中解析出instance实例
final Instance instance = parseInstance(request); serviceManager.registerInstance(namespaceId, serviceName, instance);
return "ok";
}

  然后调用 ServiceManager 进行服务的注册

public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
//创建一个空服务,在Nacos控制台服务列表展示的服务信息,实际上是初始化一个serviceMap,它是一个ConcurrentHashMap集合
createEmptyService(namespaceId, serviceName, instance.isEphemeral());
//从serviceMap中,根据namespaceId和serviceName得到一个服务对象
Service service = getService(namespaceId, serviceName); if (service == null) {
throw new NacosException(NacosException.INVALID_PARAM,
"service not found, namespace: " + namespaceId + ", service: " + serviceName);
}
//调用addInstance创建一个服务实例
addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}

  在创建空的服务实例的时候我们发现了存储实例的map:

public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
throws NacosException {
//从serviceMap中获取服务对象
Service service = getService(namespaceId, serviceName);
if (service == null) {//如果为空。则初始化
      Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
      service = new Service();
      service.setName(serviceName);
      service.setNamespaceId(namespaceId);
      service.setGroupName(NamingUtils.getGroupName(serviceName));
      // now validate the service. if failed, exception will be thrown
      service.setLastModifiedMillis(System.currentTimeMillis());
      service.recalculateChecksum();
      if (cluster != null) {
      cluster.setService(service);
      service.getClusterMap().put(cluster.getName(), cluster);
      }
      service.validate();
      putServiceAndInit(service);
      if (!local) {
       addOrReplaceService(service);
      }
}

  在 getService 方法中我们发现了Map:

/**
* Map(namespace, Map(group::serviceName, Service)).
*/
private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

  通过注释我们可以知道,Nacos是通过不同的 namespace 来维护服务的,而每个namespace下有不同的group,不同的group下才有对应的Service ,再通过这个 serviceName 来确定服务实例。

  第一次进来则会进入初始化,初始化完会调用 putServiceAndInit

private void putServiceAndInit(Service service) throws NacosException {
putService(service);//把服务信息保存到serviceMap集合
service.init();//建立心跳检测机制
//实现数据一致性监听,ephemeral(标识服务是否为临时服务,默认是持久化的,也就是true)=true表示采用raft协议,false表示采用Distro
consistencyService
.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);
consistencyService
.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);
Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJson());
}

  获取到服务以后把服务实例添加到集合中,然后基于一致性协议进行数据的同步。然后调用 addInstance

public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
throws NacosException {
// 组装key
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
// 获取刚刚组装的服务
Service service = getService(namespaceId, serviceName); synchronized (service) {
List<Instance> instanceList = addIpAddresses(service, ephemeral, ips); Instances instances = new Instances();
instances.setInstanceList(instanceList);
// 也就是上一步实现监听的类里添加注册服务
consistencyService.put(key, instances);
}
}

  然后给服务注册方发送注册成功的响应。结束服务注册流程。其中细节后续慢慢分析。

Nacos 服务注册的原理的更多相关文章

  1. nacos服务注册与发现原理解析

    前言:nacos 玩过微服务的想必不会陌生,它是阿里对于springcloud孵化出来的产品,用来完成服务之间的注册发现和配置中心,其核心作用我就不废话了 大致流程:每个服务都会有一个nacos cl ...

  2. 微服务架构 | *3.5 Nacos 服务注册与发现的源码分析

    目录 前言 1. 客户端注册进 Nacos 注册中心(客户端视角) 1.1 Spring Cloud 提供的规范标准 1.2 Nacos 的自动配置类 1.3 监听服务初始化事件 AbstractAu ...

  3. Spring Cloud Alibaba Nacos 服务注册与发现功能实现!

    Nacos 是 Spring Cloud Alibaba 中一个重要的组成部分,它提供了两个重要的功能:服务注册与发现和统一的配置中心功能. 服务注册与发现功能解决了微服务集群中,调用者和服务提供者连 ...

  4. Spring Cloud Alibaba | Nacos服务注册与发现

    目录 Spring Cloud Alibaba | Nacos服务注册与发现 1. 服务提供者 1.1 pom.xml项目依赖 1.2 配置文件application.yml 1.3 启动类Produ ...

  5. Spring Cloud Alibaba(一) 如何使用nacos服务注册和发现

    Nacos介绍 Nacos 致力于帮助您发现.配置和管理微服务.Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现.服务配置.服务元数据及流量管理. Nacos 帮助您更敏捷和容易地构 ...

  6. Alibaba Nacos 学习(三):Spring Cloud Nacos Discovery - FeignClient,Nacos 服务注册与发现

    Alibaba Nacos 学习(一):Nacos介绍与安装 Alibaba Nacos 学习(二):Spring Cloud Nacos Config Alibaba Nacos 学习(三):Spr ...

  7. SpringCloud Alibaba Nacos 服务注册

    业务服务接入Nacos服务治理中心 启动Nacos访问地址为:http://101.200.201.195:8848/nacos/ 创建bom工程用于管理依赖(下方附加源码地址) 准备工作完成后开始接 ...

  8. springcloudalibaba与nacos服务注册流程图

    springboot + springcloud + springcloudalibaba + nacos 服务注册流程图: springboot ①WebApplicationContext ②st ...

  9. 实战二:nacos服务注册与发现,openfeign服务调用

    一,参照上一篇创建好微服务结构后,按业务需求编写各微服务逻辑 二,服务注册 1,安装nacos:下载,解压,运行startup.cmd 2,访问 http://localhost:8848/nacos ...

随机推荐

  1. Ajax爬取动态数据和HTTPS自动默认证书

    Ajax数据爬取 在spider爬取数据的过程中,有些网页的数据是利用Ajax动态加载出来的,所以,在网页源代码中可能不会看到这一部分的数据,因此,我们需要使用另外的方式进行数据多爬取. 以豆瓣电影的 ...

  2. ORM研究3 - odoo fields常用的字段属性

    之前我们已经讲解了odoo ORM中的一些对字段常用的API操作方法,今天我们继续研究一下Odoo orm中字段的一些通用属性字段的使用,学会它们可以为自己创建数据映射并使用有更好的帮助. 通用字段属 ...

  3. web系统国际化思路

    需求:php开发多个中文系统支持国际化 思路: 提炼各个系统中的中文字符,替换为资源key. 多媒体文件中的中文定位(图片中的中文,中文录音,中文视频,中文模板等). 统一翻译文字.准备资源文件. 各 ...

  4. groff编写man页

    groff 是大多数 Unix 系统上所提供的流行的文本格式化工具 nroff/troff 的 GNU 版本.它一般用于编写手册页,即命令.编程接口等的在线文档.在本文中,我们将给你展示如何使用 gr ...

  5. PAT甲级:1136 A Delayed Palindrome (20分)

    PAT甲级:1136 A Delayed Palindrome (20分) 题干 Look-and-say sequence is a sequence of integers as the foll ...

  6. 通过http将yum仓库发布

    说明:这里是Linux服务综合搭建文章的一部分,本文可以作为单独构建http和发布yum仓库到内网的参考. 注意:这里所有的标题都是根据主要的文章(Linux基础服务搭建综合)的顺序来做的. 如果需要 ...

  7. C++第四十七篇 -- VS2017带参数启动调试程序

    参考链接:https://www.cnblogs.com/kileyi/p/10163269.html 举例:Test_Bluetooth.exe -help Test_Bluetooth.cpp # ...

  8. 在Python中执行普通除法

    如果希望Python只执行普通的除法,那么可以在程序前加上以下语句: 1 from _future_ import division 如果通过命令行(比如在Linux系统上)运行Python,可以使用 ...

  9. 【Azure API 管理】在APIM中使用客户端证书验证API的请求,但是一直提示错误"No client certificate received."

    API 管理 (APIM) 是一种为现有后端服务创建一致且现代化的 API 网关的方法. 问题描述 在设置了APIM客户端证书,用户保护后端API,让请求更安全. 但是,最近发现使用客户端证书的API ...

  10. 犀牛Rhino 7.0中文版安装破解教程

    犀牛Rhino 7.0中文版是一款专业的.功能强大的三维建模软件,利用它可以创建.编辑.分析.提供.渲染.动画与转换NURBS线条.曲面.实体与多边形网格:它能轻易整合3DS MAX 与Softima ...