← 返回文章列表

Dubbo 底层原理系列(三):服务暴露

2020-07-25·5 分钟阅读

前言

服务暴露是 Dubbo 服务端启动的核心流程,将服务实现类封装成 Invoker 并注册到注册中心。理解服务暴露原理对于排查服务启动问题至关重要。

技术亮点

技术点难度面试价值本文覆盖
服务导出流程⭐⭐⭐⭐高频考点
Invoker 创建⭐⭐⭐⭐进阶考点
本地/远程暴露⭐⭐⭐⭐⭐进阶考点
注册中心注册⭐⭐⭐高频考点

面试考点

  1. Dubbo 服务暴露的整体流程是怎样的?
  2. 本地暴露和远程暴露有什么区别?
  3. Invoker 是如何创建的?
  4. 服务是如何注册到注册中心的?

服务暴露入口

配置解析

┌─────────────────────────────────────────────────────────────────────────┐
│                        服务暴露配置                                      │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Spring XML 配置:                                                      │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  <dubbo:service                                                │   │
│  │      interface="com.example.UserService"                       │   │
│  │      ref="userService"                                         │   │
│  │      version="1.0.0"                                           │   │
│  │      group="production"                                        │   │
│  │      timeout="3000"                                            │   │
│  │      retries="2"                                               │   │
│  │      protocol="dubbo"                                          │   │
│  │      registry="registry1" />                                   │   │
│  │                                                                 │   │
│  │  <bean id="userService" class="com.example.UserServiceImpl"/>  │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  注解方式:                                                             │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  @DubboService(                                                │   │
│  │      version = "1.0.0",                                        │   │
│  │      group = "production",                                     │   │
│  │      timeout = 3000                                            │   │
│  │  )                                                             │   │
│  │  public class UserServiceImpl implements UserService {          │   │
│  │      // ...                                                    │   │
│  │  }                                                             │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

入口类 ServiceBean

┌─────────────────────────────────────────────────────────────────────────┐
│                        ServiceBean 入口                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  ServiceBean 继承关系:                                          │   │
│  │                                                                 │   │
│  │  ServiceBean                                                    │   │
│  │      extends ServiceConfig                                      │   │
│  │      implements InitializingBean,                               │   │
│  │                 ApplicationListener<ContextRefreshedEvent>      │   │
│  │                                                                 │   │
│  │  核心方法:                                                     │   │
│  │                                                                 │   │
│  │  1. afterPropertiesSet() - 属性设置后初始化                     │   │
│  │  2. onApplicationEvent() - Spring 容器刷新事件触发               │   │
│  │  3. export() - 服务暴露入口                                     │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  触发时机:                                                             │
│  • Spring 容器刷新完成后触发 onApplicationEvent()                       │
│  • 调用 ServiceConfig.export() 开始服务暴露                             │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

服务暴露流程

整体流程图

┌─────────────────────────────────────────────────────────────────────────┐
│                        服务暴露整体流程                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ServiceConfig.export()                                                 │
│         │                                                               │
│         ▼                                                               │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  1. 前置检查                                                     │   │
│  │         │                                                       │   │
│  │         ├──► 检查是否已暴露                                      │   │
│  │         ├──► 检查配置是否完整                                    │   │
│  │         └──► 延迟暴露处理                                        │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│         │                                                               │
│         ▼                                                               │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  2. 组装 URL                                                     │   │
│  │         │                                                       │   │
│  │         ▼                                                       │   │
│  │     URL url = new URL(                                          │   │
│  │         protocol, host, port, path, parameters)                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│         │                                                               │
│         ▼                                                               │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  3. 服务暴露                                                     │   │
│  │         │                                                       │   │
│  │         ├──► 本地暴露(injvm 协议)                              │   │
│  │         │       │                                               │   │
│  │         │       └──► exportLocal(url)                           │   │
│  │         │                                                       │   │
│  │         └──► 远程暴露(dubbo 协议)                              │   │
│  │                 │                                               │   │
│  │                 └──► doExportUrls()                             │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│         │                                                               │
│         ▼                                                               │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  4. 注册到注册中心                                               │   │
│  │         │                                                       │   │
│  │         └──► Registry.register(url)                             │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│         │                                                               │
│         ▼                                                               │
│     服务暴露完成                                                        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

URL 组装

┌─────────────────────────────────────────────────────────────────────────┐
│                        URL 组装                                          │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Dubbo URL 格式:                                                       │
│  protocol://username:password@host:port/path?key=value&key=value        │
│                                                                         │
│  示例:                                                                 │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  dubbo://192.168.1.100:20880/com.example.UserService?          │   │
│  │      application=demo-provider&                                │   │
│  │      version=1.0.0&                                            │   │
│  │      group=production&                                         │   │
│  │      timeout=3000&                                             │   │
│  │      retries=2&                                                │   │
│  │      side=provider&                                            │   │
│  │      timestamp=1609545600000&                                  │   │
│  │      pid=12345                                                 │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  URL 核心参数:                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  protocol: 协议名称(dubbo、injvm、rmi 等)                      │   │
│  │  host: 服务主机地址                                             │   │
│  │  port: 服务端口                                                 │   │
│  │  path: 服务接口全限定名                                         │   │
│  │  parameters: 配置参数 Map                                       │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

本地暴露与远程暴露

暴露策略

┌─────────────────────────────────────────────────────────────────────────┐
│                        本地暴露 vs 远程暴露                              │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  本地暴露(scope=local):                                       │   │
│  │  ┌───────────────────────────────────────────────────────────┐ │   │
│  │  │                                                           │ │   │
│  │  │  • 同一 JVM 内调用,不走网络                                │ │   │
│  │  │  • 使用 injvm 协议                                         │ │   │
│  │  │  • 性能更高,延迟更低                                       │ │   │
│  │  │  • URL 格式:injvm://127.0.0.1/...                         │ │   │
│  │  │                                                           │ │   │
│  │  └───────────────────────────────────────────────────────────┘ │   │
│  │                                                                 │   │
│  │  远程暴露(scope=remote):                                      │   │
│  │  ┌───────────────────────────────────────────────────────────┐ │   │
│  │  │                                                           │ │   │
│  │  │  • 跨 JVM 调用,走网络通信                                  │ │   │
│  │  │  • 使用 dubbo 协议(默认)                                  │ │   │
│  │  │  • 需要注册到注册中心                                       │ │   │
│  │  │  • URL 格式:dubbo://192.168.1.100:20880/...               │ │   │
│  │  │                                                           │ │   │
│  │  └───────────────────────────────────────────────────────────┘ │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  配置方式:                                                             │
│  <dubbo:service scope="remote" />  <!-- 仅远程暴露 -->                  │
│  <dubbo:service scope="local" />   <!-- 仅本地暴露 -->                  │
│  <dubbo:service scope="true" />    <!-- 两者都暴露(默认)-->            │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

本地暴露流程

┌─────────────────────────────────────────────────────────────────────────┐
│                        本地暴露流程                                      │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  exportLocal(url)                                                       │
│         │                                                               │
│         ▼                                                               │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  1. 构建本地 URL                                                 │   │
│  │         │                                                       │   │
│  │         ▼                                                       │   │
│  │     URL localUrl = URL.valueOf(                                 │   │
│  │         "injvm://127.0.0.1/..."                                 │   │
│  │     );                                                          │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│         │                                                               │
│         ▼                                                               │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  2. 创建 Invoker                                                 │   │
│  │         │                                                       │   │
│  │         ▼                                                       │   │
│  │     Invoker<?> invoker = proxyFactory.getInvoker(               │   │
│  │         ref, interfaceClass, localUrl                           │   │
│  │     );                                                          │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│         │                                                               │
│         ▼                                                               │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  3. 协议导出                                                     │   │
│  │         │                                                       │   │
│  │         ▼                                                       │   │
│  │     Exporter<?> exporter = protocol.export(invoker);            │   │
│  │         │                                                       │   │
│  │         └──► InjvmProtocol.export()                             │   │
│  │                   │                                             │   │
│  │                   └──► 存入 exporterMap                         │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│         │                                                               │
│         ▼                                                               │
│     本地暴露完成                                                        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

远程暴露流程

┌─────────────────────────────────────────────────────────────────────────┐
│                        远程暴露流程                                      │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  doExportUrls()                                                         │
│         │                                                               │
│         ▼                                                               │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  1. 获取注册中心 URL                                             │   │
│  │         │                                                       │   │
│  │         ▼                                                       │   │
│  │     List<URL> registryURLs = loadRegistries();                  │   │
│  │         │                                                       │   │
│  │         └──► zookeeper://127.0.0.1:2181/...                     │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│         │                                                               │
│         ▼                                                               │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  2. 遍历协议配置                                                 │   │
│  │         │                                                       │   │
│  │         └──► 支持多协议暴露                                      │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│         │                                                               │
│         ▼                                                               │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  3. 创建 Invoker                                                 │   │
│  │         │                                                       │   │
│  │         ▼                                                       │   │
│  │     Invoker<?> invoker = proxyFactory.getInvoker(               │   │
│  │         ref, interfaceClass, url                                │   │
│  │     );                                                          │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│         │                                                               │
│         ▼                                                               │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  4. 协议导出(启动 Netty Server)                                │   │
│  │         │                                                       │   │
│  │         ▼                                                       │   │
│  │     Exporter<?> exporter = protocol.export(invoker);            │   │
│  │         │                                                       │   │
│  │         └──► DubboProtocol.export()                             │   │
│  │                   │                                             │   │
│  │                   ├──► openServer(url) 启动 Netty               │   │
│  │                   │                                             │   │
│  │                   └──► 存入 exporterMap                         │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│         │                                                               │
│         ▼                                                               │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │  5. 注册到注册中心                                               │   │
│  │         │                                                       │   │
│  │         ▼                                                       │   │
│  │     RegistryProtocol.export()                                   │   │
│  │         │                                                       │   │
│  │         ├──► registry.register(url)                             │   │
│  │         │                                                       │   │
│  │         └──► 在 Zookeeper 创建节点                               │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│         │                                                               │
│         ▼                                                               │
│     远程暴露完成                                                        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Invoker 创建

ProxyFactory

┌─────────────────────────────────────────────────────────────────────────┐
│                        ProxyFactory 创建 Invoker                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  @SPI("javassist")                                              │   │
│  │  public interface ProxyFactory {                                │   │
│  │                                                                 │   │
│  │      // 服务端:将服务实现封装成 Invoker                          │   │
│  │      @Adaptive                                                  │   │
│  │      <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url);│   │
│  │                                                                 │   │
│  │      // 消费端:创建代理对象                                      │   │
│  │      @Adaptive                                                  │   │
│  │      <T> T getProxy(Invoker<T> invoker);                        │   │
│  │  }                                                              │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  实现类:                                                               │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  • JavassistProxyFactory:使用 Javassist 生成代理类(默认)       │   │
│  │  • JdkProxyFactory:使用 JDK 动态代理                            │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  生成的 Invoker 包装:                                                  │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  // Javassist 生成的代理类核心逻辑                               │   │
│  │  public class Wrapper0 implements Wrapper {                     │   │
│  │      public Object invokeMethod(Object instance, String name,   │   │
│  │                                 Class[] types, Object[] args) { │   │
│  │          if ("getUser".equals(name)) {                          │   │
│  │              return ((UserService) instance).getUser(...);      │   │
│  │          }                                                      │   │
│  │          // ...                                                 │   │
│  │      }                                                          │   │
│  │  }                                                              │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

注册中心注册

Zookeeper 注册

┌─────────────────────────────────────────────────────────────────────────┐
│                        Zookeeper 注册节点结构                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Zookeeper 目录结构:                                                   │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  /dubbo                                                         │   │
│  │      /com.example.UserService                                   │   │
│  │          /providers                                             │   │
│  │              /dubbo://192.168.1.100:20880/... (临时节点)         │   │
│  │              /dubbo://192.168.1.101:20880/... (临时节点)         │   │
│  │          /consumers                                             │   │
│  │              /consumer://192.168.1.200/... (临时节点)            │   │
│  │          /routers                                               │   │
│  │              /route://... (路由规则)                             │   │
│  │          /configurators                                         │   │
│  │              /override://... (动态配置)                          │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  节点类型:                                                             │
│  • providers:服务提供者 URL                                            │
│  • consumers:服务消费者 URL                                            │
│  • routers:路由规则                                                    │
│  • configurators:动态配置                                              │
│                                                                         │
│  节点特性:                                                             │
│  • 临时节点:服务下线后自动删除                                          │
│  • 持久节点:路由规则和配置                                              │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

总结

本文介绍了 Dubbo 服务暴露原理:

概念说明
入口ServiceBean.onApplicationEvent()
本地暴露injvm 协议,同一 JVM 内调用
远程暴露dubbo 协议,跨 JVM 调用
Invoker服务实现的可执行体封装
注册注册到 Zookeeper providers 节点

参考资料

下一章预告

下一章将深入解析 服务引用原理,包括:

  • 服务引用流程
  • 代理对象创建
  • 集群 Invoker 构建
分享: