dubbo 服务消费原理分析之应用级服务发现

news2024/12/23 15:56:12

文章目录

  • 前言
  • 一、MigrationRuleListener
    • 1、迁移状态模型
    • 2、Provider 端升级
    • 3、Consumer 端升级
    • 4、服务消费选址
    • 5、MigrationRuleListener.onRefer
    • 6、MigrationRuleHandler.doMigrate
    • 6、MigrationRuleHandler.refreshInvoker
    • 7、MigrationClusterInvoker.migrateToApplicationFirstInvoker
    • 8、MigrationInvoker.refreshInterfaceInvoker
    • 9、MigrationInvoker.refreshServiceDiscoveryInvoker
    • 10、MigrationInvoker.calcPreferredInvoker
    • 11、RegistryProtocol.getServiceDiscoveryInvoker


前言

文章基于3.1.0版本进行分析

		<dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>3.1.0</version>
        </dependency>

一、MigrationRuleListener

1、迁移状态模型

在 Dubbo 3 之前地址注册模型是以接口级粒度注册到注册中心的,而 Dubbo 3 全新的应用级注册模型注册到注册中心的粒度是应用级的。从注册中心的实现上来说是几乎不一样的,这导致了对于从接口级注册模型获取到的 invokers 是无法与从应用级注册模型获取到的 invokers 进行合并的。
为了帮助用户从接口级往应用级迁移,Dubbo 3 设计了 Migration 机制,基于三个状态的切换实现实际调用中地址模型的切换

图片3.1

当前共存在三种状态,FORCE_INTERFACE(强制接口级),APPLICATION_FIRST(应用级优先)、FORCE_APPLICATION(强制应用级)。
FORCE_INTERFACE:只启用兼容模式下接口级服务发现的注册中心逻辑,调用流量 100% 走原有流程
APPLICATION_FIRST:开启接口级、应用级双订阅,运行时根据阈值和灰度流量比例动态决定调用流量走向
FORCE_APPLICATION:只启用新模式下应用级服务发现的注册中心逻辑,调用流量 100% 走应用级订阅的地址

2、Provider 端升级

在不改变任何 Dubbo 配置的情况下,可以将一个应用或实例升级到 3.x 版本,升级后的 Dubbo 实例会默保保证与 2.x 版本的兼容性,即会正常注册 2.x 格式的地址到注册中心,因此升级后的实例仍会对整个集群仍保持可见状态。

图片3.2

3、Consumer 端升级

对于双订阅的场景,消费端虽然可同时持有 2.x 地址与 3.x 地址,但选址过程中两份地址是完全隔离的:要么用 2.x 地址,要么用 3.x 地址,不存在两份地址混合调用的情况,这个决策过程是在收到第一次地址通知后就完成了的。
图片3.3

4、服务消费选址

调用发生前,框架在消费端会有一个“选址过程”,注意这里的选址和之前 2.x 版本是有区别的,选址过程包含了两层筛选

  • 先进行地址列表(ClusterInvoker)筛选(接口级地址 or 应用级地址)
  • 再进行实际的 provider 地址(Invoker)筛选

图3.4

双注册带来的资源消耗
双注册不可避免的会带来额外的注册中心存储压力,但考虑到应用级地址发现模型的数据量在存储方面的极大优势,即使对于一些超大规模集群的用户而言,新增的数据量也并不会带来存储问题。总体来说,对于一个普通集群而言,数据增长可控制在之前数据总量的 1/100 ~ 1/1000

以一个中等规模的集群实例来说: 2000 实例、50个应用(500 个 Dubbo 接口,平均每个应用 10 个接口)。
​假设每个接口级 URL 地址平均大小为 5kb,每个应用级 URL 平均大小为 0.5kb
老的接口级地址量:2000 * 500 * 5kb ≈ 4.8G
​新的应用级地址量:2000 * 50 * 0.5kb ≈ 48M
​双注册后仅仅增加了 48M 的数据量。

接下来分析MigrationRuleListener的实际处理逻辑

5、MigrationRuleListener.onRefer

    @Override
    public void onRefer(RegistryProtocol registryProtocol, ClusterInvoker<?> invoker, URL consumerUrl, URL registryURL) {
		MigrationRuleHandler<?> migrationRuleHandler = handlers.computeIfAbsent((MigrationInvoker<?>) invoker, _key -> {
            ((MigrationInvoker<?>) invoker).setMigrationRuleListener(this);
            return new MigrationRuleHandler<>((MigrationInvoker<?>) invoker, consumerUrl);
        });

        // 迁移规则执行 rule是封装了迁移的配置规则的信息对应类型MigrationRule类型,在初始化对象的时候进行了配置初始化
		migrationRuleHandler.doMigrate(rule);
    }

6、MigrationRuleHandler.doMigrate

从url中读取迁移方式和阈值,用于进行迁移的调用决策

  • 获取迁移方式
  • 获取决策阈值(默认-1.0)
	public synchronized void doMigrate(MigrationRule rule) {
        if (migrationInvoker instanceof ServiceDiscoveryMigrationInvoker) {
            refreshInvoker(MigrationStep.FORCE_APPLICATION, 1.0f, rule);
            return;
        }

        // initial step : APPLICATION_FIRST
		// 迁移方式,MigrationStep 一共有3种枚举情况:FORCE_INTERFACE, APPLICATION_FIRST, FORCE_APPLICATION
		// 默认 APPLICATION_FIRST 开启接口级、应用级双订阅
        MigrationStep step = MigrationStep.APPLICATION_FIRST;
        float threshold = -1f;

        try {	
			// URL中获取迁移方式
			step = rule.getStep(consumerURL);
			// threshold: 决策阈值(默认-1.0)计算与获取
            threshold = rule.getThreshold(consumerURL);
        } catch (Exception e) {
            logger.error("Failed to get step and threshold info from rule: " + rule, e);
        }

        // 刷新调用器对象 来进行决策服务发现模式
		if (refreshInvoker(step, threshold, rule)) {
            // refresh success, update rule
            setMigrationRule(rule);
        }
    }

6、MigrationRuleHandler.refreshInvoker

通过迁移配置和当前提供者注册信息来决定创建什么类型的调用器对象(Invoker)来为后续服务调用做准备

	private boolean refreshInvoker(MigrationStep step, Float threshold, MigrationRule newRule) {
        if (step == null || threshold == null) {
            throw new IllegalStateException("Step or threshold of migration rule cannot be null");
        }
        MigrationStep originStep = currentStep;

        if ((currentStep == null || currentStep != step) || !currentThreshold.equals(threshold)) {
            boolean success = true;
            switch (step) {
                // 接口和应用双订阅,默认
				case APPLICATION_FIRST:
                    migrationInvoker.migrateToApplicationFirstInvoker(newRule);
                    break;
                // 仅应用
				case FORCE_APPLICATION:
                    success = migrationInvoker.migrateToForceApplicationInvoker(newRule);
                    break;
                // 仅接口
				case FORCE_INTERFACE:
                default:
                    success = migrationInvoker.migrateToForceInterfaceInvoker(newRule);
            }

            if (success) {
				// 设置迁移模式和阈值
                setCurrentStepAndThreshold(step, threshold);
                logger.info("Succeed Migrated to " + step + " mode. Service Name: " + consumerURL.getDisplayServiceKey());
                report(step, originStep, "true");
            } else {
                // migrate failed, do not save new step and rule
                logger.warn("Migrate to " + step + " mode failed. Probably not satisfy the threshold you set "
                        + threshold + ". Please try re-publish configuration if you still after check.");
                report(step, originStep, "false");
            }

            return success;
        }
        // ignore if step is same with previous, will continue override rule for MigrationInvoker
        return true;
    }

7、MigrationClusterInvoker.migrateToApplicationFirstInvoker

默认使用接口级和应用级双订阅来兼容迁移的服务的
对应实现类 MigrationInvoker.migrateToApplicationFirstInvoker

    @Override
    public void migrateToApplicationFirstInvoker(MigrationRule newRule) {
        CountDownLatch latch = new CountDownLatch(0);
        // 刷新接口级Invoker
		refreshInterfaceInvoker(latch);
		// 刷新应用级Invoker
        refreshServiceDiscoveryInvoker(latch);

        // directly calculate preferred invoker, will not wait until address notify
        // calculation will re-occurred when address notify later
		//  计算当前使用应用级还是接口级服务发现的Invoker对象
        calcPreferredInvoker(newRule);
    }

8、MigrationInvoker.refreshInterfaceInvoker

刷新接口级Invoker

	protected void refreshInterfaceInvoker(CountDownLatch latch) {
        // 去除旧的监听
		clearListener(invoker);
		// 需要更新
        if (needRefresh(invoker)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Re-subscribing interface addresses for interface " + type.getName());
            }

            if (invoker != null) {
                invoker.destroy();
            }
			// 创建invoker
            invoker = registryProtocol.getInvoker(cluster, registry, type, url);
        }
        setListener(invoker, () -> {
            latch.countDown();
            if (reportService.hasReporter()) {
                reportService.reportConsumptionStatus(
                    reportService.createConsumptionReport(consumerUrl.getServiceInterface(), consumerUrl.getVersion(), consumerUrl.getGroup(), "interface"));
            }
            if (step == APPLICATION_FIRST) {
                calcPreferredInvoker(rule);
            }
        });
    }

9、MigrationInvoker.refreshServiceDiscoveryInvoker

刷新应用级Invoker

	protected void refreshServiceDiscoveryInvoker(CountDownLatch latch) {
        clearListener(serviceDiscoveryInvoker);
        if (needRefresh(serviceDiscoveryInvoker)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Re-subscribing instance addresses, current interface " + type.getName());
            }

            if (serviceDiscoveryInvoker != null) {
                serviceDiscoveryInvoker.destroy();
            }
			// 获取应用级invoker
            serviceDiscoveryInvoker = registryProtocol.getServiceDiscoveryInvoker(cluster, registry, type, url);
        }
        setListener(serviceDiscoveryInvoker, () -> {
            latch.countDown();
            if (reportService.hasReporter()) {
                reportService.reportConsumptionStatus(
                    reportService.createConsumptionReport(consumerUrl.getServiceInterface(), consumerUrl.getVersion(), consumerUrl.getGroup(), "app"));
            }
            if (step == APPLICATION_FIRST) {
                calcPreferredInvoker(rule);
            }
        });
    }

10、MigrationInvoker.calcPreferredInvoker

计算当前使用应用级还是接口级服务发现的Invoker对象

	private synchronized void calcPreferredInvoker(MigrationRule migrationRule) {
        if (serviceDiscoveryInvoker == null || invoker == null) {
            return;
        }
        // 通过阈值指定invoker
		Set<MigrationAddressComparator> detectors = ScopeModelUtil.getApplicationModel(consumerUrl == null ? null : consumerUrl.getScopeModel())
            .getExtensionLoader(MigrationAddressComparator.class).getSupportedExtensionInstances();
        if (CollectionUtils.isNotEmpty(detectors)) {
            // pick preferred invoker
            // the real invoker choice in invocation will be affected by promotion
            if (detectors.stream().allMatch(comparator -> comparator.shouldMigrate(serviceDiscoveryInvoker, invoker, migrationRule))) {
                this.currentAvailableInvoker = serviceDiscoveryInvoker;
            } else {
                this.currentAvailableInvoker = invoker;
            }
        }
    }

11、RegistryProtocol.getServiceDiscoveryInvoker

这里主要做了两件事

  • 创建注册中心目录
  • 通过代理对象的invoker
    public <T> ClusterInvoker<T> getServiceDiscoveryInvoker(Cluster cluster, Registry registry, Class<T> type, URL url) {
        // 创建注册中心目录
		DynamicDirectory<T> directory = new ServiceDiscoveryRegistryDirectory<>(type, url);
		// 创建invoker
        return doCreateInvoker(directory, cluster, registry, type);
    }

服务目录(RegistryDirectory),负责框架与注册中心的订阅,并动态更新本地Invoker列表、路由列表、配置信息的逻辑
对服务目录的分析在 dubbo 服务消费原理分析之服务目录 讲解

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

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

相关文章

多维时序 | Matlab基于SSA-SVR麻雀算法优化支持向量机的数据多变量时间序列预测

多维时序 | Matlab基于SSA-SVR麻雀算法优化支持向量机的数据多变量时间序列预测 目录 多维时序 | Matlab基于SSA-SVR麻雀算法优化支持向量机的数据多变量时间序列预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab基于SSA-SVR麻雀算法优化支持向量机的数据多变…

【绿盟科技盟管家-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

[答疑]京酱肉丝可以提炼成一个类吗?

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 &#xff08;匿&#xff09; 2024-9-3 15:02 老师&#xff0c;我也经常遇到这样的困惑&#xff0c;领域概念应该提炼到什么层次&#xff0c;像京酱肉丝什么条件下可以定成子类&#x…

百元以下蓝牙耳机性价比之王品牌?四大高能性价比机型推荐

面对市场上琳琅满目的蓝牙耳机品牌和型号&#xff0c;消费者往往难以抉择&#xff0c;特别是当预算限定在百元以下时&#xff0c;找到一款既满足基本功能又具备一定品质的蓝牙耳机变得尤其困难&#xff0c;那么百元以下蓝牙耳机性价比之王品牌&#xff1f;尽管价格是一个重要的…

位运算+前缀和+预处理,CF 1017D - The Wu

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 1017D - The Wu 二、解题报告 1、思路分析 我们注意到 sum(w[])会很大但…

JKTECH柔性振动盘智能柔性上料机

柔性振动盘&#xff1a;重塑自动化上料新纪元 在快速发展的智能制造领域&#xff0c;面对复杂多变的物料需求&#xff0c;传统的上料方式已难以满足高效、精准、灵活的生产要求。柔性振动盘&#xff0c;作为自动化上料技术的革新者&#xff0c;正以其独特的优势&#xff0c;引…

comfyui攻略:故障报错应对指南!

前言 ComfyUI的常见故障和解决&#xff0c;赶紧收藏起来&#xff0c; 在探索ComfyUI的曲折旅途中&#xff0c;最让人心生畏惧的莫过于那漫天的红色方框和层出不穷的报错信息。它们如同不息的风暴&#xff0c;一波未平&#xff0c;一波又起&#xff0c;令无数热忱的初学者在这…

Dart 3.5更新对普通开发者有哪些影响?

哈喽&#xff0c;我是老刘 Flutter 3.24以及Dart 3.5不久前发布了。 突然觉得时间过得好快。六年前刚开始使用Flutter 1.0的场景还在眼前。 之前写了一篇文章盘点Flutter 3.24的新功能对普通开发者有哪些影响。Flutter 3.24 对普通开发者有哪些影响&#xff1f;https://mp.wei…

两个方法,设置Word打开密码!

想要保护word文件中的内容&#xff0c;我们可以给文件设置一个打开密码&#xff0c;这样只有知道密码的人才能够打开查看文件&#xff0c;今天分享两个word文件设置打开密码的方法。 方法一&#xff1a; 打开word文档后&#xff0c;点击【文件】-【信息】-【保护文档】这里有…

推荐让你事半功倍的5款实用软件

​ 今天我要向大家推荐5款超级好用的效率软件&#xff0c;无论是在学习还是办公中都能够极大地提高效率。这些软件可以帮助你解决许多问题&#xff0c;而且每个都是真正的神器。 1. 音乐管理——MusicBee ​ MusicBee是一款功能强大的音乐管理和播放软件&#xff0c;适合音乐…

STM32CubeMx学习笔记——GPIO使用

一、新建工程 1、选择芯片型号 2、配置时钟RCC 选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器) ​ 3、时钟树配置 在clock Configuration中将HCLK配置为最高频率然后回车 ​ 4、选择调试模式 SYS 设置&#xff0c;选择 Debug 为 Serial Wire …

iOS开发者新技能:将Flutter集成到你的原生应用中

如何在iOS原生项目中嵌入Flutter 一、创建一个新的iOS项目 首先&#xff0c;在 Xcode 中创建一个新的 iOS 项目&#xff0c;选择 Swift 作为开发语言&#xff0c;因为它的语法简洁&#xff0c;易于理解。 二、打包Flutter项目 在将 Flutter 模块集成到 iOS 项目之前&#xf…

Windows conda常用方法

这里写目录标题 conda链接jupyter conda链接jupyter 列出当前所有环境&#xff1a; conda env list 创建新环境&#xff1a; conda create -n your_env_name pythonX.X&#xff08;2.7、3.6、3.8等&#xff09; 激活环境&#xff1a; conda activate your_env_name 链接jupyte…

大模型微调有必要做吗?LoRa还是RAG?

我需要对大模型做微调吗&#xff1f; 想自定义大模型时&#xff0c;选择&#xff1a;微调还是RAG还是ICL&#xff1f; 需要对大模型做微调&#xff1f; 在人工智能的世界里&#xff0c;大型语言模型&#xff08;LLM&#xff09;已经成为了我们探索未知、解决问题的得力助手。…

uniapp业务实现

uni.requset添加异常判断提示,以及加载动画 /*** 该函数用于发送网络请求获取数据* 请求失败时会弹出相应的错误提示* 请求成功时会检查返回的数据是否存在错误&#xff0c;并根据错误代码做出相应处理* 如果数据请求成功且无错误&#xff0c;则将返回的数据赋值给pets变量*/fu…

NVIDIA Triton Inference Server 部署 yolov5

文章目录 一、拉取 tensorrt 、yolov5、tritonserver 镜像二、下载 yolov5-6.2、tensorrtx/yolov5-6.2源码三、pt转wts四、wts转engine五、创建triton推理服务器六、创建客户端进行测试 一、拉取 tensorrt 、yolov5、tritonserver 镜像 docker pull hakuyyf/tensorrtx:trt8.2_…

群晖NAS本地部署Photopea在线图片PS编辑工具

文章目录 前言1. 部署Photopea2. 运行Photopea3. 群晖安装Cpolar4. 配置公网地址5. 公网访问测试6. 固定公网地址 前言 本文主要介绍如何在群晖NAS本地部署Photopea在线图片PS编辑工具&#xff0c;并结合cpolar内网穿透实现公网环境远程访问本地部署的Photopea处理图片. Phot…

JAVA开源项目 员工绩效考核系统 计算机毕业设计

本文项目编号 T 021 &#xff0c;文末自助获取源码 \color{red}{T021&#xff0c;文末自助获取源码} T021&#xff0c;文末自助获取源码 目录 一、系统介绍1.1 业务分析1.2 用例分析 二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行…

梧桐数据库|中秋节活动·抽奖领取大闸蟹

有话说 众所周不知&#xff0c;我的工作就是做一个国产的数据库产品—中国移动梧桐数据库&#xff08;简称WuTongDB&#xff09;。 近期我们举办了一次小活动&#xff0c;来提升梧桐数据库的搜索量和知名度&#xff0c;欢迎大家来参加&#xff0c;免费抽奖领取大闸蟹哦~~~ 具…

AIGC入门:Comfyui整合包,解压即用!

前言 今天给大家分享的Comfyui的整合包&#xff0c;无需复杂的操作&#xff0c;解压即可使用。 整合包已经打包好了&#xff0c;获取方式放在&#x1f447;&#xff0c;需要的朋友可以自行领取哦。 什么是Comfyui ComfyUI采用节点式的操作方式&#xff0c;这种方式让用户能…