Dubbo 生产部署
📖 前置阅读:本文是 Dubbo 系列的终篇,假设读者已经掌握前五篇的全部内容(核心架构、SpringBoot 集成、集群容错与负载均衡、注册中心、Dubbo 3.x 新特性)。
一、⚡ 问题切入:单机开发的配置能上生产吗?
前五篇的页面配置——超时 1 秒、重试 2 次、单注册中心、无限流无监控——只能用来学习。生产环境:
| 单点/隐患 | 后果 |
|---|---|
| 一个 Nacos 实例 | Nacos 挂了 → 新 Consumer 启动不了 → 新 Provider 无法注册 |
| 没设并发限制 | 一个 Provider 被大量请求打爆 → 线程池满 → 所有请求排队或失败 |
| 超时设太短 | Provider 还在处理,Consumer 已断开 → 重复调用 |
| 没有监控 | Provider 变慢了、Success Rate 下降了——完全不知道 |
| 无优雅上下线 | 重启 Provider → 正在处理的请求全部失败 |
生产最低配:Nacos 集群(至少 3 台) + Provider 并发限制 + Consumer 合理超时 + Dubbo Admin 监控 + 优雅上下线。
二、高可用架构
2.1 Nacos 集群部署
# docker-compose-nacos-cluster.yml
version: '3.8'
services:
nacos1:
image: nacos/nacos-server:v2.3.0
container_name: nacos1
environment:
- MODE=cluster
- NACOS_SERVERS=nacos1:8848 nacos2:8848 nacos3:8848
- NACOS_APPLICATION_PORT=8848
- SPRING_DATASOURCE_PLATFORM=mysql
- MYSQL_SERVICE_HOST=mysql
- MYSQL_SERVICE_DB_NAME=nacos
- MYSQL_SERVICE_USER=nacos
- MYSQL_SERVICE_PASSWORD=nacos123
ports:
- "8848:8848"
- "9848:9848"
# nacos2、nacos3 类似——改端口映射
mysql:
image: mysql:8.0
container_name: nacos-mysql
environment:
- MYSQL_ROOT_PASSWORD=root123
- MYSQL_DATABASE=nacos
- MYSQL_USER=nacos
- MYSQL_PASSWORD=nacos123
volumes:
- ./mysql/data:/var/lib/mysql
Nacos 集群需要 MySQL(生产不能用内置 Derby 数据库——数据不共享)。
2.2 Dubbo 的多注册中心高可用
dubbo:
registries:
primary:
address: nacos://nacos1:8848,nacos2:8848,nacos3:8848 # 集群地址
default: true
同一个注册中心的多个节点用逗号分隔——Consumer 连接任意一台可用即可。
2.3 Provider 多实例部署
order-provider(3 个实例):
Instance-1: 192.168.1.10:20880 (weight=200, 4C8G)
Instance-2: 192.168.1.11:20880 (weight=200, 4C8G)
Instance-3: 192.168.1.12:20880 (weight=100, 2C4G) ← 性能差的机器权重低
Consumer 负载均衡:
40% → Instance-1
40% → Instance-2
20% → Instance-3
三、调优
3.1 Provider 端调优
dubbo:
provider:
# ===== 线程模型 =====
threads: 200 # 业务线程池大小(默认 200)
threadpool: fixed # fixed / cached / limited / eager
queues: 0 # 等待队列大小——0 表示队列满后直接拒绝(有界队列)
# ===== 并发控制 =====
actives: 500 # 最大并发调用数——超过则等待或拒绝
executes: 1000 # 最大并发执行数
# ===== 超时 =====
timeout: 3000 # Provider 端超时(ms)——Consumer 端可覆盖
# ===== 连接控制 =====
accepts: 500 # 最大连接数
payload: 8388608 # 最大请求体大小(8MB)
Provider 线程模型:
| 线程池 | Provider 中的角色 | 默认值 |
|---|---|---|
| Boss 线程 | 接收 TCP 连接 | 1(Netty 默认) |
| I/O Worker 线程 | 处理网络 I/O——序列化/反序列化 | CPU 核数 + 1 |
| 业务线程池 | 执行 Provider 的业务逻辑(你的代码) | 200 |
Consumer 请求到达 Provider:
Netty I/O Worker → 反序列化 → 提交到业务线程池 → 执行业务方法 → 返回
↑ ↓
非阻塞——I/O 线程立即 业务线程执行结束后通知 I/O 线程发响应
回去接收下一个请求
# 调整线程数
dubbo:
provider:
threads: 400 # 业务线程——CPU 密集型:CPU 核数 × 2
# I/O 密集型:CPU 核数 × 10~20
iothreads: 8 # I/O Worker 线程——不要超过 CPU 核数 × 2
| 参数 | 调大 | 调小 | 依据 |
|---|---|---|---|
threads | RPC 调下游服务多(I/O 等待) | 纯计算逻辑(CPU 密集) | 线程数 = CPU 核数 × (1 + 等待时间/计算时间) |
iothreads | 请求量大、序列化开销大 | CPU 核数少 | 不超过 CPU 核数 × 2——多了上下文切换 |
actives | Provider 处理能力强 | 保护 Provider 不被打爆 | 压测得到的最大并发数 × 0.8 |
timeout | Provider 处理慢 | Provider 处理快 | 99 分位延迟 × 1.5 |
3.2 Consumer 端调优
dubbo:
consumer:
timeout: 3000 # 调用超时(ms)
retries: 0 # 重试次数——非幂等写操作必须为 0
loadbalance: p2c # 负载均衡——Dubbo 3.2+ 推荐
check: true # 启动时检查 Provider 是否可用
connections: 1 # 每个 Provider 的连接数——默认 1(共享连接)
timeout 的设置参考:
| 调用类型 | timeout 建议 | 原因 |
|---|---|---|
| 简单查询(单表、有索引) | 1000ms | 快——超了就重试 |
| 复杂查询(多表关联、大数据量) | 5000ms | 慢——给足时间 |
| 写操作(创建、更新) | 3000ms | 中等——但 retries=0 |
| 第三方 API 调用(短信、支付) | 10000ms | 不可控——给足时间 |
// 为不同方法设置不同的超时——细粒度控制
@DubboReference(
timeout = 3000,
retries = 0, // 写操作不重试
parameters = {
"getOrderById.timeout", "1000", // 查询超时 1s
"createOrder.timeout", "5000" // 创建超时 5s
}
)
private OrderService orderService;
3.3 JVM 调优
# Provider JVM 参数
java -jar order-provider.jar \
-Xms2g -Xmx2g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=50 \
-XX:InitiatingHeapOccupancyPercent=40 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/dubbo/heapdump
| 参数 | 含义 | 建议值 |
|---|---|---|
-Xms2g -Xmx2g | 堆内存 | 至少 2G——Dubbo 用堆存请求和响应对象 |
-XX:+UseG1GC | G1 垃圾回收器——低延迟 | 必选 |
-XX:MaxGCPauseMillis=50 | 目标 GC 停顿 < 50ms | 更小的值会导致更频繁的 GC |
-XX:InitiatingHeapOccupancyPercent=40 | 堆使用 40% 开始并发标记 | 默认 45——给 GC 更多提前量 |
3.4 Netty 层调优
dubbo:
provider:
# Netty I/O 调优(通过 -D 参数传递)
# -Ddubbo.protocol.payload=8388608 # 8MB 请求上限
# -Ddubbo.protocol.buffer=16384 # 网络缓冲区大小
# -Ddubbo.protocol.serialization=hessian2
四、Dubbo Admin —— 可视化监控
4.1 核心页面
访问 http://localhost:8081
| Tab | 看什么 | 为什么要看 |
|---|---|---|
| 服务列表 | 所有已注册的 Provider 和 Consumer、接口列表、实例数 | 确认服务是否都在线 |
| 服务关系 | 谁调了谁——调用拓扑图 | 找出不合理的依赖(A 调了不该调的 C) |
| 流量管理 | 动态路由规则、权重调整、条件路由 | 无需重启调整流量分配 |
| 配置管理 | Provider/Consumer 的运行时参数 | 调整超时、重试、负载均衡——实时生效 |
| 监控 | 调用次数、平均耗时、成功率 | 发现慢调用和异常 |
4.2 必须盯住的三个指标
| 指标 | Dubbo Admin 看哪里 | 告警阈值 |
|---|---|---|
| Success Rate | 监控 → 成功率 | < 99.9% |
| 平均耗时 | 监控 → 响应时间 | P99 持续增长 |
| Provider 在线数 | 服务列表 → 实例数 | 少于预期实例数 |
| 并发调用数 | 服务详情 → 活跃数 | 接近 actives 限制 |
4.3 动态配置——不重启改参数
Dubbo Admin 支持动态下发配置——修改后实时生效:
在 Dubbo Admin → 配置管理 → 新增配置:
# 针对特定服务的配置覆盖
configVersion: v1.0
enabled: true
configs:
- side: provider
key: org.example.api.OrderService
parameters:
timeout: 5000 # 调大超时
actives: 300 # 调整并发限制
loadbalance: leastactive
动态配置 vs yml 配置的优先级:动态配置 > yml 配置。如果在 Dubbo Admin 中改了 timeout: 5000,会覆盖 yml 中的值。
五、优雅上下线
5.1 优雅下线 —— 重启时不丢请求
# Provider 配置优雅下线
dubbo:
provider:
# 服务关闭时等待请求处理完的时间(ms)
shutdown-timeout: 10000 # 10s 内处理完所有已接收的请求再关闭
优雅下线流程:
1. 收到关闭信号(kill PID / K8s SIGTERM)
2. Provider 从 Registry 注销服务——Consumer 不再收到这个实例的地址
3. 等待 10s——处理完已接收但未完成的请求
4. 10s 到了——强制关闭
# K8s 中配合 preStop hook
# 先注销、再等、再关进程
spec:
containers:
- name: order-provider
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- |
# 1. 调用 Dubbo 的离线命令——从注册中心注销
curl -X POST http://localhost:22222/offline
# 2. 等待 10s——处理完所有正在进行的请求
sleep 10
5.2 优雅上线 —— 预热
dubbo:
provider:
warmup: 120000 # 启动后 120s 内权重从 0 慢慢增加到正常值
新启动的 Provider——JIT 还没编译、缓存还是冷的——性能比老实例差。开启预热后,Dubbo 在预热期内给新实例分配较少流量:
启动第 0s: weight = 0 → 没流量
启动第 30s: weight = 25% → 25% 流量
启动第 60s: weight = 50% → 50% 流量
启动第 90s: weight = 75% → 75% 流量
启动第 120s: weight = 100 → 100% 流量
六、常见生产故障
| 故障 | 现象 | 排查 |
|---|---|---|
| 线程池满 | Provider 日志 RejectedExecutionException | ① threads 是否设太小 ② Provider 处理逻辑是否有慢调用 ③ 增加实例或调大线程池 |
| Consumer 超时雪崩 | 一个 Provider 变慢 → Consumer 线程全部阻塞等待 → 整个 Consumer 不可用 | ① 设合理的 timeout ② 用 CompletableFuture 异步调用——不阻塞 Consumer 主线程 |
| 序列化不兼容 | Hessian2Exception: expected string but got int | Provider 和 Consumer 的 API 版本不一致——字段类型变了。确保 API 模块版本一致 |
| Provider 全部离线 | Consumer 报 No provider available——Registry 列表为空 | ① Nacos 是否正常 ② Provider 是否因 OOM/GC 停顿导致心跳丢失 ③ 检查 Nacos 的网络连接 |
| 内存泄漏 | Provider Full GC 越来越频繁,最终 OOM | ① 检查是否在 Provider 方法中把请求对象存到了 static 集合 ② 检查 actives 是否设太大 |
| rebalance 风暴 | Dubbo 3.x 元数据刷新过于频繁 | 调大 dubbo.metadata-report.retry-times 和 dubbo.metadata-report.cycle-report |
七、上线前 10 项检查清单
| # | 检查项 | 配置/命令 |
|---|---|---|
| 1 | Nacos 集群部署 ≥ 3 台 | docker-compose-nacos-cluster.yml 中 3 个 nacos 实例 + MySQL |
| 2 | Consumer 写操作关重试 | @DubboReference(retries = 0) 或 cluster = "failfast" |
| 3 | Provider 设并发限制 | dubbo.provider.actives——压测最大并发 × 0.8 |
| 4 | Provider 设线程池 | dubbo.provider.threads——根据 I/O 密集度调整 |
| 5 | Consumer 超时合理 | 查询 1000ms、写操作 3000ms、外部 API 10000ms |
| 6 | 优雅下线 | dubbo.provider.shutdown-timeout=10000 + K8s preStop hook |
| 7 | 预热开启 | dubbo.provider.warmup=120000——2 分钟预热 |
| 8 | Dubbo Admin 部署 | 先最小化部署——至少盯住服务列表和成功率 |
| 9 | Provider JVM 堆 ≥ 2G + G1GC | -Xms2g -Xmx2g -XX:+UseG1GC |
| 10 | check=true(默认) | Provider 没启动时 Consumer 启动就报错——不要改成 false 掩盖问题 |
八、Dubbo vs gRPC vs Spring Cloud 最终选型
六篇 Dubbo 学完了。加上之前的三个 MQ 系列——现在选型时:
| 场景 | 选谁 | 理由 |
|---|---|---|
| Java 微服务内部 RPC——高吞吐低延迟 | Dubbo | dubbo/triple 协议 + 内置服务治理——性能和服务治理一把抓 |
| 需要消息重放、流处理 | Kafka | 分布式提交日志——核心就是持久化和重放 |
| 需要事务消息、延迟消息 | RocketMQ | 半消息 + 18 级延迟 + 原生事务——MQ 中事务支持最好 |
| 路由灵活、小团队 | RabbitMQ | Exchange + Binding 灵活度最高,单 Docker 即可 |
| 跨语言 RPC——Go/Node.js/Python 调 Java | gRPC | Protobuf + 多语言 SDK——跨语言是核心优势 |
| 对外 API Gateway + 内部调用 | Dubbo 3 Triple | Triple 协议 HTTP/2 + JSON——浏览器可调,内部 Protobuf 性能高 |
| 全套 Spring 生态 | Spring Cloud | 全家桶——Gateway、Config、Sleuth 全集成 |
| 云原生 / Istio / K8s | Dubbo 3.x Mesh 模式 | 服务治理下沉到 Sidecar——Dubbo 只做 RPC |
🎯 总结
Dubbo 的生产部署核心在三点:
高可用架构:Nacos 集群 ≥ 3 台(数据存在 MySQL),Provider 多实例 + 权重调节。Registry 本地缓存兜底——Nacos 全部宕机也不影响已有连接的调用。
调优关键是并发和超时:Provider
actives保护自己不被打爆,Consumertimeout防止雪崩。线程数根据 I/O 密集度调整——threads = CPU 核数 × (1 + 等待时间/计算时间)。监控盯着三个指标:Dubbo Admin 的 Success Rate、平均耗时、Provider 在线数。支持动态配置——不重启改超时和负载均衡策略。
📖 系列总览
Dubbo 六篇系列到此结束:
| # | 篇 | 核心收获 |
|---|---|---|
| 1 | 核心架构与 RPC 模型 | RPC 本质、Dubbo 三角架构、与 REST 的差异、Registry 是"黄页"不是"中转站" |
| 2 | SpringBoot 全操作指南 | @DubboService / @DubboReference 两个注解替代全部 XML、dubbo/triple 协议配置、序列化选择 |
| 3 | 集群容错与负载均衡 | 六种容错策略、七种负载均衡、异步调用 CompletableFuture、版本分组灰度发布 |
| 4 | 注册中心:Nacos 与 Zookeeper | 服务发现全链路、Nacos 同时做注册中心和配置中心、多注册中心双活 |
| 5 | Dubbo 3.x 新特性 | Triple 协议(HTTP/2 + Protobuf)、应用级服务发现、curl 直接调 RPC |
| 6 | 生产环境部署与调优 | Nacos 集群、Provider/Consumer/JVM 三层调优、Dubbo Admin 监控、10 项检查清单 |
建议从 1 到 6 顺序阅读,每篇以前一篇为前提。学完这六篇,从 RPC 概念到生产部署的全链路都覆盖了。
四个系列的完整技术栈:
- 消息队列:RabbitMQ(灵活路由)+ RocketMQ(事务消息)+ Kafka(流处理与重放)
- RPC 框架:Dubbo(Java 微服务内部通信 + 服务治理)