mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-17 08:46:40 +08:00
955 lines
41 KiB
HTML
955 lines
41 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>42 加餐:服务引用流程全解析.md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
<body>
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/00 开篇词 深入掌握 Dubbo 原理与实现,提升你的职场竞争力.md.html">00 开篇词 深入掌握 Dubbo 原理与实现,提升你的职场竞争力.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/01 Dubbo 源码环境搭建:千里之行,始于足下.md.html">01 Dubbo 源码环境搭建:千里之行,始于足下.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/02 Dubbo 的配置总线:抓住 URL,就理解了半个 Dubbo.md.html">02 Dubbo 的配置总线:抓住 URL,就理解了半个 Dubbo.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/03 Dubbo SPI 精析,接口实现两极反转(上).md.html">03 Dubbo SPI 精析,接口实现两极反转(上).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/04 Dubbo SPI 精析,接口实现两极反转(下).md.html">04 Dubbo SPI 精析,接口实现两极反转(下).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/05 海量定时任务,一个时间轮搞定.md.html">05 海量定时任务,一个时间轮搞定.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/06 ZooKeeper 与 Curator,求你别用 ZkClient 了(上).md.html">06 ZooKeeper 与 Curator,求你别用 ZkClient 了(上).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/07 ZooKeeper 与 Curator,求你别用 ZkClient 了(下).md.html">07 ZooKeeper 与 Curator,求你别用 ZkClient 了(下).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/08 代理模式与常见实现.md.html">08 代理模式与常见实现.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/09 Netty 入门,用它做网络编程都说好(上).md.html">09 Netty 入门,用它做网络编程都说好(上).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/10 Netty 入门,用它做网络编程都说好(下).md.html">10 Netty 入门,用它做网络编程都说好(下).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/11 简易版 RPC 框架实现(上).md.html">11 简易版 RPC 框架实现(上).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/12 简易版 RPC 框架实现(下).md.html">12 简易版 RPC 框架实现(下).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/13 本地缓存:降低 ZooKeeper 压力的一个常用手段.md.html">13 本地缓存:降低 ZooKeeper 压力的一个常用手段.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/14 重试机制是网络操作的基本保证.md.html">14 重试机制是网络操作的基本保证.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/15 ZooKeeper 注册中心实现,官方推荐注册中心实践.md.html">15 ZooKeeper 注册中心实现,官方推荐注册中心实践.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/16 Dubbo Serialize 层:多种序列化算法,总有一款适合你.md.html">16 Dubbo Serialize 层:多种序列化算法,总有一款适合你.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/17 Dubbo Remoting 层核心接口分析:这居然是一套兼容所有 NIO 框架的设计?.md.html">17 Dubbo Remoting 层核心接口分析:这居然是一套兼容所有 NIO 框架的设计?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/18 Buffer 缓冲区:我们不生产数据,我们只是数据的搬运工.md.html">18 Buffer 缓冲区:我们不生产数据,我们只是数据的搬运工.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/19 Transporter 层核心实现:编解码与线程模型一文打尽(上).md.html">19 Transporter 层核心实现:编解码与线程模型一文打尽(上).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/20 Transporter 层核心实现:编解码与线程模型一文打尽(下).md.html">20 Transporter 层核心实现:编解码与线程模型一文打尽(下).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/21 Exchange 层剖析:彻底搞懂 Request-Response 模型(上).md.html">21 Exchange 层剖析:彻底搞懂 Request-Response 模型(上).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/22 Exchange 层剖析:彻底搞懂 Request-Response 模型(下).md.html">22 Exchange 层剖析:彻底搞懂 Request-Response 模型(下).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/23 核心接口介绍,RPC 层骨架梳理.md.html">23 核心接口介绍,RPC 层骨架梳理.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/24 从 Protocol 起手,看服务暴露和服务引用的全流程(上).md.html">24 从 Protocol 起手,看服务暴露和服务引用的全流程(上).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/25 从 Protocol 起手,看服务暴露和服务引用的全流程(下).md.html">25 从 Protocol 起手,看服务暴露和服务引用的全流程(下).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/26 加餐:直击 Dubbo “心脏”,带你一起探秘 Invoker(上).md.html">26 加餐:直击 Dubbo “心脏”,带你一起探秘 Invoker(上).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/27 加餐:直击 Dubbo “心脏”,带你一起探秘 Invoker(下).md.html">27 加餐:直击 Dubbo “心脏”,带你一起探秘 Invoker(下).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/28 复杂问题简单化,代理帮你隐藏了多少底层细节?.md.html">28 复杂问题简单化,代理帮你隐藏了多少底层细节?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/29 加餐:HTTP 协议 + JSON-RPC,Dubbo 跨语言就是如此简单.md.html">29 加餐:HTTP 协议 + JSON-RPC,Dubbo 跨语言就是如此简单.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/30 Filter 接口,扩展 Dubbo 框架的常用手段指北.md.html">30 Filter 接口,扩展 Dubbo 框架的常用手段指北.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/31 加餐:深潜 Directory 实现,探秘服务目录玄机.md.html">31 加餐:深潜 Directory 实现,探秘服务目录玄机.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/32 路由机制:请求到底怎么走,它说了算(上).md.html">32 路由机制:请求到底怎么走,它说了算(上).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/33 路由机制:请求到底怎么走,它说了算(下).md.html">33 路由机制:请求到底怎么走,它说了算(下).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/34 加餐:初探 Dubbo 动态配置的那些事儿.md.html">34 加餐:初探 Dubbo 动态配置的那些事儿.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/35 负载均衡:公平公正物尽其用的负载均衡策略,这里都有(上).md.html">35 负载均衡:公平公正物尽其用的负载均衡策略,这里都有(上).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/36 负载均衡:公平公正物尽其用的负载均衡策略,这里都有(下).md.html">36 负载均衡:公平公正物尽其用的负载均衡策略,这里都有(下).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/37 集群容错:一个好汉三个帮(上).md.html">37 集群容错:一个好汉三个帮(上).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/38 集群容错:一个好汉三个帮(下).md.html">38 集群容错:一个好汉三个帮(下).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/39 加餐:多个返回值不用怕,Merger 合并器来帮忙.md.html">39 加餐:多个返回值不用怕,Merger 合并器来帮忙.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/40 加餐:模拟远程调用,Mock 机制帮你搞定.md.html">40 加餐:模拟远程调用,Mock 机制帮你搞定.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/41 加餐:一键通关服务发布全流程.md.html">41 加餐:一键通关服务发布全流程.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
<a class="current-tab" href="/专栏/Dubbo源码解读与实战-完/42 加餐:服务引用流程全解析.md.html">42 加餐:服务引用流程全解析.md.html</a>
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/43 服务自省设计方案:新版本新方案.md.html">43 服务自省设计方案:新版本新方案.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/44 元数据方案深度剖析,如何避免注册中心数据量膨胀?.md.html">44 元数据方案深度剖析,如何避免注册中心数据量膨胀?.md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/45 加餐:深入服务自省方案中的服务发布订阅(上).md.html">45 加餐:深入服务自省方案中的服务发布订阅(上).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/46 加餐:深入服务自省方案中的服务发布订阅(下).md.html">46 加餐:深入服务自省方案中的服务发布订阅(下).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/47 配置中心设计与实现:集中化配置 and 本地化配置,我都要(上).md.html">47 配置中心设计与实现:集中化配置 and 本地化配置,我都要(上).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/48 配置中心设计与实现:集中化配置 and 本地化配置,我都要(下).md.html">48 配置中心设计与实现:集中化配置 and 本地化配置,我都要(下).md.html</a>
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/49 结束语 认真学习,缩小差距.md.html">49 结束语 认真学习,缩小差距.md.html</a>
|
||
</li>
|
||
|
||
</ul>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
</script>
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>42 加餐:服务引用流程全解析</h1>
|
||
|
||
<p>Dubbo 作为一个 RPC 框架,暴露给用户最基本的功能就是服务发布和服务引用。在上一课时,我们已经分析了服务发布的核心流程。那么在本课时,我们就接着深入分析<strong>服务引用的核心流程</strong>。</p>
|
||
|
||
<p>Dubbo 支持两种方式引用远程的服务:</p>
|
||
|
||
<ul>
|
||
|
||
<li><strong>服务直连的方式</strong>,仅适合在<strong>调试服务</strong>的时候使用;</li>
|
||
|
||
<li><strong>基于注册中心引用服务</strong>,这是<strong>生产环境</strong>中使用的服务引用方式。</li>
|
||
|
||
</ul>
|
||
|
||
<h3>DubboBootstrap 入口</h3>
|
||
|
||
<p>在上一课时介绍服务发布的时候,我们介绍了 DubboBootstrap.start() 方法的核心流程,其中除了会<strong>调用 exportServices() 方法完成服务发布</strong>之外,还会<strong>调用 referServices() 方法完成服务引用</strong>,这里就不再贴出 DubboBootstrap.start() 方法的具体代码,你若感兴趣的话可以参考<a href="https://github.com/xxxlxy2008/dubbo">源码</a>进行学习。</p>
|
||
|
||
<p>在 DubboBootstrap.referServices() 方法中,会从 ConfigManager 中获取所有 ReferenceConfig 列表,并根据 ReferenceConfig 获取对应的代理对象,入口逻辑如下:</p>
|
||
|
||
<pre><code>private void referServices() {
|
||
if (cache == null) { // 初始ReferenceConfigCache
|
||
cache = ReferenceConfigCache.getCache();
|
||
}
|
||
configManager.getReferences().forEach(rc -> {
|
||
// 遍历ReferenceConfig列表
|
||
ReferenceConfig referenceConfig = (ReferenceConfig) rc;
|
||
referenceConfig.setBootstrap(this);
|
||
if (rc.shouldInit()) { // 检测ReferenceConfig是否已经初始化
|
||
if (referAsync) { // 异步
|
||
CompletableFuture<Object> future = ScheduledCompletableFuture.submit(
|
||
executorRepository.getServiceExporterExecutor(),
|
||
() -> cache.get(rc)
|
||
);
|
||
asyncReferringFutures.add(future);
|
||
} else { // 同步
|
||
cache.get(rc);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>这里的 ReferenceConfig 是哪里来的呢?在[第 01 课时]dubbo-demo-api-consumer 示例中,我们可以看到构造 ReferenceConfig 对象的逻辑,这些新建的 ReferenceConfig 对象会通过 DubboBootstrap.reference() 方法添加到 ConfigManager 中进行管理,如下所示:</p>
|
||
|
||
<pre><code>public DubboBootstrap reference(ReferenceConfig<?> referenceConfig) {
|
||
configManager.addReference(referenceConfig);
|
||
return this;
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<h3>ReferenceConfigCache</h3>
|
||
|
||
<p><strong>服务引用的核心实现在 ReferenceConfig 之中</strong>,一个 ReferenceConfig 对象对应一个服务接口,每个 ReferenceConfig 对象中都封装了与注册中心的网络连接,以及与 Provider 的网络连接,这是一个非常重要的对象。</p>
|
||
|
||
<p><strong>为了避免底层连接泄漏造成性能问题,从 Dubbo 2.4.0 版本开始,Dubbo 提供了 ReferenceConfigCache 用于缓存 ReferenceConfig 实例。</strong></p>
|
||
|
||
<p>在 dubbo-demo-api-consumer 示例中,我们可以看到 ReferenceConfigCache 的基本使用方式:</p>
|
||
|
||
<pre><code>ReferenceConfig<DemoService> reference = new ReferenceConfig<>();
|
||
reference.setInterface(DemoService.class);
|
||
...
|
||
// 这一步在DubboBootstrap.start()方法中完成
|
||
ReferenceConfigCache cache = ReferenceConfigCache.getCache();
|
||
...
|
||
DemoService demoService = ReferenceConfigCache.getCache().get(reference);
|
||
|
||
</code></pre>
|
||
|
||
<p>在 ReferenceConfigCache 中维护了一个静态的 Map(CACHE_HOLDER)字段,其中 Key 是由 Group、服务接口和 version 构成,Value 是一个 ReferenceConfigCache 对象。在 ReferenceConfigCache 中可以传入一个 KeyGenerator 用来修改缓存 Key 的生成逻辑,KeyGenerator 接口的定义如下:</p>
|
||
|
||
<pre><code>public interface KeyGenerator {
|
||
String generateKey(ReferenceConfigBase<?> referenceConfig);
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>默认的 KeyGenerator 实现是 ReferenceConfigCache 中的匿名内部类,其对象由 DEFAULT_KEY_GENERATOR 这个静态字段引用,具体实现如下:</p>
|
||
|
||
<pre><code>public static final KeyGenerator DEFAULT_KEY_GENERATOR = referenceConfig -> {
|
||
String iName = referenceConfig.getInterface();
|
||
if (StringUtils.isBlank(iName)) { // 获取服务接口名称
|
||
Class<?> clazz = referenceConfig.getInterfaceClass();
|
||
iName = clazz.getName();
|
||
}
|
||
if (StringUtils.isBlank(iName)) {
|
||
throw new IllegalArgumentException("No interface info in ReferenceConfig" + referenceConfig);
|
||
}
|
||
// Key的格式是group/interface:version
|
||
StringBuilder ret = new StringBuilder();
|
||
if (!StringUtils.isBlank(referenceConfig.getGroup())) {
|
||
ret.append(referenceConfig.getGroup()).append("/");
|
||
}
|
||
ret.append(iName);
|
||
if (!StringUtils.isBlank(referenceConfig.getVersion())) {
|
||
ret.append(":").append(referenceConfig.getVersion());
|
||
}
|
||
return ret.toString();
|
||
};
|
||
|
||
</code></pre>
|
||
|
||
<p>在 ReferenceConfigCache 实例对象中,会维护下面两个 Map 集合。</p>
|
||
|
||
<ul>
|
||
|
||
<li>proxies(ConcurrentMap<Class<?>, ConcurrentMap<String, Object>>类型):该集合用来存储服务接口的全部代理对象,其中第一层 Key 是服务接口的类型,第二层 Key 是上面介绍的 KeyGenerator 为不同服务提供方生成的 Key,Value 是服务的代理对象。</li>
|
||
|
||
<li>referredReferences(ConcurrentMap<String, ReferenceConfigBase<?>> 类型):该集合用来存储已经被处理的 ReferenceConfig 对象。</li>
|
||
|
||
</ul>
|
||
|
||
<p>我们回到 DubboBootstrap.referServices() 方法中,看一下其中与 ReferenceConfigCache 相关的逻辑。</p>
|
||
|
||
<p>首先是 ReferenceConfigCache.getCache() 这个静态方法,会在 CACHE_HOLDER 集合中添加一个 Key 为“<em>DEFAULT</em>”的 ReferenceConfigCache 对象(使用默认的 KeyGenerator 实现),它将作为默认的 ReferenceConfigCache 对象。</p>
|
||
|
||
<p>接下来,无论是同步服务引用还是异步服务引用,都会调用 ReferenceConfigCache.get() 方法,创建并缓存代理对象。下面就是 ReferenceConfigCache.get() 方法的核心实现:</p>
|
||
|
||
<pre><code>public <T> T get(ReferenceConfigBase<T> referenceConfig) {
|
||
// 生成服务提供方对应的Key
|
||
String key = generator.generateKey(referenceConfig);
|
||
// 获取接口类型
|
||
Class<?> type = referenceConfig.getInterfaceClass();
|
||
// 获取该接口对应代理对象集合
|
||
proxies.computeIfAbsent(type, _t -> new ConcurrentHashMap<>());
|
||
ConcurrentMap<String, Object> proxiesOfType = proxies.get(type);
|
||
// 根据Key获取服务提供方对应的代理对象
|
||
proxiesOfType.computeIfAbsent(key, _k -> {
|
||
// 服务引用
|
||
Object proxy = referenceConfig.get();
|
||
// 将ReferenceConfig记录到referredReferences集合
|
||
referredReferences.put(key, referenceConfig);
|
||
return proxy;
|
||
});
|
||
return (T) proxiesOfType.get(key);
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<h3>ReferenceConfig</h3>
|
||
|
||
<p>通过前面的介绍我们知道,<strong>ReferenceConfig 是服务引用的真正入口</strong>,其中会创建相关的代理对象。下面先来看 ReferenceConfig.get() 方法:</p>
|
||
|
||
<pre><code>public synchronized T get() {
|
||
if (destroyed) { // 检测当前ReferenceConfig状态
|
||
throw new IllegalStateException("...");
|
||
}
|
||
if (ref == null) {// ref指向了服务的代理对象
|
||
init(); // 初始化ref字段
|
||
}
|
||
return ref;
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>在 ReferenceConfig.init() 方法中,首先会对服务引用的配置进行处理,以保证配置的正确性。这里的具体实现其实本身并不复杂,但由于涉及很多的配置解析和处理逻辑,代码就显得非常长,我们就不再一一展示,你若感兴趣的话可以参考<a href="https://github.com/xxxlxy2008/dubbo">源码</a>进行学习。</p>
|
||
|
||
<p><strong>ReferenceConfig.init() 方法的核心逻辑是调用 createProxy() 方法</strong>,调用之前会从配置中获取 createProxy() 方法需要的参数:</p>
|
||
|
||
<pre><code>public synchronized void init() {
|
||
if (initialized) { // 检测ReferenceConfig的初始化状态
|
||
return;
|
||
}
|
||
if (bootstrap == null) { // 检测DubboBootstrap的初始化状态
|
||
bootstrap = DubboBootstrap.getInstance();
|
||
bootstrap.init();
|
||
}
|
||
... // 省略其他配置的检查
|
||
Map<String, String> map = new HashMap<String, String>();
|
||
map.put(SIDE_KEY, CONSUMER_SIDE); // 添加side参数
|
||
// 添加Dubbo版本、release参数、timestamp参数、pid参数
|
||
ReferenceConfigBase.appendRuntimeParameters(map);
|
||
// 添加interface参数
|
||
map.put(INTERFACE_KEY, interfaceName);
|
||
... // 省略其他参数的处理
|
||
String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY);
|
||
if (StringUtils.isEmpty(hostToRegistry)) {
|
||
hostToRegistry = NetUtils.getLocalHost();
|
||
} else if (isInvalidLocalHost(hostToRegistry)) {
|
||
throw new IllegalArgumentException("...");
|
||
}
|
||
// 添加ip参数
|
||
map.put(REGISTER_IP_KEY, hostToRegistry);
|
||
// 调用createProxy()方法
|
||
ref = createProxy(map);
|
||
...// 省略其他代码
|
||
initialized = true;
|
||
// 触发ReferenceConfigInitializedEvent事件
|
||
dispatch(new ReferenceConfigInitializedEvent(this, invoker));
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>ReferenceConfig.createProxy() 方法中处理了多种服务引用的场景,例如,直连单个/多个Provider、单个/多个注册中心。下面是 createProxy() 方法的核心流程,大致可以梳理出这么 5 个步骤。</p>
|
||
|
||
<ol>
|
||
|
||
<li>根据传入的参数集合判断协议是否为 injvm 协议,如果是,直接通过 InjvmProtocol 引用服务。</li>
|
||
|
||
<li>构造 urls 集合。Dubbo 支持<strong>直连 Provider</strong>和<strong>依赖注册中心</strong>两种服务引用方式。如果是直连服务的模式,我们可以通过 url 参数指定一个或者多个 Provider 地址,会被解析并填充到 urls 集合;如果通过注册中心的方式进行服务引用,则会调用 AbstractInterfaceConfig.loadRegistries() 方法加载所有注册中心。</li>
|
||
|
||
<li>如果 urls 集合中只记录了一个 URL,通过 Protocol 适配器选择合适的 Protocol 扩展实现创建 Invoker 对象。如果是直连 Provider 的场景,则 URL 为 dubbo 协议,这里就会使用 DubboProtocol 这个实现;如果依赖注册中心,则使用 RegistryProtocol 这个实现。</li>
|
||
|
||
<li>如果 urls 集合中有多个注册中心,则使用 ZoneAwareCluster 作为 Cluster 的默认实现,生成对应的 Invoker 对象;如果 urls 集合中记录的是多个直连服务的地址,则使用 Cluster 适配器选择合适的扩展实现生成 Invoker 对象。</li>
|
||
|
||
<li>通过 ProxyFactory 适配器选择合适的 ProxyFactory 扩展实现,将 Invoker 包装成服务接口的代理对象。</li>
|
||
|
||
</ol>
|
||
|
||
<p>通过上面的流程我们可以看出<strong>createProxy() 方法中有两个核心</strong>:一是通过 Protocol 适配器选择合适的 Protocol 扩展实现创建 Invoker 对象;二是通过 ProxyFactory 适配器选择合适的 ProxyFactory 创建代理对象。</p>
|
||
|
||
<p>下面我们来看 createProxy() 方法的具体实现:</p>
|
||
|
||
<pre><code>private T createProxy(Map<String, String> map) {
|
||
if (shouldJvmRefer(map)) { // 根据url的协议、scope以及injvm等参数检测是否需要本地引用
|
||
// 创建injvm协议的URL
|
||
URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
|
||
// 通过Protocol的适配器选择对应的Protocol实现创建Invoker对象
|
||
invoker = REF_PROTOCOL.refer(interfaceClass, url);
|
||
if (logger.isInfoEnabled()) {
|
||
logger.info("Using injvm service " + interfaceClass.getName());
|
||
}
|
||
} else {
|
||
urls.clear();
|
||
if (url != null && url.length() > 0) {
|
||
String[] us = SEMICOLON_SPLIT_PATTERN.split(url); // 配置多个URL的时候,会用分号进行切分
|
||
if (us != null && us.length > 0) { // url不为空,表明用户可能想进行点对点调用
|
||
for (String u : us) {
|
||
URL url = URL.valueOf(u);
|
||
if (StringUtils.isEmpty(url.getPath())) {
|
||
url = url.setPath(interfaceName); // 设置接口完全限定名为URL Path
|
||
}
|
||
if (UrlUtils.isRegistry(url)) { // 检测URL协议是否为registry,若是,说明用户想使用指定的注册中心
|
||
// 这里会将map中的参数整理成一个参数添加到refer参数中
|
||
urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
|
||
} else {
|
||
// 将map中的参数添加到url中
|
||
urls.add(ClusterUtils.mergeUrl(url, map));
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())) {
|
||
checkRegistry();
|
||
// 加载注册中心的地址RegistryURL
|
||
List<URL> us = ConfigValidationUtils.loadRegistries(this, false);
|
||
if (CollectionUtils.isNotEmpty(us)) {
|
||
for (URL u : us) {
|
||
URL monitorUrl = ConfigValidationUtils.loadMonitor(this, u);
|
||
if (monitorUrl != null) {
|
||
map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
|
||
}
|
||
// 将map中的参数整理成refer参数,添加到RegistryURL中
|
||
urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
|
||
}
|
||
}
|
||
if (urls.isEmpty()) { // 既不是服务直连,也没有配置注册中心,抛出异常
|
||
throw new IllegalStateException("...");
|
||
}
|
||
}
|
||
}
|
||
if (urls.size() == 1) {
|
||
// 在单注册中心或是直连单个服务提供方的时候,通过Protocol的适配器选择对应的Protocol实现创建Invoker对象
|
||
invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
|
||
} else {
|
||
// 多注册中心或是直连多个服务提供方的时候,会根据每个URL创建Invoker对象
|
||
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
|
||
URL registryURL = null;
|
||
for (URL url : urls) {
|
||
invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
|
||
if (UrlUtils.isRegistry(url)) { // 确定是多注册中心,还是直连多个Provider
|
||
registryURL = url;
|
||
}
|
||
}
|
||
if (registryURL != null) {
|
||
// 多注册中心的场景中,会使用ZoneAwareCluster作为Cluster默认实现,多注册中心之间的选择
|
||
URL u = registryURL.addParameterIfAbsent(CLUSTER_KEY, ZoneAwareCluster.NAME);
|
||
invoker = CLUSTER.join(new StaticDirectory(u, invokers));
|
||
} else {
|
||
// 多个Provider直连的场景中,使用Cluster适配器选择合适的扩展实现
|
||
invoker = CLUSTER.join(new StaticDirectory(invokers));
|
||
}
|
||
}
|
||
}
|
||
if (shouldCheck() && !invoker.isAvailable()) {
|
||
// 根据check配置决定是否检测Provider的可用性
|
||
invoker.destroy();
|
||
throw new IllegalStateException("...");
|
||
}
|
||
...// 元数据处理相关的逻辑
|
||
// 通过ProxyFactory适配器选择合适的ProxyFactory扩展实现,创建代理对象
|
||
return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<h3>RegistryProtocol</h3>
|
||
|
||
<p>在直连 Provider 的场景中,会使用 DubboProtocol.refer() 方法完成服务引用,DubboProtocol.refer() 方法的具体实现在前面[第 25 课时]中已经详细介绍过了,这里我们重点来看存在注册中心的场景中,Dubbo Consumer 是如何通过 RegistryProtocol 完成服务引用的。</p>
|
||
|
||
<p>在 RegistryProtocol.refer() 方法中,会先根据 URL 获取注册中心的 URL,再调用 doRefer 方法生成 Invoker,在 refer() 方法中会使用 MergeableCluster 处理多 group 引用的场景。</p>
|
||
|
||
<pre><code>public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
|
||
url = getRegistryUrl(url); // 从URL中获取注册中心的URL
|
||
// 获取Registry实例,这里的RegistryFactory对象是通过Dubbo SPI的自动装载机制注入的
|
||
Registry registry = registryFactory.getRegistry(url);
|
||
if (RegistryService.class.equals(type)) {
|
||
return proxyFactory.getInvoker((T) registry, type, url);
|
||
}
|
||
// 从注册中心URL的refer参数中获取此次服务引用的一些参数,其中就包括group
|
||
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
|
||
String group = qs.get(GROUP_KEY);
|
||
if (group != null && group.length() > 0) {
|
||
if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
|
||
// 如果此次可以引用多个group的服务,则Cluser实现使用MergeableCluster实现,
|
||
// 这里的getMergeableCluster()方法就会通过Dubbo SPI方式找到MergeableCluster实例
|
||
return doRefer(getMergeableCluster(), registry, type, url);
|
||
}
|
||
}
|
||
// 如果没有group参数或是只指定了一个group,则通过Cluster适配器选择Cluster实现
|
||
return doRefer(cluster, registry, type, url);
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>在 doRefer() 方法中,首先会根据 URL 初始化 RegistryDirectory 实例,然后生成 Subscribe URL 并进行注册,之后会通过 Registry 订阅服务,最后通过 Cluster 将多个 Invoker 合并成一个 Invoker 返回给上层,具体实现如下:</p>
|
||
|
||
<pre><code>private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
|
||
// 创建RegistryDirectory实例
|
||
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
|
||
directory.setRegistry(registry);
|
||
directory.setProtocol(protocol);
|
||
// 生成SubscribeUrl,协议为consumer,具体的参数是RegistryURL中refer参数指定的参数
|
||
Map<String, String> parameters = new HashMap<String, String>(directory.getConsumerUrl().getParameters());
|
||
URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
|
||
if (directory.isShouldRegister()) {
|
||
directory.setRegisteredConsumerUrl(subscribeUrl); // 在SubscribeUrl中添加category=consumers和check=false参数
|
||
registry.register(directory.getRegisteredConsumerUrl()); // 服务注册,在Zookeeper的consumers节点下,添加该Consumer对应的节点
|
||
}
|
||
directory.buildRouterChain(subscribeUrl); // 根据SubscribeUrl创建服务路由
|
||
// 订阅服务,toSubscribeUrl()方法会将SubscribeUrl中category参数修改为"providers,configurators,routers"
|
||
// RegistryDirectory的subscribe()在前面详细分析过了,其中会通过Registry订阅服务,同时还会添加相应的监听器
|
||
directory.subscribe(toSubscribeUrl(subscribeUrl));
|
||
// 注册中心中可能包含多个Provider,相应地,也就有多个Invoker,
|
||
// 这里通过前面选择的Cluster将多个Invoker对象封装成一个Invoker对象
|
||
Invoker<T> invoker = cluster.join(directory);
|
||
// 根据URL中的registry.protocol.listener参数加载相应的监听器实现
|
||
List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
|
||
if (CollectionUtils.isEmpty(listeners)) {
|
||
return invoker;
|
||
}
|
||
// 为了方便在监听器中回调,这里将此次引用使用到的Directory对象、Cluster对象、Invoker对象以及SubscribeUrl
|
||
// 封装到一个RegistryInvokerWrapper中,传递给监听器
|
||
RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker, subscribeUrl);
|
||
for (RegistryProtocolListener listener : listeners) {
|
||
listener.onRefer(this, registryInvokerWrapper);
|
||
}
|
||
return registryInvokerWrapper;
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>这里涉及的 RegistryDirectory、Router 接口、Cluster 接口及其相关的扩展实现,我们都已经在前面的课时详细分析过了,这里不再重复。</p>
|
||
|
||
<h3>总结</h3>
|
||
|
||
<p>本课时,我们重点介绍了 Dubbo 服务引用的整个流程。</p>
|
||
|
||
<ul>
|
||
|
||
<li>首先,我们介绍了 DubboBootStrap 这个入口门面类与服务引用相关的方法,其中涉及 referServices()、reference() 等核心方法。</li>
|
||
|
||
<li>接下来,我们分析了 ReferenceConfigCache 这个 ReferenceConfig 对象缓存,以及 ReferenceConfig 实现服务引用的核心流程。</li>
|
||
|
||
<li>最后,我们还讲解了 RegistryProtocol 从注册中心引用服务的核心实现。</li>
|
||
|
||
</ul>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/41 加餐:一键通关服务发布全流程.md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/Dubbo源码解读与实战-完/43 服务自省设计方案:新版本新方案.md.html">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70996f791aaa3d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
|
||
</body>
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
|
||
<script>
|
||
|
||
window.dataLayer = window.dataLayer || [];
|
||
function gtag() {
|
||
|
||
dataLayer.push(arguments);
|
||
|
||
}
|
||
gtag('js', new Date());
|
||
|
||
gtag('config', 'G-NPSEEVD756');
|
||
|
||
var path = window.location.pathname
|
||
|
||
var cookie = getCookie("lastPath");
|
||
|
||
console.log(path)
|
||
|
||
if (path.replace("/", "") === "") {
|
||
|
||
if (cookie.replace("/", "") !== "") {
|
||
|
||
console.log(cookie)
|
||
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
setCookie("lastPath", path)
|
||
|
||
}
|
||
function setCookie(cname, cvalue) {
|
||
|
||
var d = new Date();
|
||
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
|
||
var expires = "expires=" + d.toGMTString();
|
||
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
|
||
}
|
||
function getCookie(cname) {
|
||
|
||
var name = cname + "=";
|
||
|
||
var ca = document.cookie.split(';');
|
||
|
||
for (var i = 0; i < ca.length; i++) {
|
||
|
||
var c = ca[i].trim();
|
||
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
|
||
}
|
||
|
||
return "";
|
||
|
||
}
|
||
</script>
|
||
</html>
|
||
|