Dubbo 通信流程 - 服务的调用

news2025/4/2 2:49:13

Dubbo 客户端的使用

在 Dubbo 应用中,往类成员注解 @DubboReference,服务启动后便可以调用到远端:

@Component
public class InvokeDemoFacade {

    @Autowired
    @DubboReference
    private DemoFacade demoFacade;

    public String hello(String name){
    	// 经过网络调用到服务端的 DemoFacade
        return demoFacade.sayHello(name);
    }
}

在 Dubbo 通信流程 - 客户端代理对象的创建 中讲到,Dubbo 会为注解了 @DubboReference 的 Bean 创建代理对象并注册到 Spring 容器中。对类成员进行依赖注入时,Spring 会调用工厂对象 ReferenceBean 的 getObject 方法获取 Bean,该方法返回一个懒加载的代理对象。

所以,当调用一个注解了 @DubboReference 对象的方法时,调用的实际是其代理对象的方法:

public class LazyTargetInvocationHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	if (target == null) {
            target = lazyTargetSource.getTarget();
        }
    	...
            try {
                return method.invoke(target, args);
            } catch (InvocationTargetException exception) {
                Throwable targetException = exception.getTargetException();
                if (targetException != null) {
                    throw targetException;
                }
            }
        ...
	}
}

Dubbo 客户端的调用流程

在 Dubbo 通信流程 - 客户端代理对象的创建 中分析到,上述 target 是 InvokerInvocationHandler 类型,在这里再回忆一下:

// ReferenceBean.class
    private Object getCallProxy() throws Exception {
        if (referenceConfig == null) {
            synchronized (LockUtils.getSingletonMutex(applicationContext)) {
                if (referenceConfig == null) {
                    referenceBeanManager.initReferenceBean(this);
                    applicationContext
                            .getBean(
                                    DubboConfigApplicationListener.class.getName(),
                                    DubboConfigApplicationListener.class)
                            .init();
                    logger.warn(
                            CONFIG_DUBBO_BEAN_INITIALIZER,
                            "",
                            "",
                            "ReferenceBean is not ready yet, please make sure to "
                                    + "call reference interface method after dubbo is started.");
                }
            }
        }
        // get reference proxy
        // Subclasses should synchronize on the given Object if they perform any sort of extended singleton creation
        // phase.
        // In particular, subclasses should not have their own mutexes involved in singleton creation, to avoid the
        // potential for deadlocks in lazy-init situations.
        // The redundant type cast is to be compatible with earlier than spring-4.2
        if (referenceConfig.configInitialized()) {
            return referenceConfig.get();
        }
        synchronized (LockUtils.getSingletonMutex(applicationContext)) {
            return referenceConfig.get();
        }
    }

    private class DubboReferenceLazyInitTargetSource implements LazyTargetSource {
        @Override
        public Object getTarget() throws Exception {
            return getCallProxy();
        }
    }
}

InvokerInvocationHandler 中,使用了 invoker + rpcInvocation 传入 InvocationUtil 工具类:

public class InvokerInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ...
        // RpcInvocation 封装了这次请求的相关信息
        RpcInvocation rpcInvocation = new RpcInvocation(
                serviceModel,
                method.getName(),
                invoker.getInterface().getName(),
                protocolServiceKey,
                method.getParameterTypes(),
                args);

        if (serviceModel instanceof ConsumerModel) {
            rpcInvocation.put(Constants.CONSUMER_MODEL, serviceModel);
            rpcInvocation.put(Constants.METHOD_MODEL, ((ConsumerModel) serviceModel).getMethodModel(method));
        }
        return InvocationUtil.invoke(invoker, rpcInvocation);
    }
}

上述 invoker 是 InvokerInvocationHandler 的成员变量,其实际类型是 MigrationInvoker。MigrationInvoker 是流量切换的核心组件,它的核心作用是在服务迁移期间,对服务调用流量进行灵活的控制和管理,实现新旧服务提供者之间的平稳切换,避免因服务迁移给业务带来影响。

MigrationInvoker
流量切换:根据预设的规则,逐步将服务调用的流量从旧的服务提供者转移到新的服务提供者。
服务兼容:在迁移过程中,同时支持对旧服务和新服务的调用,确保业务的连续性。
数据对比:在流量切换过程中,可以对新旧服务的调用结果进行对比,以验证新服务的正确性。

MigrationInvoker 的 invoker 是 ScopeClusterInvoker,ScopeClusterInvoker 是 Dubbo 框架里的一个关键组件,其核心作用是依据服务调用的范围(Scope)对服务调用进行集群管理,实现对不同范围的服务提供者进行有效的调用和容错处理。

ScopeClusterInvoker
支持多范围的服务调用:在复杂的分布式系统里,服务可能会存在于不同的范围中,例如不同的数据中心、不同的业务单元等。ScopeClusterInvoker 可以按照预先设定的范围,对服务提供者进行分组管理。当服务消费者发起调用时,它能够精准地在特定范围的服务提供者中挑选合适的节点进行调用。
实现服务调用的隔离:借助对不同范围的服务提供者进行隔离调用,ScopeClusterInvoker 可以防止某个范围的服务故障影响到其他范围的服务。例如,当一个数据中心出现故障时,服务调用会被限制在其他正常的数据中心内进行,这样就能保证服务的可用性和稳定性。
动态调整调用范围:在系统运行过程中,ScopeClusterInvoker 能够根据实际情况动态地调整服务调用的范围。例如,当某个范围的服务提供者性能下降时,可以减少对该范围的调用,将更多的流量导向其他性能较好的范围。这种动态调整的能力可以提高系统的整体性能和资源利用率。
容错和负载均衡:ScopeClusterInvoker 集成了 Dubbo 的容错和负载均衡机制。在每个范围内,它会依据配置的负载均衡策略(如随机、轮询、最少活跃调用数等)选择合适的服务提供者进行调用。同时,当调用失败时,它会按照预设的容错策略(如失败重试、失败快速返回等)进行处理,确保服务调用的可靠性。

MigrationInvoker 的 invoker 是 MockClusterInvoker,MockClusterInvoker 是一个集群调用器,它会在服务调用时拦截调用请求,并根据配置的 Mock 策略来决定是调用实际的服务提供者还是返回 Mock 结果。

经过一系列的 Invoker 后,调用来到 AbstractCluster,AbstractCluster 调用 FilterChainBuilder 的 invoke 方法,进入过滤器链,依次经过 FutureFilter、MonitorFilter 等过滤器后,进入 FailoverClusterInvoker,FailoverClusterInvoker 在服务调用失败时,通过重试机制选择其他可用的服务提供者进行调用,以此保证服务调用的可靠性。

继续 Debug,来到 DubboInvoker:

///                  
// org.apache.dubbo.rpc.protocol.dubbo.DubboInvoker#doInvoke
// 按照dubbo协议发起调用实现类
///
@Override
protected Result doInvoke(final Invocation invocation) throws Throwable {
    RpcInvocation inv = (RpcInvocation) invocation;
    final String methodName = RpcUtils.getMethodName(invocation);
    inv.setAttachment(PATH_KEY, getUrl().getPath());
    inv.setAttachment(VERSION_KEY, version);
    
    // 获取发送数据的客户端
    ExchangeClient currentClient;
    if (clients.length == 1) {
        currentClient = clients[0];
    } else {
        currentClient = clients[index.getAndIncrement() % clients.length];
    }
    try {
        // 看看是单程发送不需要等待响应,还是发送完了后需要等待响应
        boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
        // 获取超时时间
        int timeout = calculateTimeout(invocation, methodName);
        invocation.setAttachment(TIMEOUT_KEY, timeout);
        
        if (isOneway) {
        	// 单程发送,不需要等待响应
            boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
            currentClient.send(inv, isSent);
            return AsyncRpcResult.newDefaultAsyncResult(invocation);
        } else {
            // 发送完了之后需要等待响应
            ExecutorService executor = getCallbackExecutor(getUrl(), inv);
            // 操作 currentClient 发送了一个 request 请求,
            // 然后接收了一个 CompletableFuture 对象,说明这里存在异步操作
            CompletableFuture<AppResponse> appResponseFuture =
                    currentClient.request(inv, timeout, executor).thenApply(obj -> (AppResponse) obj);
            FutureContext.getContext().setCompatibleFuture(appResponseFuture);
            AsyncRpcResult result = new AsyncRpcResult(appResponseFuture, inv);
            result.setExecutor(executor);
            return result;
        }
    } catch (TimeoutException e) {
        throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
    } catch (RemotingException e) {
        throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

从代码流程看,拿到了一个交换数据的客户端类,然后走两个发送数据的分支,一条分支逻辑单程调用不需要响应,一条有响应,两条分支最终都返回了一个异步结果对象。

ReferenceCountExchangeClient.request -> HeaderExchangeChannel.request -> AbstractPeer.send -> AbstractClient.send -> NettyChannel.send

在 HeaderExchangeChannel 中 new 了一个 DefaultFuture,便调用 AbstractPeer 的 send 方法,进入异步的发送流程。

调用流程回顾

在这里插入图片描述

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

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

相关文章

【数据结构】哈夫曼树

哈夫曼树 在学习哈夫曼树之前&#xff0c;先了解以下几个概念&#xff1a; 一&#xff1a;**路径长度&#xff1a;**在一棵树中&#xff0c;从一个节点到另一个节点所经过的“边”的数量&#xff0c;被我们称为两个节点之间的路径长度。 二&#xff1a;**树的路径长度&#xf…

HCIP(TCP)(2)

1. TCP三次握手 SYN (同步序列编号) 报文: 客户端发送 SYN 报文&#xff0c;开始建立连接&#xff0c;并初始化序列号。 SYN-ACK (同步序列编号-确认) 报文: 服务器收到 SYN 报文后&#xff0c;回复 SYN-ACK 报文&#xff0c;确认连接请求&#xff0c;并初始化自己的序列号和确…

基于Web的交互式智能成绩管理系统设计

目录 摘要 绪论 一、应用背景 二、行业发展现状 三、程序开发的重要意义 四、结语 1 代码 2 数据初始化模块 3 界面布局模块 4 核心功能模块 5 可视化子系统 6 扩展功能模块 7 架构设计亮点 功能总结 一、核心数据管理 二、智能分析体系 三、可视化系统 四、扩…

k8s日志管理

k8s日志管理 k8s查看日志查看集群中不是完全运行状态的pod查看deployment日志查看service日志进入pod的容器内查看日志 管理k8s组件日志kubectl logs查看日志原理 管理k8s应用日志收集k8s日志思路收集标准输出收集容器中日志文件 k8s查看节点状态失败k8s部署prometheus监控 k8s…

element-plus中,Loading 加载组件的使用

一.基本使用 给一个组件&#xff0c;如&#xff1a;table表格&#xff0c;加上v-loading"true"即可。 举例&#xff1a;复制如下代码。 <template><el-table v-loading"loading" :data"tableData" style"width: 100%"><…

Mybatis_Plus中的常用注解

目录 1、TableName TableId TableId的type属性 TableField 1、TableName 经过以上的测试&#xff0c;在使用MyBatis-Plus实现基本的CRUD时&#xff0c;我们并没有指定要操作的表&#xff0c;只是在 Mapper接口继承BaseMapper时&#xff0c;设置了泛型User&#xff0c;而操…

高并发金融系统,“可观测-可追溯-可回滚“的闭环审计体系

一句话总结 在高并发金融系统中&#xff0c;审计方案设计需平衡"观测粒度"与"系统损耗"&#xff0c;通过双AOP实现非侵入式采集&#xff0c;三表机制保障操作原子性&#xff0c;最终形成"可观测-可追溯-可回滚"的闭环体系。 业务痛点与需求 在…

企业内训|DeepSeek技术革命、算力范式重构与场景落地洞察-某头部券商

3月19日北京&#xff0c;TsingtaoAI公司负责人汶生受邀为某证券公司管理层和投资者举办专题培训&#xff0c;围绕《DeepSeek技术革命、算力范式重构与场景落地洞察》主题&#xff0c;系统阐述了当前AI技术演进的核心趋势、算力需求的结构性变革&#xff0c;以及行业应用落地的关…

VS Code C/C++项目设置launch.json中的environment参数解决支持库路径问题

问题描述 Windows 11 VS Code C/C 开发环境搭建分别写了c和cpp两个示例代码&#xff0c;在运行过程中c代码没有发现问题&#xff08;可能简单&#xff0c;没有用到太多支持&#xff09;&#xff0c;但使用了stl的cpp代码并没有运行出来&#xff0c;如下图&#xff1a; 出问题…

怎样解决 Windows 11 上的 DirectX 错误,最新DX 问题解决方法

在使用 Windows 11 操作系统的过程中&#xff0c;大家可能会遇到 DirectX 错误的情况&#xff0c;这可能会给游戏体验、多媒体应用甚至是系统的整体性能带来负面影响。不过别担心&#xff0c;本文将为大家详细介绍如何解决 Windows 11 上的 DirectX 错误&#xff0c;让您的系统…

PH热榜 | 2025-03-30

1. Deepcord 标语&#xff1a;Discord 数据分析&#xff1a;获取指标洞察与受众研究 介绍&#xff1a;Deepcord&#xff1a;为社区建设者提供的Discord分析工具。跟踪超过50万个服务器的指标&#xff0c;发现热门社区&#xff0c;监控竞争对手&#xff0c;找到你的目标受众。…

Open webui的使用

问题 之前本地量化模型管理器ollama的文章&#xff0c;我们知道可以通过ollama来管理本地量化模型&#xff0c;也能够在命令行中与相关模型进行对话。现在我们想要在有个web页面通过浏览器来与本地模型对话。这里我们就使用Open webui作为界面来与本地模型对话。 安装启动 这…

STM32单片机的桌面宠物机器人(基于HAL库)

效果 基于STM32单片机的桌面宠物机器人 概要 语音模块&#xff1a;ASR PRO&#xff0c;通过天问block软件烧录语音指令 主控芯片&#xff1a;STM32F103C8T6 使用HAL库 屏幕&#xff1a;0.96寸OLED屏&#xff0c;用来显示表情 4个舵机&#xff0c;用来当作四只腿 底部一个面…

Ubuntu 22 Linux上部署DeepSeek R1保姆式操作详解(ollama方式)

操作系统&#xff1a;Ubuntu Linux 22.04 一、安装模型运行环境 打开链接https://ollama.com/download/linux 1.安装ollama &#xff08;1&#xff09;一条指令即可实现的简易版安装方法&#xff08;也可称为在线安装&#xff09; curl -fsSL https://ollama.com/install.s…

深度学习处理时间序列(6)

RNN的高级用法 循环dropout&#xff08;recurrent dropout&#xff09;​&#xff1a;这是dropout的一种变体&#xff0c;用于在循环层中降低过拟合。 循环层堆叠&#xff08;stacking recurrent layers&#xff09;​&#xff1a;这会提高模型的表示能力&#xff08;代价是更…

【鸿蒙5.0】向用户申请麦克风授权

#效果图 步骤 在 config.json 里声明权限&#xff1a;在项目的 config.json 文件中添加麦克风权限的声明&#xff0c;告知系统应用需要使用该权限。检查权限状态&#xff1a;在代码里检查应用是否已经获得了麦克风权限。请求权限&#xff1a;若应用未获得麦克风权限&#xff0…

【解决】导入PNG图片,转 Sprite 格式成功但资产未生效问题

开发平台&#xff1a;Unity 6.0 图片格式&#xff1a;.png   问题描述 当 PNG 成功转换为 Sprite&#xff08;精灵&#xff09;时&#xff0c;资产状态将显示扩展箭头&#xff0c;即表明该资产可 Sprite 使用。 解决方法&#xff1a;设置正确的 Sprite Mode Single 关于 Spr…

从DeepSeek到Qwen,AI大模型的移植与交互实战指南

在不久前发布的《技术实战 | OK3588-C开发板上部署DeepSeek-R1大模型的完整指南》一文中&#xff0c;小编为大家介绍了DeepSeek-R1在飞凌嵌入式OK3588-C开发板上的移植部署、效果展示以及性能评测&#xff0c;本篇文章不仅将继续为大家带来关于DeepSeek-R1的干货知识&#xff0…