Dubbo 底层原理系列(三):服务暴露
2020-07-25·5 分钟阅读
前言
服务暴露是 Dubbo 服务端启动的核心流程,将服务实现类封装成 Invoker 并注册到注册中心。理解服务暴露原理对于排查服务启动问题至关重要。
技术亮点
| 技术点 | 难度 | 面试价值 | 本文覆盖 |
|---|---|---|---|
| 服务导出流程 | ⭐⭐⭐⭐ | 高频考点 | ✅ |
| Invoker 创建 | ⭐⭐⭐⭐ | 进阶考点 | ✅ |
| 本地/远程暴露 | ⭐⭐⭐⭐⭐ | 进阶考点 | ✅ |
| 注册中心注册 | ⭐⭐⭐ | 高频考点 | ✅ |
面试考点
- Dubbo 服务暴露的整体流程是怎样的?
- 本地暴露和远程暴露有什么区别?
- Invoker 是如何创建的?
- 服务是如何注册到注册中心的?
服务暴露入口
配置解析
┌─────────────────────────────────────────────────────────────────────────┐
│ 服务暴露配置 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 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 构建
相关文章
Dubbo 底层原理系列(七):集群容错
2020-09-03·5 分钟阅读
深入解析 Dubbo 集群容错原理,包括 Failover、Failfast、Failsafe、Failback、Forking、Broadcast 等容错模式。
Dubbo 底层原理系列(六):负载均衡
2020-08-25·5 分钟阅读
深入解析 Dubbo 负载均衡原理,包括 Random、RoundRobin、LeastActive、ConsistentHash 策略的实现细节。
Dubbo 底层原理系列(四):服务引用
2020-08-03·5 分钟阅读
深入解析 Dubbo 服务引用原理,包括服务引用流程、代理对象创建、Directory 与 Cluster Invoker 构建。