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

文章目录

  • 前言
  • 一、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

    @Overridepublic 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 rulesetMigrationRule(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 rulelogger.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 MigrationInvokerreturn true;}

7、MigrationClusterInvoker.migrateToApplicationFirstInvoker

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

    @Overridepublic void migrateToApplicationFirstInvoker(MigrationRule newRule) {CountDownLatch latch = new CountDownLatch(0);// 刷新接口级InvokerrefreshInterfaceInvoker(latch);// 刷新应用级InvokerrefreshServiceDiscoveryInvoker(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();}// 创建invokerinvoker = 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();}// 获取应用级invokerserviceDiscoveryInvoker = 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;}// 通过阈值指定invokerSet<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 promotionif (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);// 创建invokerreturn doCreateInvoker(directory, cluster, registry, type);}

服务目录(RegistryDirectory),负责框架与注册中心的订阅,并动态更新本地Invoker列表、路由列表、配置信息的逻辑

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

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

相关文章

初识命名空间

1.创建两个命名空间 ip netns add host1 ip netns add host2 2. 查看命名空间 ip netns ls 3 、 创建veth ip -netns host1 link add veth0 type veth peer name host1-peer 4、 查看命名空间接口 ip -netns host1 address 5、 把host1-peer移动到host2命名空间 ip -ne…

ctfshow-nodejs

什么是nodejs Node.js 是一个基于 Chrome V8 引擎的 Javascript 运行环境。可以说nodejs是一个运行环境&#xff0c;或者说是一个 JS 语言解释器 Nodejs 是基于 Chrome 的 V8 引擎开发的一个 C 程序&#xff0c;目的是提供一个 JS 的运行环境。最早 Nodejs 主要是安装在服务器…

SAP B1 基础实操 - 用户定义字段 (UDF)

目录 一、功能介绍 1. 使用场景 2. 操作逻辑 3. 常用定义部分 3.1 主数据 3.2 营销单据 4. 字段设置表单 4.1 字段基础信息 4.2 不同类详细设置 4.3 默认值/必填 二、案例 1 要求 2 操作步骤 一、功能介绍 1. 使用场景 在实施过程中&#xff0c;经常会碰见用户需…

Jmeter使用时小技巧添加“泊松随机定时器“模拟用户思考时间

1、模拟用户思考时间&#xff0c;添加"泊松随机定时器"

SQL Server导入导出

SQL Server导入导出 导出导入 这里已经安装好了SQL Server&#xff0c;也已经创建了数据库和表。现在想导出来给别人使用&#xff0c;所以需要导入导出功能。环境&#xff1a;SQL Server 2012 SP4 如果没有安装&#xff0c;可以查看安装教程&#xff1a; Microsoft SQL Server …

装WebVideoCreator记录

背景&#xff0c;需要在docker容器内配置WebVideoCreator环境&#xff0c;配置npm、node.js WebVideoCreator地址&#xff1a;https://github.com/Vinlic/WebVideoCreator 配置环境&#xff0c;使用这个教程&#xff1a; linux下安装node和npm_linux离线安装npm-CSDN博客 1…

JavaWeb - Mybatis - 基础操作

删除Delete 接口方法&#xff1a; Mapper public interface EmpMapper { //Delete("delete from emp where id 17") //public void delete(); //以上delete操作的SQL语句中的id值写成固定的17&#xff0c;就表示只能删除id17的用户数据 //SQL语句中的id值不能写成…

[数据集][目标检测]西红柿成熟度检测数据集VOC+YOLO格式3241张5类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;3241 标注数量(xml文件个数)&#xff1a;3241 标注数量(txt文件个数)&#xff1a;3241 标注…

GitHub精选|8 个强大工具,助力你的开发和探究工作

本文精选了8个来自 GitHub 的优秀项目&#xff0c;涵盖了 低代码、报表工具、Web 开发、云原生、通知管理、构建系统、生物计算、位置追踪、API 规范和依赖更新等方面&#xff0c;为开发者和研究人员提供了丰富的资源和灵感。 目录 1.防弹 React&#xff1a;构建强大的 Web 应…

第十周:机器学习笔记

第十周机器学习周报 摘要Abstract机器学习——self-attention&#xff08;注意力机制&#xff09;1. 为什么要用self-attention2. self-attention 工作原理2.1 求α的两种方式2.2 attention-score&#xff08;关联程度&#xff09; Pytorch学习1. 损失函数代码实战1.1 L1loss&a…

电路分析 ---- 加法器

1 同相加法器 分析过程 虚短&#xff1a; u u − R G R G R F u O u_{}u_{-}\cfrac{R_{G}}{R_{G}R_{F}}u_{O} u​u−​RG​RF​RG​​uO​ i 1 u I 1 − u R 1 i_{1}\cfrac{u_{I1}-u_{}}{R_{1}} i1​R1​uI1​−u​​&#xff1b; i 2 u I 2 − u R 2 i_{2}\cfrac{u_{…

如何判断小程序是运行在“企业微信”中的还是运行在“微信”中的?

如何判断小程序是运行在“企业微信”中的还是运行在“微信”中的&#xff1f; 目录 如何判断小程序是运行在“企业微信”中的还是运行在“微信”中的&#xff1f; 一、官方开发文档 1.1、“微信小程序”开发文档的说明 1.2、“企业微信小程序”开发文档的说明 1.3、在企业…

无线信道中ph和ph^2的场景

使用 p h ph ph的情况&#xff1a; Rayleigh 分布的随机变量可以通过两个独立且相同分布的零均值、高斯分布的随机变量表示。设两个高斯随机变量为 X ∼ N ( 0 , σ 2 ) X \sim \mathcal{N}(0, \sigma^2) X∼N(0,σ2)和 Y ∼ N ( 0 , σ 2 ) Y \sim \mathcal{N}(0, \sigma^2)…

终端协会发布《移动互联网应用程序(App)自动续费测评规范》

随着移动互联网的快速发展&#xff0c;App自动续费服务已成为许多应用的标配&#xff0c;但同时也引发了不少消费者的投诉和不满。为了规范这一市场行为&#xff0c;保护消费者的合法权益&#xff0c;电信终端协会&#xff08;TAF&#xff09;发布了《移动互联网应用程序&#…

代码随想录 刷题记录-28 图论 (5)最短路径

一、dijkstra&#xff08;朴素版&#xff09;精讲 47. 参加科学大会 思路 本题就是求最短路&#xff0c;最短路是图论中的经典问题即&#xff1a;给出一个有向图&#xff0c;一个起点&#xff0c;一个终点&#xff0c;问起点到终点的最短路径。 接下来讲解最短路算法中的 d…

网络层 V(IPv6)【★★★★★★】

一、IPv6 的特点 IP 是互联网的核心协议。现在使用的 IP&#xff08;即 IPv4 ) 是在 20 世纪 70 年代末期设计的。互联网经过几十年的飞速发展&#xff0c;到 2011 年 2 月&#xff0c;IPv4 的地址已经耗尽&#xff0c; ISP 已经不能再申请到新的 IP 地址块了。我国在 2014 年…

梨花声音教育退费普通话学习技巧之了解文化背景

在学习普通话的过程中&#xff0c;了解中国的文化背景是不可或缺的一环。语言不仅是交流的工具&#xff0c;更是文化的载体。通过深入了解中国的历史、文化和社会背景&#xff0c;学习者可以更好地理解和掌握普通话&#xff0c;使语言学习变得更加生动有趣。本文将从几个方面详…

【iOS】属性关键字

目录 深浅拷贝 自定义类 容器类深拷贝 属性关键字 原子操作 atomic nonatomic 读写权限 readwrite readonly 内存管理 weak assign strong retian copy strong与copy 补充 属性关键字格式 ARC下property的默认属性 深浅拷贝 关于深浅拷贝&#xff0c;笔者在…

ClickHouse的安装教程

ClickHouse的安装教程 文章目录 ClickHouse的安装教程写在前面准备工作关闭防火墙CentOS 取消打开文件数限制安装依赖CentOS 取消 SELINUX 单机安装在 **node01** 的/opt/software 下创建 clickhouse 目录将下载的文件上传到 node01 的 /opt/software/clickhouse 目录下将安装文…

FPGA第 9 篇,Verilog 中的关键字和基数

前言 在 Verilog 中&#xff0c;关键字&#xff08;Keywords&#xff09;和基数&#xff08;Radix&#xff09;是语言的重要组成部分&#xff0c;它们有助于描述和定义硬件设计。上期分享了 Verilog 的基本使用&#xff0c;以及数据类型、逻辑值和算数运算符的简单应用&#x…