【Dubbo 源码解析】05_Dubbo 服务发现&引用
Dubbo 服务发现&引用
Dubbo 引用的服务消费者最终会构造成一个 Spring 的 Bean,具体是通过 ReferenceBean
来实现的。它是一个 FactoryBean,所有的服务消费者 Bean 都通过它来生产。
- ReferenceBean#getObject() --> ReferenceConfig#get()
ReferenceConfig 最终会创建一个动态代理类返回:
- private T createProxy(Map<String, String> map) {
- ......
- // assemble URL from register center's configuration
- // 从注册中心的配置组装 URL(服务发现)
- List<URL> us = loadRegistries(false);
- if (us != null && !us.isEmpty()) {
- for (URL u : us) {
- URL monitorUrl = loadMonitor(u);
- if (monitorUrl != null) {
- map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
- }
- urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
- }
- }
- ......
- if (urls.size() == 1) {
- // 创建 Invoker
- invoker = refprotocol.refer(interfaceClass, urls.get(0));
- } else {
- List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
- URL registryURL = null;
- for (URL url : urls) {
- invokers.add(refprotocol.refer(interfaceClass, url));
- if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
- registryURL = url; // use last registry url
- }
- }
- if (registryURL != null) { // registry url is available
- // use AvailableCluster only when register's cluster is available
- URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
- // 当服务提供者有多个时,就创建一个 ClusterInvoker
- invoker = cluster.join(new StaticDirectory(u, invokers));
- } else { // not a registry url
- invoker = cluster.join(new StaticDirectory(invokers));
- }
- }
- ......
- // create service proxy
- return (T) proxyFactory.getProxy(invoker);
- }
服务发现
dubbo 的服务发现,是通过从注册中心订阅服务提供者组装成 URL,然后通过 URL 创建出 Invoker 来实现的。
服务引用
Dubbo 的服务引用,实际上是为引用的接口创建一个 Proxy,这个 Proxy 的功能就是去执行 refprotocol.refer(interfaceClass, url)
创建出来的 Invoker。 当服务提供者有多个时,就创建一个 ClusterInvoker。Cluster 是一个 SPI 扩展点,默认使用的是 failover=com.alibaba.dubbo.rpc.cluster.support.FailoverCluster。
所以,Consumer 端服务调用的逻辑被封装在 refprotocol.refer(interfaceClass, url) 创建出来的 Invoker 上。通过之前 Dubbo Protocol & Filter 的学习,我们可以知道 refprotocol 是一个 Wrapped Protocol,refer() 方法创建出来的 Invoker 是被 Filter 包裹的一个 DubboInvoker。
综上,Consumer 端服务调用的逻辑是:
执行 FailoverClusterInvoker#invoke(Invocation invocation) (负载均衡。当有多个 provider 时走这一步,没有的话,就跳过)
执行 Filter#invoke(Invoker<?> invoker, Invocation invocation) (所有 group="provider" 的 Filter)
执行 DubboInvoker#invoke(Invocation inv)
服务消费端创建 tcp 连接
refprotocol.refer(interfaceClass, url) 被调用时会去创建与 provider 的 tcp 连接。
DubboProtocol#refer(Class<T> serviceType, URL url) 源码如下:
- public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
- optimizeSerialization(url);
- // create rpc invoker. 同时,创建一条与 provider 的 tcp 连接
- DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
- invokers.add(invoker);
- return invoker;
- }
Dubbo 服务引用时,首先创建一条与 provider 的 tcp 连接 ExchangeClient,然后再创建一个 DubboInvoker。
getClients(url) 最终会调用 initClient(URL url) 来创建一条新连接:
- private ExchangeClient initClient(URL url) {
- ......
- // 设置 codec = "dubbo"
- url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
- // enable heartbeat by default
- url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
- ......
- ExchangeClient client;
- try {
- // connection should be lazy
- if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
- client = new LazyConnectExchangeClient(url, requestHandler);
- } else {
- // 创建一条新连接
- client = Exchangers.connect(url, requestHandler);
- }
- } catch (RemotingException e) {
- throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
- }
- return client;
- }
新连接创建过程:
Exchangers.connect(url, requestHandler) --> ExchangeClient#connect(URL url, ExchangeHandler handler) --> Exchanger$Adaptive#connect(URL url, ExchangeHandler handler) --> HeaderExchanger#connect(URL url, ExchangeHandler handler) --> 返回 new HeaderExchangeClient(client, true)
- // HeaderExchanger.class
- public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
- // 创建新连接;启动 dubbo 心跳
- return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
- }
Transporters#connect(URL url, ChannelHandler... handlers) --> Transporter$Adaptive#connect(URL url, ChannelHandler handler) --> NettyTransporter#connect(URL url, ChannelHandler listener) --> 返回 new NettyClient(url, listener)
编解码最终使用的是:com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec
附:
Dubbo 心跳(HeartBeatTask)的作用是:当检测到心跳超时的时候,自动重连
官方如是说:
引用服务
1. 直连引用服务:
在没有注册中心,直连提供者的情况下 [3],ReferenceConfig
解析出的 URL 的格式为:dubbo://service-host/com.foo.FooService?version=1.0.0
。
基于扩展点自适应机制,通过 URL 的 dubbo://
协议头识别,直接调用 DubboProtocol
的 refer()
方法,返回提供者引用。
2. 从注册中心发现引用服务:
在有注册中心,通过注册中心发现提供者地址的情况下 [4],ReferenceConfig
解析出的 URL 的格式为:registry://registry-host/org.apache.dubbo.registry.RegistryService?refer=URL.encode("consumer://consumer-host/com.foo.FooService?version=1.0.0")
。
基于扩展点自适应机制,通过 URL 的 registry://
协议头识别,就会调用 RegistryProtocol
的 refer()
方法,基于 refer
参数中的条件,查询提供者 URL,如: dubbo://service-host/com.foo.FooService?version=1.0.0
。
基于扩展点自适应机制,通过提供者 URL 的 dubbo://
协议头识别,就会调用 DubboProtocol
的 refer()
方法,得到提供者引用。
然后 RegistryProtocol
将多个提供者引用,通过 Cluster
扩展点,伪装成单个提供者引用返回。
服务消费者消费一个服务的详细过程
上图是服务消费的主过程:
首先 ReferenceConfig
类的 init
方法调用 Protocol
的 refer
方法生成 Invoker
实例(如上图中的红色部分),这是服务消费的关键。接下来把 Invoker
转换为客户端需要的接口(如:HelloWorld)。
关于每种协议如 RMI/Dubbo/Web service 等它们在调用 refer
方法生成 Invoker
实例的细节和上一章节所描述的类似。
- 如果想了解更多Dubbo源码的知识,请移步 Dubbo源码解读——通向高手之路 的视频讲解:
- http://edu.51cto.com/sd/2e565
【Dubbo 源码解析】05_Dubbo 服务发现&引用的更多相关文章
- dubbo源码解析-spi(4)
前言 本篇是spi的第四篇,本篇讲解的是spi中增加的AOP,还是和上一篇一样,我们先从大家熟悉的spring引出AOP. AOP是老生常谈的话题了,思想都不会是一蹴而就的.比如架构设计从All in ...
- dubbo源码解析-spi(一)
前言 虽然标题是dubbo源码解析,但是本篇并不会出现dubbo的源码,本篇和之前的dubbo源码解析-简单原理.与spring融合一样,为dubbo源码解析专题的知识预热篇. 插播面试题 你是否了解 ...
- dubbo源码解析五 --- 集群容错架构设计与原理分析
欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 博客园 Dubbo 入门之二 --- 项目结构解析 博客园 Dubbo 源码分析系列之 ...
- Dubbo 源码解析四 —— 负载均衡LoadBalance
欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 Dubbo 入门之二 --- 项目结构解析 Dubbo 源码分析系列之三 -- 架构原 ...
- Netty 4源码解析:服务端启动
Netty 4源码解析:服务端启动 1.基础知识 1.1 Netty 4示例 因为Netty 5还处于测试版,所以选择了目前比较稳定的Netty 4作为学习对象.而且5.0的变化也不像4.0这么大,好 ...
- dubbo源码解析-spi(3)
前言 在上一篇的末尾,我们提到了dubbo的spi中增加了IoC和AOP的功能.那么本篇就讲一下这个增加的IoC,spi部分预计会有四篇,因为这东西实在是太重要了.温故而知新,我们先来回顾一下,我们之 ...
- dubbo源码解析-zookeeper创建节点
前言 在之前dubbo源码解析-本地暴露中的前言部分提到了两道高频的面试题,其中一道dubbo中zookeeper做注册中心,如果注册中心集群都挂掉,那发布者和订阅者还能通信吗?在上周的dubbo源码 ...
- Dubbo源码解析(一)服务发现
一.Dubbo源码模块 官网地址 源码地址 1.1 源码模块组织 Dubbo工程是一个Maven多Module的项目,以包结构来组织各个模块. 核心模块及其关系,如图所示: 1.2 模块说明 dubb ...
- 【Dubbo 源码解析】06_Dubbo 服务调用
Dubbo 服务调用 根据上图,可以看出,服务调用过程为: Consumer 端的 Proxy 调用 Cluster 层选择集群中的某一个 Invoker(负载均衡) Invoker 最终会调用 Pr ...
随机推荐
- PAT Basic 1026
1026 程序运行时间 (15 分) 要获得一个 C 语言程序的运行时间,常用的方法是调用头文件 time.h,其中提供了 clock() 函数,可以捕捉从程序开始运行到 clock() 被调用时所耗 ...
- Codeforces Round #443 (Div. 1) D. Magic Breeding 位运算
D. Magic Breeding link http://codeforces.com/contest/878/problem/D description Nikita and Sasha play ...
- oracle数据库启动和关闭方式
Oracle数据库是重量级的,其管理非常复杂,将其在Linux平台上的启动和关闭步骤整理一下. 安装完毕oracle以后,需要创建oracle系统用户,并在/home/oracle下面的.bash_p ...
- poj2456 Aggressive cows(二分查找)
https://vjudge.net/problem/POJ-2456 二分,从最大长度开始,不断折半试,如果牛全放下了,就是可行,修改下界,否则改上届. #include<iostream&g ...
- 优先队列重载<运算符
https://vjudge.net/problem/POJ-3190 #include<iostream> #include<cstdio> #include<queu ...
- Codeforces899D Shovel Sale(思路)
http://codeforces.com/problemset/problem/899/D 还是得tag一下,以下代码只有G++ 14 6.4.0能过,其他都过不了不知为什么? 思路:先求出最多的9 ...
- JSON序列——保存修改数据
JSON序列——保存修改数据 procedure TForm1.Button6Click(Sender: TObject); begin var delta: TynJsonDelta := TynJ ...
- YUV422 YUV420 Planar \ Semi-Planar \ Interleaved YCbCr与YUV
YCbCr是DVD.摄像机.数字电视等消费类视频产品中,常用的色彩编码方案.YCbCr 有时会称为 YCC..Y'CbCr 在模拟分量视频(analog component video)中也常被称为 ...
- tensorflow由于未初始化变量所导致的错误
版权声明:本文为博主原创文章,如需转载请注明出处,谢谢. https://blog.csdn.net/qq_38542085/article/details/78742295 初始代码 import ...
- iOS:Gif动画功能(显示gif动画、获取gif动画时长、获取gif动画执行次数)
一.简单介绍 gif动画是iOS开发中很常用的一个功能,有的是为了显示加载视频的过程,更多的是为了显示一个结果状态(动画更直观). 那么如何执行gif动画,方法有很多.(这里只写一下方法三,前两种之前 ...