Dubbo 底层原理系列(四):服务引用
2020-08-03·5 分钟阅读
前言
服务引用是 Dubbo 消费端启动的核心流程,从注册中心订阅服务并创建代理对象。理解服务引用原理有助于排查服务调用问题。
技术亮点
| 技术点 | 难度 | 面试价值 | 本文覆盖 |
|---|---|---|---|
| 服务引用流程 | ⭐⭐⭐⭐ | 高频考点 | ✅ |
| 代理创建 | ⭐⭐⭐⭐ | 进阶考点 | ✅ |
| Directory | ⭐⭐⭐⭐⭐ | 进阶考点 | ✅ |
| Cluster Invoker | ⭐⭐⭐⭐⭐ | 高频考点 | ✅ |
面试考点
- Dubbo 服务引用的整体流程是怎样的?
- 代理对象是如何创建的?
- Directory 的作用是什么?
- Cluster Invoker 是如何构建的?
服务引用入口
配置解析
┌─────────────────────────────────────────────────────────────────────────┐
│ 服务引用配置 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Spring XML 配置: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ <dubbo:reference │ │
│ │ id="userService" │ │
│ │ interface="com.example.UserService" │ │
│ │ version="1.0.0" │ │
│ │ group="production" │ │
│ │ timeout="3000" │ │
│ │ retries="2" │ │
│ │ check="false" /> │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 注解方式: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ @DubboReference( │ │
│ │ version = "1.0.0", │ │
│ │ group = "production", │ │
│ │ timeout = 3000 │ │
│ │ ) │ │
│ │ private UserService userService; │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
入口类 ReferenceBean
┌─────────────────────────────────────────────────────────────────────────┐
│ ReferenceBean 入口 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ReferenceBean 继承关系: │ │
│ │ │ │
│ │ ReferenceBean<T> │ │
│ │ extends ReferenceConfig<T> │ │
│ │ implements FactoryBean<T>, │ │
│ │ InitializingBean │ │
│ │ │ │
│ │ 核心方法: │ │
│ │ │ │
│ │ 1. getObject() - FactoryBean 接口,返回代理对象 │ │
│ │ 2. afterPropertiesSet() - 初始化 │ │
│ │ 3. get() - 创建代理对象入口 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 触发时机: │
│ • Spring 容器注入 Bean 时调用 getObject() │
│ • 第一次调用时懒加载(check=false) │
│ • 启动时立即检查(check=true) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
服务引用流程
整体流程图
┌─────────────────────────────────────────────────────────────────────────┐
│ 服务引用整体流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ReferenceConfig.get() │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 1. 前置检查 │ │
│ │ │ │ │
│ │ ├──► 检查是否已创建 │ │
│ │ ├──► 检查配置是否完整 │ │
│ │ └──► 组装 URL │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 2. 本地引用检查 │ │
│ │ │ │ │
│ │ └──► 检查是否有本地实现 │ │
│ │ │ │ │
│ │ └──► 有:创建本地 Invoker │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 3. 远程引用 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ createProxy(map) │ │
│ │ │ │ │
│ │ ├──► 直连模式:直接创建 Invoker │ │
│ │ │ │ │
│ │ └──► 注册中心模式: │ │
│ │ │ │ │
│ │ ├──► 订阅注册中心 │ │
│ │ ├──► 创建 Directory │ │
│ │ └──► 创建 Cluster Invoker │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 4. 创建代理对象 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ proxy = proxyFactory.getProxy(invoker) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 返回代理对象 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
createProxy 流程
┌─────────────────────────────────────────────────────────────────────────┐
│ createProxy 详细流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ createProxy(Map<String, String> map) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 1. 判断引用范围 │ │
│ │ │ │ │
│ │ ├──► scope=local:本地引用 │ │
│ │ │ │ │
│ │ ├──► scope=remote:远程引用 │ │
│ │ │ │ │
│ │ └──► 默认:先检查本地,再远程 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 2. 直连模式判断 │ │
│ │ │ │ │
│ │ └──► URL 包含 ip:port → 直连 │ │
│ │ │ │ │
│ │ └──► invoker = protocol.refer(url) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 3. 注册中心模式 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ List<URL> us = loadRegistries(); │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ for (URL url : us) { │ │
│ │ invoker = protocol.refer(url); // RegistryProtocol │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 4. 创建 Cluster Invoker │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ if (invokers.size() == 1) { │ │
│ │ return invokers.get(0); │ │
│ │ } │ │
│ │ return cluster.join(directory); │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 5. 创建代理 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ return (T) proxyFactory.getProxy(invoker); │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Directory 服务目录
Directory 架构
┌─────────────────────────────────────────────────────────────────────────┐
│ Directory 架构 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Directory 接口:维护服务提供者列表 │ │
│ │ │ │
│ │ public interface Directory<T> extends Node { │ │
│ │ List<Invoker<T>> list(Invocation invocation); │ │
│ │ Class<T> getInterface(); │ │
│ │ } │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 实现类: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ RegistryDirectory │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ • 从注册中心订阅服务提供者列表 │ │ │
│ │ │ • 监听注册中心变化,动态更新 Invoker 列表 │ │ │
│ │ │ • 实现 NotifyListener 接口 │ │ │
│ │ │ │ │ │
│ │ │ 核心属性: │ │ │
│ │ │ • Map<String, Invoker<T>> urlInvokerMap │ │ │
│ │ │ • Map<String, List<Invoker<T>>> methodInvokerMap │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
服务订阅流程
┌─────────────────────────────────────────────────────────────────────────┐
│ 服务订阅流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ RegistryProtocol.refer() │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 1. 获取注册中心实例 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ Registry registry = registryFactory.getRegistry(url); │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 2. 创建 Directory │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ RegistryDirectory<T> directory = new RegistryDirectory<>(); │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 3. 订阅服务 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ registry.subscribe(url, directory); │ │
│ │ │ │ │
│ │ └──► ZookeeperRegistry.subscribe() │ │
│ │ │ │ │
│ │ ├──► 创建监听节点 │ │
│ │ ├──► 获取 providers 列表 │ │
│ │ └──► 触发 notify() │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 4. 构建 Cluster Invoker │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ClusterInvoker<T> clusterInvoker = cluster.join(directory);│ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 返回 clusterInvoker │
│ │
└─────────────────────────────────────────────────────────────────────────┘
服务变更通知
┌─────────────────────────────────────────────────────────────────────────┐
│ 服务变更通知流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Zookeeper 节点变化 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ RegistryDirectory.notify(List<URL> urls) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 1. 解析 URL 分类 │ │
│ │ │ │ │
│ │ ├──► providers:服务提供者 │ │
│ │ ├──► routers:路由规则 │ │
│ │ └──► configurators:动态配置 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 2. 更新 Invoker 列表 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ refreshInvoker(urls) │ │
│ │ │ │ │
│ │ ├──► 销毁无效 Invoker │ │
│ │ └──► 创建新 Invoker │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Invoker 列表更新完成 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Cluster Invoker
Cluster 架构
┌─────────────────────────────────────────────────────────────────────────┐
│ Cluster 架构 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Cluster 接口:将多个 Invoker 合并成一个 │ │
│ │ │ │
│ │ @SPI("failover") │ │
│ │ public interface Cluster { │ │
│ │ @Adaptive │ │
│ │ <T> Invoker<T> join(Directory<T> directory); │ │
│ │ } │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Cluster Invoker 继承关系: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ AbstractClusterInvoker │ │
│ │ │ │ │
│ │ ├──► FailoverClusterInvoker (失败重试) │ │
│ │ ├──► FailfastClusterInvoker (快速失败) │ │
│ │ ├──► FailsafeClusterInvoker (失败安全) │ │
│ │ ├──► FailbackClusterInvoker (失败自动恢复) │ │
│ │ ├──► ForkingClusterInvoker (并行调用) │ │
│ │ └──► BroadcastClusterInvoker (广播调用) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
AbstractClusterInvoker 核心方法
┌─────────────────────────────────────────────────────────────────────────┐
│ AbstractClusterInvoker 核心逻辑 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ invoke(Invocation invocation) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 1. 获取 Invoker 列表 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ List<Invoker<T>> invokers = directory.list(invocation); │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 2. 路由过滤 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ for (Router router : routers) { │ │
│ │ invokers = router.route(invokers); │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 3. 负载均衡选择 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ Invoker<T> invoker = select(loadbalance, invocation, │ │
│ │ invokers, selected); │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 4. 执行调用 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ doInvoke(invocation, invokers, loadbalance) │ │
│ │ │ │ │
│ │ └──► 子类实现具体容错逻辑 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 返回调用结果 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
代理对象创建
ProxyFactory.getProxy
┌─────────────────────────────────────────────────────────────────────────┐
│ 代理对象创建 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ proxyFactory.getProxy(invoker) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ JavassistProxyFactory.getProxy() │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 1. 生成代理类代码 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 代码模板: │ │
│ │ public class proxy0 implements UserService { │ │
│ │ private Invoker invoker; │ │
│ │ │ │
│ │ public User getUser(Long id) { │ │
│ │ Object[] args = new Object[]{id}; │ │
│ │ Invocation inv = new RpcInvocation( │ │
│ │ "getUser", args, ...); │ │
│ │ return (User) invoker.invoke(inv); │ │
│ │ } │ │
│ │ } │ │
│ │ │ │
│ │ 2. 编译并实例化 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ Class<?> clazz = compiler.compile(code); │ │
│ │ Object proxy = clazz.newInstance(); │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 代理对象特点: │
│ • 实现服务接口 │
│ • 持有 Invoker 引用 │
│ • 方法调用转为 Invoker.invoke() │
│ │
└─────────────────────────────────────────────────────────────────────────┘
总结
本文介绍了 Dubbo 服务引用原理:
| 概念 | 说明 |
|---|---|
| 入口 | ReferenceBean.getObject() |
| Directory | 维护服务提供者列表 |
| Cluster | 合并多个 Invoker |
| 代理 | 将方法调用转为 Invoker.invoke() |
参考资料
下一章预告
下一章将深入解析 注册中心原理,包括:
- 注册中心接口设计
- Zookeeper 注册中心实现
- 订阅与通知机制
相关文章
Dubbo 底层原理系列(七):集群容错
2020-09-03·5 分钟阅读
深入解析 Dubbo 集群容错原理,包括 Failover、Failfast、Failsafe、Failback、Forking、Broadcast 等容错模式。
Dubbo 底层原理系列(六):负载均衡
2020-08-25·5 分钟阅读
深入解析 Dubbo 负载均衡原理,包括 Random、RoundRobin、LeastActive、ConsistentHash 策略的实现细节。
Dubbo 底层原理系列(三):服务暴露
2020-07-25·5 分钟阅读
深入解析 Dubbo 服务暴露原理,包括服务导出流程、URL 解析、本地暴露与远程暴露以及注册中心注册。