# dubbo_demo **Repository Path**: tea4go/dubbo_demo ## Basic Information - **Project Name**: dubbo_demo - **Description**: dubbo_demo - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-03 - **Last Updated**: 2026-07-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Dubbo 慢实例问题演示项目 (真实 RPC 版) ## 项目简介 一个**真实 Dubbo RPC** 的可运行项目,演示并解决"服务集群中一个慢实例拖垮整体性能"的问题。 不是模拟——项目在单个 JVM 内启动**内嵌 ZooKeeper** + **4个相互独立的 Dubbo Provider**(各自独立 ApplicationModel、独立端口、独立业务线程池),Consumer 通过 ZooKeeper 发现它们并发起真实的网络 RPC 调用。 ### 业务场景 - 数据库中有大量员工记录(本Demo用120条,10个不同姓名,每人12条) - 按姓名分组,10个线程并发调用 Dubbo 服务 A - 服务 A 有4个实例:A1(慢,3秒/条)、A2/A3/A4(快,200ms/条) - **问题**:RoundRobin 负载均衡下,所有姓名组都会因命中 A1 而被拖慢 ## 架构 ``` 单个 JVM 进程 ┌──────────────────────────────────────────────┐ │ 内嵌 ZooKeeper (curator TestingServer, 随机端口)│ │ ↑注册 ↑注册 ↑订阅发现 │ │ ┌─────────┐ ┌──────────┐ ┌────────────┐ │ │ │Provider │ │Provider │ │ Consumer │ │ │ │A1:20881 │ │A2:20882 │ │(10线程并发) │ │ │ │ (慢3s) │ │A3:20883 │ │ │ │ │ │ │ │A4:20884 │ │ 真实RPC → │ │ │ │ │ │ (快200ms)│ │ │ │ │ └─────────┘ └──────────┘ └────────────┘ │ │ 每个Provider = 独立ApplicationModel(真RPC) │ └──────────────────────────────────────────────┘ ``` **关键区别**:Provider 端的处理(sleep)发生在**Provider 自己的业务线程池**里, Consumer 线程只是发起 RPC 并等待网络响应——这才是真实 Dubbo 的线程模型, "发送线程"和"服务处理线程"真正分离。 ## 项目结构 ``` dubbo_demo/ ├── pom.xml # Maven配置(Dubbo 3.2.12 + ZK curator5) ├── .mvn/jvm.config # JDK17模块开放参数(add-opens) ├── README.md └── src/main/ ├── java/com/demo/ │ ├── Main.java # 主入口:启ZK→启Provider→跑3场景 │ ├── api/ │ │ └── ServiceA.java # Dubbo远程服务接口(Provider/Consumer共享) │ ├── model/ │ │ ├── Record.java # 员工记录(implements Serializable) │ │ └── ProcessResult.java # 调用结果(implements Serializable) │ ├── provider/ │ │ └── ServiceAImpl.java # 服务实现(可配置实例名+处理耗时) │ ├── bootstrap/ │ │ ├── EmbeddedZookeeper.java # 内嵌ZK(curator-test) │ │ ├── ProviderStarter.java # 启动4个独立Provider │ │ └── ConsumerStarter.java # 构建3种负载均衡策略的引用 │ └── demo/ │ ├── DataGenerator.java # 生成测试数据+按姓名分组 │ └── ScenarioRunner.java # 并发调用+统计各Provider命中量 └── resources/ └── logback.xml # 日志配置(压低Dubbo/ZK噪音) ``` ## 环境要求 - JDK 8 ~ 17(Dubbo 3.2.x 兼容范围;本项目在 JDK 17 验证通过) - Maven 3.6+ - **无需**安装外部 ZooKeeper(curator-test 进程内启动真实 ZK) ## 编译运行 ### 方式1:Maven(推荐) ```bash cd D:/MyWork/GitCode/dubbo_demo mvn clean compile exec:java ``` `.mvn/jvm.config` 已配置 JDK17 所需的 `--add-opens`,开箱即用。 ### 方式2:打包成可执行 jar ```bash mvn clean package # JDK 17 运行需手动加 --add-opens(jar运行不读.mvn/jvm.config) java --add-opens java.base/java.lang=ALL-UNNAMED \ --add-opens java.base/java.math=ALL-UNNAMED \ --add-opens java.base/java.util=ALL-UNNAMED \ -jar target/dubbo-slow-instance-demo-1.0-SNAPSHOT.jar # JDK 8 无模块限制,直接运行 java -jar target/dubbo-slow-instance-demo-1.0-SNAPSHOT.jar ``` ### 方式3:IDE 导入为 Maven 项目,运行 `com.demo.Main`。 JDK 17 需在运行配置的 VM options 里加上上面3个 `--add-opens`。 ## 三个演示场景 程序依次运行,每个场景都是"10组并发、组内串行调用",仅负载均衡策略不同: | 场景 | 负载均衡 | timeout | retries | actives | 对应Dubbo配置 | |------|----------|---------|---------|---------|---------------| | 问题 | roundrobin | 10s | 0 | - | 复现问题 | | 方案A | leastactive | 10s | 0 | 1000 | `loadbalance="leastactive"` | | 方案B | leastactive | 600ms | 2 | 1000 | `+timeout=600 retries=2` | > `actives=1000`:leastactive 依赖 ActiveLimitFilter 统计活跃数, > 设一个较大的值只为**开启统计**,并非真正限流。 ## 实测结果(JDK17,A1=3000ms / A2-A4=200ms,120条) ``` ================================================================ 耗时对比汇总 ================================================================ 问题场景 (RoundRobin) : 16823 ms 方案A (LeastActive) : 8047 ms 提速 2.1x 方案B (LeastActive+Failover) : 4849 ms 提速 3.5x ================================================================ 各Provider命中量: 问题场景: A1=30 A2=30 A3=30 A4=30 ← 轮询精确25%,A1拖慢全组 方案A: A1=3 A2=44 A3=32 A4=41 ← A1被活跃数架空,仅初期命中几次 方案B: A1=0 A2=42 A3=39 A4=39 ← 超时Failover,A1彻底不占用结果 ``` > 数值每次运行略有波动(并发时序、初期活跃数统计有关),趋势稳定。 ## 关键代码说明 ### 1. 单JVM启动多个独立Provider(ProviderStarter) ```java // 每个Provider独立ApplicationModel,模拟独立进程,避免ApplicationConfig冲突 ApplicationModel appModel = FrameworkModel.defaultModel().newApplication(); ServiceConfig service = new ServiceConfig<>(appModel.getDefaultModule()); service.setApplication(new ApplicationConfig("provider-" + instanceId)); service.setRegistry(new RegistryConfig(zkAddress)); service.setProtocol(new ProtocolConfig("dubbo", port)); // 不同端口 service.setInterface(ServiceA.class); service.setRef(new ServiceAImpl(instanceId, processMs)); // A1传3000, 其余200 service.export(); ``` ### 2. 三种负载均衡引用(ConsumerStarter) ```java ReferenceConfig ref = new ReferenceConfig<>(appModel.getDefaultModule()); ref.setInterface(ServiceA.class); ref.setLoadbalance(loadbalance); // roundrobin / leastactive ref.setTimeout(timeoutMs); ref.setRetries(retries); ref.setScope("remote"); // 强制走网络RPC,不被injvm本地短路 ref.setActives(actives); // >0 开启活跃数统计(leastactive依赖) ServiceA proxy = ref.get(); ``` ## 生产环境对应配置 真实 Dubbo 项目里,无需上面这套 Bootstrap 代码,直接注解即可: ```java // 方案A:LeastActive负载均衡 @DubboReference(loadbalance = "leastactive") private ServiceA serviceA; // 方案B:LeastActive + 超时 + Failover @DubboReference( loadbalance = "leastactive", timeout = 5000, // 超时(生产按实际RT设) retries = 2, // 超时后重试2次 cluster = "failover" // 失败转移到其他实例(默认) ) private ServiceA serviceA; ``` ## 参数调整 - `Main.SLOW_MS` / `Main.FAST_MS` — A1及其它实例的处理耗时 - `DataGenerator.TOTAL_RECORDS` — 记录总数(默认120,生产可改 1_000_000) - `ConsumerStarter` 各方法里的 timeout/retries/actives ## 总结 1. **根本解**:排查并修复 A1 慢的根因(代码bug、资源竞争、慢SQL、GC等) 2. **即时解**:Dubbo Admin 把 A1 权重设为 0,临时摘除流量 3. **架构解**:`leastactive` 负载均衡 + 超时 Failover,防御类似问题 --- © 2026 Dubbo 慢实例问题演示项目