本文目录导读:

Dubbo 服务的发布(暴露)过程,核心是将一个普通的 Java Bean 通过网络注册成一个可以被远程调用的服务,整个过程主要涉及 服务导出、注册 和 启动 三个环节。
以下是基于 Dubbo 最新版本(3.x,兼容 2.x)的标准发布流程和底层原理分析。
核心流程概括
服务端启动
|
1. 扫描 @DubboService / @Service 注解
|
2. 组装 URL (包含 IP, Port, 协议, 接口名, 版本等)
|
3. 导出 (Export)
| ├── 本地导出 (Injvm) -> 存于本地 Map
| └── 远程导出 (Netty/Server) -> 端口监听
|
4. 注册 (Register)
| └── 将 URL 写入注册中心 (ZooKeeper/Nacos)
|
5. 订阅 (可选)
| └── 监听配置/路由变化
|
服务启动完成
发布方式(三种主流方式)
注解方式(最常用,Spring Boot 项目)
这是企业中最常用的方式,结合 Spring Boot 使用。
步骤:
-
引入依赖(在
pom.xml中):<!-- Dubbo Spring Boot Starter --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>3.x</version> </dependency> <!-- 注册中心客户端(Nacos) --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-nacos-spring-boot-starter</artifactId> </dependency> -
配置
application.yml:dubbo: application: name: user-service-provider # 服务名 protocol: name: dubbo # 协议(dubbo, tri, rest, http, grpc) port: 20880 # 服务端口 registry: address: nacos://127.0.0.1:8848 # 注册中心地址 -
编写服务接口(单独抽成 API 模块或放在 provider 中):
public interface UserService { String getUserName(Long id); } -
编写实现类并使用
@DubboService注解: 这是发布的关键步骤。@DubboService注解会告诉 Dubbo 框架,这个类需要被发布成远程服务。// 替换低版本的 @Service 注解(dubbo 3.x 推荐使用 @DubboService) @DubboService(version = "1.0.0", group = "test", timeout = 3000) @Component // 让 Spring 管理 Bean public class UserServiceImpl implements UserService { @Override public String getUserName(Long id) { return "User_" + id; } } -
启动 Spring Boot 应用: 当
ApplicationContext刷新完成后,Dubbo 会自动扫描@DubboService注解,并完成发布和注册。
XML 配置方式(传统 Spring 项目,也适用于 Spring Boot)
虽然现在注解更流行,但 XML 配置仍然是理解 Dubbo 底层机制的基础。
步骤:
-
编写接口和实现类(同上)。
-
创建配置文件
dubbo-provider.xml:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xsi:schemaLocation="..."> <!-- 1. 应用信息 --> <dubbo:application name="user-service-provider"/> <!-- 2. 注册中心 --> <dubbo:registry address="nacos://127.0.0.1:8848"/> <!-- 3. 协议和端口 --> <dubbo:protocol name="dubbo" port="20880"/> <!-- 4. 声明需要暴露的服务接口 --> <dubbo:service interface="com.example.UserService" ref="userService" version="1.0.0"/> <!-- 5. 具体的实现 Bean --> <bean id="userService" class="com.example.impl.UserServiceImpl"/> </beans> -
启动 Spring 容器:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo-provider.xml"); context.start(); System.out.println("Service is running...");
API 方式(纯代码,无 Spring / 测试)
适用于非 Spring 环境或需要动态发布服务的场景。
步骤:(基于 Dubbo 2.x / 3.x API)
import org.apache.dubbo.config.*;
import org.apache.dubbo.rpc.*;
public class ApiProvider {
public static void main(String[] args) throws Exception {
// 1. 应用配置
ApplicationConfig application = new ApplicationConfig("sample-provider");
// 2. 注册中心配置
RegistryConfig registry = new RegistryConfig();
registry.setAddress("zookeeper://127.0.0.1:2181");
// 3. 协议配置
ProtocolConfig protocol = new ProtocolConfig();
protocol.setName("dubbo");
protocol.setPort(20880);
// 4. 服务配置
ServiceConfig<UserService> service = new ServiceConfig<>();
service.setApplication(application);
service.setRegistry(registry);
service.setProtocol(protocol);
service.setInterface(UserService.class); // 接口
service.setRef(new UserServiceImpl()); // 实现
service.setVersion("1.0.0");
// 5. 导出服务
service.export();
System.out.println("Service exported, waiting...");
System.in.read(); // 按任意键退出
}
}
发布底层机制(深入理解)
当触发 export() 方法时,Dubbo 内部会执行以下关键步骤:
本地导出(Injvm 协议)
- 为了提高性能,同一 JVM 内的调用会走本地调用(不经过网络)。
- 动作:将服务以
injvm://...的 URL 形式存入urls集合,并暴露一个本地的 Invoker。 - 结果:消费者如果在同一 JVM,直接走内存调用。
远程导出(Netty Server)
- 关键动作:
DubboProtocol.export()。 - 过程:
- 解析 URL(
dubbo://192.168.1.2:20880/com.example.UserService?version=1.0.0...)。 - 创建或复用
Exchanger->ExchangeServer->TransportServer。 - 打开端口:在配置的端口(如 20880)启动一个 Netty Server,开始监听客户端的网络请求。
- 绑定请求处理器:将
ExchangeHandler绑定到 Netty Channel 上,用于处理请求。
- 解析 URL(
服务注册(Registry)
- 关键动作:
RegistryProtocol.export()。 - 过程:
- 获取注册中心客户端实例(Zookeeper/Nacos)。
- 将服务 URL 写入注册中心,在 Zookeeper 中会创建路径:
/dubbo/com.example.UserService/providers/dubbo%3A%2F%2F192.168.1.2%3A20880... - 数据持久化:注册中心节点保存 URL 信息作为临时节点(Ephemeral),当服务宕机时自动删除,实现服务上下线感知。
关键配置:@Service 与 @DubboService
org.apache.dubbo.config.annotation.Service:- Dubbo 2.x 的注解。
- 使用时需显式指定接口或扫描路径。
org.apache.dubbo.config.annotation.DubboService:- Dubbo 3.x 引入的正式版本。
- 推荐使用,它支持
@AliasFor属性别名,与 Spring Boot 3.x 配合更好,支持接口自动推断(如果实现类只有一个接口)。
代码示例对比:
// Dubbo 3.x 推荐
@DubboService(version = "1.0.0")
public class UserServiceImpl implements UserService {}
// Dubbo 2.x 兼容
@Service(version = "1.0.0")
public class UserServiceImpl implements UserService {}
配置属性说明
以下是 @DubboService 中常用的配置项:
| 属性 | 默认值 | 说明 |
|---|---|---|
interface / interfaceName |
自动推断 | 服务接口全类名 |
version |
版本号(用于灰度发布、多版本隔离) | |
group |
服务分组(不同的分组服务隔离) | |
timeout |
默认值 | 方法调用超时时间(ms) |
retries |
2 |
远程调用失败重试次数(注意幂等性) |
loadbalance |
"random" |
负载均衡策略(random, roundrobin, leastactive, consistenthash) |
delay |
-1 |
延迟发布服务(ms),用于等待一些依赖初始化 |
weight |
100 |
服务权重(用于负载均衡) |
常见问题与优化
Q1:发布时端口被占用?
- 解决方案:修改端口号,Dubbo 默认端口
20880,如果被占用,会自动尝试20881、20882等。 - 建议:在配置中明确设置
dubbo.protocol.port=20880。
Q2:服务发布成功但消费者调不通?
- 检查:
- 网络连通性:
telnet 服务端IP 20880。 - 注册中心:消费者是否连接了同一个注册中心?注册中心的 Namespace/Group 是否一致?
- 接口版本:
version是否匹配?消费者请求0.0,服务端发布0.1会失败。 - 防火墙:服务器的 20880 端口是否被防火墙/安全组拦截?
- 网络连通性:
Q3:如何动态发布/取消发布服务?
- 使用 API 方式(参考上面的 API 示例),调用
ServiceConfig.export()和ServiceConfig.unexport()动态控制。
Q4:服务延迟发布(等待数据库连接池等初始化完成后才发布)?
@DubboService(delay = 5000) // 延迟 5 秒发布 // 或者使用 Spring 的 @Lazy 配合 @PostConstruct
| 方面 | 关键点 | 推荐场景 |
|---|---|---|
| 注解方式 | @DubboService + application.yml |
99% 的 Spring Boot 微服务项目 |
| XML 方式 | <dubbo:service> 配置文件 |
传统 Spring 项目 / 遗留系统迁移 |
| API 方式 | ServiceConfig.export() |
非 Spring 环境 / 动态服务管理 |
一句话总结: Dubbo 发布服务的本质是:在服务端启动一个 Netty 监听端口,并将服务描述(接口名、IP、端口、版本)写入注册中心(ZooKeeper/Nacos),供消费者发现和调用。