[Nacos] Nacos Client向Server发送注册请求和心跳请求 (二)

news2024/11/23 22:33:27

文章目录

      • 1.Nacos Client的自动注册原理和实现
      • 2.Naocs Client向Server发送注册请求
      • 3.Nacos Client向Server发送心跳请求

Nacos Client的任务: 向Server发送注册请求, 向Server发送心跳请求, Client获取所有的服务, Client定时更新本地服务, Client获取要调用服务的提供者列表

1.Nacos Client的自动注册原理和实现

 <dependency>
     <groupId>com.alibaba.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
 </dependency>

在这里插入图片描述

在这里插入图片描述

@Configuration(proxyBeanMethods = false)
@Import(AutoServiceRegistrationConfiguration.class)
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
		matchIfMissing = true)
public class AutoServiceRegistrationAutoConfiguration {

	@Autowired(required = false)
	private AutoServiceRegistration autoServiceRegistration;

	@Autowired
	private AutoServiceRegistrationProperties properties;

	@PostConstruct
	protected void init() {
		if (this.autoServiceRegistration == null && this.properties.isFailFast()) {
			throw new IllegalStateException("Auto Service Registration has "
					+ "been requested, but there is no AutoServiceRegistration bean");
		}
	}

}

在这里插入图片描述

在这里插入图片描述

注册方法: register()

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  1. NacosServiceRegistry
  2. NacosAutoServiceRegistration

在这里插入图片描述

AbstractAutoServiceRegistration.onApplicationEvent()

在这里插入图片描述

在这里插入图片描述

调用的是NacosServiceRegistry.register()

  • 为什么一启动的时候就注册:
    AbstractAutoServiceRegistration.onApplicationEvent(), 一启动的时候会调用NacosServiceRegistry.register(), 而NacosServiceRegistry是NacosDiscoveryAutoConfiguration类注入的。

2.Naocs Client向Server发送注册请求

根据之前的register()

在这里插入图片描述

NacosNamingService.registerInstance()

在这里插入图片描述

    @Override
    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
        // 生成的String格式为:groupId@@微服务名称
        String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
        // 若当前实例为临时实例,则向Server发送心跳
        if (instance.isEphemeral()) {
            // 构建一个心跳信息实例
            BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
            // 向server发送心跳(定时任务)
            beatReactor.addBeatInfo(groupedServiceName, beatInfo);
        }
        // 向Server发送注册请求
        serverProxy.registerService(groupedServiceName, groupName, instance);
    }

BeatInfo为心跳类, BeatReactor为生成心跳类的类

NamingProxy.registerService()

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

        NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, serviceName,
                instance);

        // 将instance拆散后写入到params中,并以请求参数的形式出现在请求中
        final Map<String, String> params = new HashMap<String, String>(16);
        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", JacksonUtils.toJson(instance.getMetadata()));
        // 提交一个POST请求
        reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);

    }

    public String reqApi(String api, Map<String, String> params, String method) throws NacosException {
        return reqApi(api, params, Collections.EMPTY_MAP, method);
    }

    public String reqApi(String api, Map<String, String> params, Map<String, String> body, String method)
            throws NacosException {
        // 第4个参数是获取到配置文件中指定的nacos server地址
        return reqApi(api, params, body, getServerList(), method);
    }

将instance拆散后写入到params中,并以请求参数的形式出现在请求中, 提交一个post请求

    public String reqApi(String api, Map<String, String> params, Map<String, String> body, List<String> servers,
            String method) throws NacosException {

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

        if (CollectionUtils.isEmpty(servers) && StringUtils.isEmpty(nacosDomain)) {
            throw new NacosException(NacosException.INVALID_PARAM, "no server available");
        }

        NacosException exception = new NacosException();

        // 遍历所有server,从中随机的选择一个server去连接,
        // 若该server连接失败,则采用轮询方式选择下一个去尝试连接,
        // 直到连接成功为止,或尝试次数为server数量
        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 {
                    // 连接server
                    return callServer(api, params, body, server, method);
                } catch (NacosException e) {
                    exception = e;
                    if (NAMING_LOGGER.isDebugEnabled()) {
                        NAMING_LOGGER.debug("request {} failed.", server, e);
                    }
                }
                index = (index + 1) % servers.size();  // 轮询
            }
        }

        if (StringUtils.isNotBlank(nacosDomain)) {
            // 默认尝试着连接三次
            for (int i = 0; i < UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT; i++) {
                try {
                    return callServer(api, params, body, nacosDomain, method);
                } catch (NacosException e) {
                    exception = e;
                    if (NAMING_LOGGER.isDebugEnabled()) {
                        NAMING_LOGGER.debug("request {} failed.", nacosDomain, e);
                    }
                }
            } // end-for
        }

        NAMING_LOGGER.error("request: {} failed, servers: {}, code: {}, msg: {}", api, servers, exception.getErrCode(),
                exception.getErrMsg());

        throw new NacosException(exception.getErrCode(),
                "failed to req API:" + api + " after all servers(" + servers + ") tried: " + exception.getMessage());

    }

在这里插入图片描述

    public String callServer(String api, Map<String, String> params, Map<String, String> body, String curServer,
            String method) throws NacosException {
        long start = System.currentTimeMillis();
        long end = 0;
        injectSecurityInfo(params);
        Header header = builderHeader();

        String url;
        // 构建请求url
        if (curServer.startsWith(UtilAndComs.HTTPS) || curServer.startsWith(UtilAndComs.HTTP)) {
            url = curServer + api;
        } else {
            if (!curServer.contains(UtilAndComs.SERVER_ADDR_IP_SPLITER)) {
                curServer = curServer + UtilAndComs.SERVER_ADDR_IP_SPLITER + serverPort;
            }
            url = NamingHttpClientManager.getInstance().getPrefix() + curServer + api;
        }

        try {
            // 提交请求
            HttpRestResult<String> restResult = nacosRestTemplate
                    .exchangeForm(url, header, Query.newInstance().initParams(params), body, method, String.class);
            end = System.currentTimeMillis();

            MetricsMonitor.getNamingRequestMonitor(method, url, String.valueOf(restResult.getCode()))
                    .observe(end - start);

            if (restResult.ok()) {
                return restResult.getData();
            }
            if (HttpStatus.SC_NOT_MODIFIED == restResult.getCode()) {
                return StringUtils.EMPTY;
            }
            throw new NacosException(restResult.getCode(), restResult.getMessage());
        } catch (Exception e) {
            NAMING_LOGGER.error("[NA] failed to request", e);
            throw new NacosException(NacosException.SERVER_ERROR, e);
        }
    }

NacosRestTemplate.java

	// NacosRestTemplate.java
    public <T> HttpRestResult<T> exchangeForm(String url, Header header, Query query, Map<String, String> bodyValues,
            String httpMethod, Type responseType) throws Exception {
        RequestHttpEntity requestHttpEntity = new RequestHttpEntity(
                header.setContentType(MediaType.APPLICATION_FORM_URLENCODED), query, bodyValues);
        // 提交请求
        return execute(url, httpMethod, requestHttpEntity, responseType);
    }

    private <T> HttpRestResult<T> execute(String url, String httpMethod, RequestHttpEntity requestEntity,
            Type responseType) throws Exception {
        URI uri = HttpUtils.buildUri(url, requestEntity.getQuery());
        if (logger.isDebugEnabled()) {
            logger.debug("HTTP method: {}, url: {}, body: {}", httpMethod, uri, requestEntity.getBody());
        }

        ResponseHandler<T> responseHandler = super.selectResponseHandler(responseType);
        HttpClientResponse response = null;
        try {
            // 获取到Nacos自定义的HttpClient,其就是对JDK中的HttpURLConnection的封装
            response = this.requestClient().execute(uri, httpMethod, requestEntity);
            return responseHandler.handle(response);
        } finally {
            if (response != null) {
                response.close();
            }
        }
    }

获取到Nacos自定义的HttpClient,其就是对JDK中的HttpURLConnection的封装,发送对应的post请求。

在这里插入图片描述

JdkHttpClientRequest.execute()

在这里插入图片描述

3.Nacos Client向Server发送心跳请求

NacosNamingService.registerInstance()

在这里插入图片描述

    public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
        NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
        // 形成的key的格式为:groupId@@微服务名称#ip#port
        // 这个key是固定了主机了
        String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
        BeatInfo existBeat = null;
        //fix #1733
        // dom2Beat是一个缓存map,其key为主机,value则为该主机发送的心跳beatInfo
        if ((existBeat = dom2Beat.remove(key)) != null) {
            existBeat.setStopped(true);
        }
        dom2Beat.put(key, beatInfo);
        // 开启一个定时任务
        executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
        MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
    }

开启一个定时任务来发送心跳。

在这里插入图片描述

BeatTask.run()

        @Override
        public void run() {
            if (beatInfo.isStopped()) {
                return;
            }
            long nextTime = beatInfo.getPeriod();
            try {
                // 发送心跳
                JsonNode result = serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled);
                long interval = result.get("clientBeatInterval").asLong();
                boolean lightBeatEnabled = false;
                if (result.has(CommonParams.LIGHT_BEAT_ENABLED)) {
                    lightBeatEnabled = result.get(CommonParams.LIGHT_BEAT_ENABLED).asBoolean();
                }
                BeatReactor.this.lightBeatEnabled = lightBeatEnabled;
                if (interval > 0) {
                    nextTime = interval;
                }
                int code = NamingResponseCode.OK;
                if (result.has(CommonParams.CODE)) {
                    code = result.get(CommonParams.CODE).asInt();
                }
                // 若在server端没有发现该client,则server返回的状态码为20404
                // 此时client会发起注册请求
                if (code == NamingResponseCode.RESOURCE_NOT_FOUND) {
                    Instance instance = new Instance();
                    instance.setPort(beatInfo.getPort());
                    instance.setIp(beatInfo.getIp());
                    instance.setWeight(beatInfo.getWeight());
                    instance.setMetadata(beatInfo.getMetadata());
                    instance.setClusterName(beatInfo.getCluster());
                    instance.setServiceName(beatInfo.getServiceName());
                    instance.setInstanceId(instance.getInstanceId());
                    instance.setEphemeral(true);
                    try {
                        // 向server发送注册请求
                        serverProxy.registerService(beatInfo.getServiceName(),
                                NamingUtils.getGroupName(beatInfo.getServiceName()), instance);
                    } catch (Exception ignore) {
                    }
                }
            } catch (NacosException ex) {
                NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}",
                        JacksonUtils.toJson(beatInfo), ex.getErrCode(), ex.getErrMsg());

            }
            // 又启动一次定时任务
            executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
        }

在这里插入图片描述

发送心跳: sendBeat(), 发送put请求。

在这里插入图片描述

若在server端没有发现该client,则server返回的状态码为20404, 此时client会发起注册请求

registerService(): 发送post请求

在这里插入图片描述

在run()方法最后有又启动一次定时任务。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/549828.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Robot Dynamics Lecture Notes学习笔记之关节空间动力学控制

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 关节空间动力学 关节空间动力学控制关节阻抗调节重力补偿逆动力学控制 关节空间动力学控制 目前的工业机器人几乎完全依赖于关节位置控制的概念。它们建立在PID控制器的基础…

MySQL数据库期末实验报告(含实验步骤和实验数据)

MYSQL实验 实验步骤 1.创建数据库salesmanage 2.创建数据表&#xff1a;员工表&#xff0c;部门表&#xff0c;销售表&#xff1b; &#xff08;1&#xff09;员工表&#xff08;(员工号(CHAR)&#xff0c;员工姓名(CHAR)&#xff0c;性别(CHAR)&#xff0c;年龄(INT)&…

前端部署项目后nginx转发接口404(页面正常)

目录 1.前言 2. 场景复现&#xff1a; 3.问题的原因&#xff1a; 4.使用nginx一般要注意的小细节&#xff1a; 1. location / 写在下面&#xff0c;其他的转发如/v1写在上面​编辑 2.如何查看nginx转发请求到哪里了&#xff1f; 3.怎么写自己的前端路径&#xff1f; 5.使…

实验六 自动驾驶建模与仿真

【实验目的】 了解Matlab/Simulink软件环境&#xff0c;熟悉Simulink建模步骤&#xff1b;了解车辆运动控制的基本原理&#xff0c;学会简单的车辆运动控制建模及仿真&#xff1b;了解自动驾驶建模的基本过程&#xff0c;了解典型ADAS系统模型的应用特点。了解自动驾驶相关函数…

【SpringCloud组件——Nacos】

前置准备&#xff1a; 分别提供订单系统&#xff08;OrderService&#xff09;和用户系统&#xff08;UserService&#xff09;。订单系统主要负责订单相关信息的处理&#xff0c;用户系统主要负责用户相关信息的处理。 一、服务注册与发现 1.1、在父工程当中引入Nacos依赖 …

JavaScript实现输入数值判断是否为质数、合数的代码

以下为实现输入数值判断是否为质数、合数的程序代码和运行截图 目录 前言 一、输入数值判断是否为质数、合数 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 前言 1.若有选择&#xff0c;您可以在目录里进行快速查找&#xff1b; 2.本博文代码可…

通讯录实现的需求分析和架构设计

本文实现的是通讯录产品的需求分析和架构设计&#xff0c;重点在于结构层次的设计&#xff0c;方便代码阅读和维护。 一、通讯录实现的需求分析 1、通讯录的功能清单 添加一个人员打印显示所有人员删除一个人员查找一个人员保存文件加载文件 2&#xff0c;数据存储信息 人员…

实际开发中一些实用的JS数据处理方法

写在开头 JavaScript 是一种脚本语言&#xff0c;最初是为了网页提供交互式前端功能而设计的&#xff0c;而现在&#xff0c;通过 Node.js&#xff0c;JavaScript 还可以用于编写服务器端代码。 JavaScript 具有动态性、基于原型的面向对象特性、弱类型、多范式、支持闭包执行…

Golang每日一练(leetDay0072) 课程表 I\II Course Schedule

目录 1. 课程表 Course Schedule I &#x1f31f;&#x1f31f; 2. 课程表 Course Schedule II &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一…

电子邮件协议(SMTP,MIME,POP3,IMAP)

SMTP 关键词&#xff1a; 电子邮件协议:SMTP简单邮件传输协议&#xff0c;负责将邮件上传到服务器&#xff0c;采用TCP的25端口&#xff0c;C/S工作。仅传送ASCII码文本 详细介绍&#xff1a; SMTP是一种提供可靠且有效的电子邮件传输的协议。SMTP是建立在FTP文件传输服务上…

学系统集成项目管理工程师(中项)系列23b_信息系统集成及服务管理(下)

1. 信息技术服务 1.1. 供方为需方提供如何开发、应用信息技术的服务&#xff0c;以及供方以信息技术为手段提供支持需方业务活动的服务 1.2. 信息技术咨询服务、设计与开发服务、信息系统集成服务、数据处理和运营服务及其他信息技术服务 2. 信息系统审计 2.1. 收集并评估证…

Golang中的协程(goroutine)

目录 进程 线程 并发 并行 协程(goroutine) 使用sync.WaitGroup等待协程执行完毕 多协程和多线程 进程 进程就是程序在操作系统中的一次执行过程&#xff0c;是系统进行资源分配和调度的基本单位&#xff0c;进程是一个动态概念&#xff0c;是程序在执行过程中分配和管理…

C语言_用VS2019写第一个C语言或C++程序

接上一篇&#xff1a;C语言简述、特点、常用编译器&#xff0c;VS2010写第一个C语言程序 本次来分享用VS2019来写C语言或C程序&#xff0c;也是补充上一篇的知识&#xff0c;话不多说&#xff0c;开始上菜&#xff1a; 此博主在CSDN发布的文章目录&#xff1a;我的CSDN目录&…

微信小程序nodejs+vue+uniapp超市网上购物商城系统

超市购物系统用户端要求在系统的安卓手机上可以运行&#xff0c;主要实现了管理端&#xff1b;首页、个人中心、用户管理、商品分类管理、商品信息管理、商品入库管理、订单信息管理、订单配送管理、订单评价管理、退货申请管理、换货申请管理、系统管理&#xff0c;用户端&…

总结857

学习目标&#xff1a; 月目标&#xff1a;5月&#xff08;张宇强化前10讲&#xff0c;背诵15篇短文&#xff0c;熟词僻义300词基础词&#xff09; 周目标&#xff1a;张宇强化前3讲并完成相应的习题并记录&#xff0c;英语背3篇文章并回诵 每日必复习&#xff08;5分钟&#…

4-《安卓进阶》

4-《安卓进阶》 1 Okhttp2 Retrofit3 Android常用图片库对比4 Glide原理手写图片加载框架思路5 Rxjava6 Android IPC机制&#xff08;面试八股文之一&#xff09;6.1.Android中进程和线程的区别6.2.IPC概念6.3.Android序列化与反序列化6.3.Android如何开启多进程&#xff1f;多…

MDIO总线

基于linux-3.14.16 首先要搞清楚总线的位置&#xff0c;即硬件上的位置 如上图&#xff0c;mdio总线是mac和phy之间的连接方式&#xff0c;主要用于配置配置phy的寄存器&#xff0c;所以phy应该是器的一类物理设备&#xff0c;mdio总线驱动和总线设备都是围绕phy工作的。 一…

一图看懂 async_timeout 模块:异步 I/O 的超时设置,资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 async_timeout 模块&#xff1a;异步 I/O 的超时设置&#xff0c;资料整理笔记&#xff08;大全&#xff09; &#x1f9ca;摘要&#x1f9ca;模块图&#x1f9ca;类关系图…

chatgpt赋能Python-pythonfrozenset

Python frozenset介绍 在Python中&#xff0c;可以通过frozenset创建不可变集合。与set不同&#xff0c;frozenset一旦被创建就无法修改。frozenset通常用于作为字典的键&#xff0c;因为字典键必须是不可变的。 如何创建frozenset frozenset可以通过将可迭代对象作为参数传…

Blazor实战——Known框架快速开始

Known是基于C#和Blazor开发的前后端分离快速开发框架&#xff0c;开箱即用&#xff0c;跨平台&#xff0c;一处代码&#xff0c;多处运行。 开源地址: https://gitee.com/known/Known 1. 安装项目模板并创建新项目 打开命令行输入如下命令安装和创建。 -- 安装模板 dotnet n…