Dubbo 服务调用

根据上图,可以看出,服务调用过程为:

  1. Consumer 端的 Proxy 调用 Cluster 层选择集群中的某一个 Invoker(负载均衡)

  2. Invoker 最终会调用 Protocol 层进行 RPC 通讯(netty,tcp 长连接),将服务调用信息和配置信息进行传递

  3. Provider 端 Protocol 层接收到服务调用信息后,最终会调用真实的服务实现

Consumer 端调用过程

通过前面 Dubbo 服务发现&引用 的学习,我们知道,Consumer 端的调用过程大体如下:

  1. 执行 FailoverClusterInvoker#invoke(Invocation invocation) (负载均衡。当有多个 provider 时走这一步,没有的话,就跳过)

  2. 执行 Filter#invoke(Invoker<?> invoker, Invocation invocation) (所有 group="provider" 的 Filter)

  3. 执行 DubboInvoker#invoke(Invocation inv)

所以,最终的通讯过程是由 DubboInvoker 来完成的:

protected Result doInvoke(final Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
final String methodName = RpcUtils.getMethodName(invocation);
inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
inv.setAttachment(Constants.VERSION_KEY, version);
// 选择 ExchangeClient(一般情况下只有一个)
ExchangeClient currentClient;
if (clients.length == 1) {
currentClient = clients[0];
} else {
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
if (isOneway) {
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
currentClient.send(inv, isSent);
RpcContext.getContext().setFuture(null);
return new RpcResult();
} else if (isAsync) { // dubbo 异步调用
ResponseFuture future = currentClient.request(inv, timeout);
RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
return new RpcResult();
} else { // 服务调用分支
RpcContext.getContext().setFuture(null);
return (Result) currentClient.request(inv, timeout).get();
}
} 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);
}
}

阅读源码可以发现,获取 Rpc 调用结果的方法是: ExchangeClient#request(inv, timeout).get(), 最终会调用 NettyChannel#send(Object message, false) 去写消息。而消息的编解码是通过 com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec 来处理的。

// 对 Request 消息进行编码(DubboCodec.class)
protected void encodeRequestData(Channel channel, ObjectOutput out, Object data) throws IOException {
RpcInvocation inv = (RpcInvocation) data;
out.writeUTF(inv.getAttachment(Constants.DUBBO_VERSION_KEY, DUBBO_VERSION));
out.writeUTF(inv.getAttachment(Constants.PATH_KEY));
out.writeUTF(inv.getAttachment(Constants.VERSION_KEY));
out.writeUTF(inv.getMethodName()); // 写出调用方法的名称
out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes())); // 写出调用方法的参数
Object[] args = inv.getArguments();
if (args != null)
for (int i = 0; i < args.length; i++) {
out.writeObject(encodeInvocationArgument(channel, inv, i)); // 写出调用的实参
}
out.writeObject(inv.getAttachments()); // 写出 attachments
}

Provider 端调用过程

Provider 端接收到 Consumer 端的消息后,会通过 DubboCodec#decodeBody(Channel channel, InputStream is, byte[] header)处理,将消息转化为 RpcInvocation。最终通过预先设置好的 netty 的 handler 来将 RpcInvocation 转化为 Invoker 的调用。

通过 Dubbo 服务发现&引用 的学习,我们知道,这个 handler 最终会调用 DubboProtocol#requestHandler 来处理,最终将 Invocation 转化为 Invoker 的调用。

调用完成后,返回结果通过DubboCodec#encodeResponseData(Channel channel, ObjectOutput out, Object data)进行编码,最终写回到 Consumer 端:

// 对返回结果进行编码
protected void encodeResponseData(Channel channel, ObjectOutput out, Object data) throws IOException {
Result result = (Result) data;
Throwable th = result.getException();
if (th == null) {
Object ret = result.getValue();
if (ret == null) {
out.writeByte(RESPONSE_NULL_VALUE);
} else {
out.writeByte(RESPONSE_VALUE);
out.writeObject(ret);
}
} else {
out.writeByte(RESPONSE_WITH_EXCEPTION);
out.writeObject(th);
}
}

Consumer 端接收调用返回

Conmuser 端接收到调用的返回数据后,会由 DubboCodec#decodeBody(Channel channel, InputStream is, byte[] header)将数据转化成 RpcResult。

ExchangeClient#request(inv, timeout).get() 最终会调用 DefaultFuture#get() 来获取 Response 中的返回数据。DefaultFuture#get() 使用了同步阻塞的方式来等待 provider 端的返回数据。

官方如是说:

满眼都是 Invoker

由于 Invoker 是 Dubbo 领域模型中非常重要的一个概念,很多设计思路都是向它靠拢。这就使得 Invoker渗透在整个实现代码里,对于刚开始接触 Dubbo 的人,确实容易给搞混了。 下面我们用一个精简的图来说明最重要的两种 Invoker:服务提供 Invoker 和服务消费 Invoker

服务提供 Invoker:DubboInvoker(默认)、HessianRpcInvoker等(视协议而定)

服务消费 Invoker:AbstractProxyInvoker

如果想了解更多Dubbo源码的知识,请移步 Dubbo源码解读——通向高手之路 的视频讲解:
http://edu.51cto.com/sd/2e565

【Dubbo 源码解析】06_Dubbo 服务调用的更多相关文章

  1. dubbo源码解析五 --- 集群容错架构设计与原理分析

    欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 博客园 Dubbo 入门之二 --- 项目结构解析 博客园 Dubbo 源码分析系列之 ...

  2. Dubbo 源码解析四 —— 负载均衡LoadBalance

    欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 Dubbo 入门之二 --- 项目结构解析 Dubbo 源码分析系列之三 -- 架构原 ...

  3. dubbo源码解析-spi(4)

    前言 本篇是spi的第四篇,本篇讲解的是spi中增加的AOP,还是和上一篇一样,我们先从大家熟悉的spring引出AOP. AOP是老生常谈的话题了,思想都不会是一蹴而就的.比如架构设计从All in ...

  4. Netty 4源码解析:服务端启动

    Netty 4源码解析:服务端启动 1.基础知识 1.1 Netty 4示例 因为Netty 5还处于测试版,所以选择了目前比较稳定的Netty 4作为学习对象.而且5.0的变化也不像4.0这么大,好 ...

  5. dubbo源码解析-spi(一)

    前言 虽然标题是dubbo源码解析,但是本篇并不会出现dubbo的源码,本篇和之前的dubbo源码解析-简单原理.与spring融合一样,为dubbo源码解析专题的知识预热篇. 插播面试题 你是否了解 ...

  6. dubbo源码解析-zookeeper创建节点

    前言 在之前dubbo源码解析-本地暴露中的前言部分提到了两道高频的面试题,其中一道dubbo中zookeeper做注册中心,如果注册中心集群都挂掉,那发布者和订阅者还能通信吗?在上周的dubbo源码 ...

  7. dubbo源码解析-spi(3)

    前言 在上一篇的末尾,我们提到了dubbo的spi中增加了IoC和AOP的功能.那么本篇就讲一下这个增加的IoC,spi部分预计会有四篇,因为这东西实在是太重要了.温故而知新,我们先来回顾一下,我们之 ...

  8. 【Dubbo 源码解析】05_Dubbo 服务发现&引用

    Dubbo 服务发现&引用 Dubbo 引用的服务消费者最终会构造成一个 Spring 的 Bean,具体是通过 ReferenceBean 来实现的.它是一个 FactoryBean,所有的 ...

  9. 【Dubbo 源码解析】04_Dubbo 服务注册&暴露

    Dubbo 服务注册&暴露 Dubbo 服务暴露过程是通过 com.alibaba.dubbo.config.spring.ServiceBean 来实现的.Spring 容器 refresh ...

随机推荐

  1. selenium+PhantomJS小案例—爬豆瓣网所有电影代码python

    #coding=utf-8from selenium import webdriver def crawMovie(): driver=webdriver.PhantomJS() driver.get ...

  2. PAT基础6-2

    6-2 多项式求值 (15 分) 本题要求实现一个函数,计算阶数为n,系数为a[0] ... a[n]的多项式f(x)=∑​i=0​n​​(a[i]×x​i​​) 在x点的值. 函数接口定义: dou ...

  3. 使用PrerenderSpaPlugin预渲染插件没有成功渲染

    问题 在已有vue项目里使用prerender-spa-plugin插件时,遇到了build出来的页面是白屏或者出现{"statusCode":404,"error&qu ...

  4. ES6语法(一)let 和 const 命令

    1.let命令 与 var 的区别 用法类似于var,但是所声明的变量,只在 let命令所在的代码块内有效. var 声明的变量可以在声明之前使用,值为 undefined ;let命令改变了语法行为 ...

  5. django之模型层(model)--建表、查询、删除基础

    要说一个项目最重要的部分是什么那铁定数据了,也就是数据库,这篇就开始带大家走进django关于模型层model的使用,model主要就是操纵数据库不使用sql语句的情况下完成数据库的增删改查.本篇仅带 ...

  6. 编写CentOS的System V init启动脚本

    系统本身自带了说明,在/usr/share/doc/initscripts-(*)/sysvinitfiles,内容如下: 所有System V init脚本都命名为/etc/rc.d/init.d/ ...

  7. /etc/passwd /etc/group /etc/shadow 文件的格式说明

    /etc/passwd 存放账户信息: root:x:0:0:root:/root:/bin/bashjianing:x:1011:100::/home/jianing:/bin/bashuserna ...

  8. Selenium + WebDriver 各浏览器驱动下载地址

    Chrome 点击下载chrome的webdriver: http://chromedriver.storage.googleapis.com/index.html 不同的Chrome的版本对应的ch ...

  9. Compiler Error: Function call with parameters that may be unsafe

    如下的代码: #include <stdio.h> #include <string> #include <algorithm> #include <cass ...

  10. javascript日期时间操作库推荐

    https://github.com/datejs/Datejs 链接: https://pan.baidu.com/s/1QTGhxslarNyW_0kB6gyJYA 提取码: ibab 如果这篇文 ...