Appearance
Dubbo 3.x + Zookeeper 分布式服务架构实战:从 API 定义到 RPC 调用的完整实践
作者: 必码 | bima.cc
前言
在当今微服务架构盛行的时代,分布式服务框架已经成为企业级应用开发的基础设施。Apache Dubbo 作为国内应用最广泛的 RPC 框架之一,从 2011 年阿里巴巴开源至今,已经走过了十余年的发展历程。Dubbo 3.x 作为该框架的最新大版本,不仅继承了前代版本高性能、易用性的优良传统,更在服务发现、流量治理、云原生适配等方面进行了全面升级。
本文基于实际生产项目 smart-scaffold-dubbo(智能中间件集成验证脚手架 Dubbo 版),从零开始,系统性地讲解 Dubbo 3.x + Zookeeper 分布式服务架构的完整实践。该项目深度融合了 AI 能力与主流中间件集成方案,集成了 Elasticsearch、Kafka、MongoDB、MyBatis、RabbitMQ、Redis、RocketMQ 等七大中间件,同时支持 Ollama 本地模型与 OpenAI API 调用,是一个极具参考价值的分布式服务架构实践范本。
本文将从架构设计、模块划分、API 定义、服务注册发现、RPC 调用、容器化部署等多个维度进行深度解析,力求为读者提供一份既有理论深度又有实战指导价值的技术文档。无论你是初次接触 Dubbo 的开发者,还是希望深入了解 Dubbo 3.x 新特性的资深架构师,都能从中获得有价值的参考。
第一章 Dubbo 3.x 架构概述
1.1 Apache Dubbo 3.2.0 新特性全景解析
Apache Dubbo 3.2.0 是 Dubbo 3.x 系列的一个重要里程碑版本,它在保持向后兼容的前提下,引入了大量面向云原生时代的新特性。在本项目的实际应用中,我们深入体验了这些特性带来的架构优势。
1.1.1 核心架构升级
Dubbo 3.2.0 最核心的架构升级在于应用级服务发现的全面落地。在 Dubbo 2.x 时代,服务注册是以接口为粒度进行的,这意味着一个应用暴露 100 个接口,就会在注册中心注册 100 条服务信息。当集群规模扩大时,注册中心的数据量会急剧膨胀,给注册中心带来巨大的存储和推送压力。
Dubbo 3.2.0 引入了应用级服务发现机制,将注册粒度从接口级别提升到应用级别。一个应用无论暴露多少个接口,在注册中心只注册一条应用信息。接口级别的元数据则通过独立的元数据中心进行管理。这种设计大幅降低了注册中心的压力,使得 Dubbo 能够更好地支撑大规模集群场景。
在我们的 smart-scaffold-dubbo 项目中,Provider 端暴露了 12 个 Dubbo 服务接口,但在 Zookeeper 注册中心中,只注册了一个应用实例信息(smart-scaffold-dubbo-provider),这就是应用级服务发现带来的直接收益。
1.1.2 新一代 RPC 协议
Dubbo 3.2.0 对 RPC 协议进行了全面升级,支持 Triple 协议(基于 gRPC)。Triple 协议是 Dubbo 3.x 提出的新一代 RPC 协议,它基于 HTTP/2 构建,具备以下优势:
- 跨语言支持: 基于 gRPC 标准协议,天然支持多语言互通
- 流式通信: 支持 Unary、Server Streaming、Client Streaming、Bidirectional Streaming 四种通信模式
- 穿透性强: 基于 HTTP/2,能够更好地穿透网关、代理等网络中间件
- 易调试: 使用标准的 HTTP/2 协议,可以借助通用工具进行调试
在本项目中,IChatClientFactory 接口中的 chatStream 方法返回 Flux<String> 类型,用于实现 SSE 流式聊天功能。这种流式通信模式正是 Triple 协议所擅长的场景。
1.1.3 统一流量治理
Dubbo 3.2.0 内置了统一的流量治理能力,包括路由规则、负载均衡、服务降级、限流熔断等功能。这些能力不再需要依赖外部组件(如 Sentinel),而是通过 Dubbo 自身的能力即可实现。同时,Dubbo 3.x 还提供了与 Istio 等 Service Mesh 技术的集成方案,为云原生迁移提供了平滑路径。
1.1.4 Spring Boot 3.x 全面适配
本项目使用的是 Spring Boot 3.5.12,Dubbo 3.2.0 对 Spring Boot 3.x 提供了全面适配。通过 dubbo-spring-boot-starter 依赖,开发者可以使用 @EnableDubbo 注解一键启用 Dubbo 功能,同时享受 Spring Boot 自动配置带来的便利。Java 17 的支持也确保了项目能够使用最新的语言特性。
1.1.5 元数据与服务治理增强
Dubbo 3.2.0 增强了元数据管理能力,支持将服务的接口定义、方法签名、参数类型等元数据信息存储在独立的元数据中心。在本项目的配置中,我们通过 dubbo:metadata-report 标签配置了元数据中心,使得 Consumer 端在引用服务时能够获取到完整的接口元数据信息。
1.2 RPC vs HTTP 通信模型深度对比
在微服务架构中,服务间的通信方式主要有两种:RPC(Remote Procedure Call)和 HTTP(包括 RESTful API)。理解两者的差异和适用场景,是进行架构选型的关键。
1.2.1 通信协议层面
RPC 通信模型:
RPC 采用的是二进制协议进行通信,Dubbo 默认使用 Dubbo 协议(基于 TCP 长连接)。二进制协议的优势在于:
- 序列化效率高: 使用 Hessian2、Kryo、Protobuf 等高效序列化框架,相比 JSON 格式,序列化后的数据体积更小、速度更快
- 网络开销低: 二进制协议的头部信息更紧凑,不需要像 HTTP 那样携带大量的 Header 信息
- 长连接复用: TCP 长连接避免了频繁的连接建立和断开开销
java
// Dubbo RPC 调用示例 —— 二进制协议,长连接
@DubboReference
private IRedisService redisService;
// 调用远程服务,如同调用本地方法
String result = redisService.setKey("user:1001", "张三");HTTP 通信模型:
HTTP 采用的是文本协议(HTTP/1.1)或二进制协议(HTTP/2),通常使用 JSON 格式进行数据交换。HTTP 的优势在于:
- 通用性强: 几乎所有的网络设备、代理、网关都支持 HTTP 协议
- 跨语言友好: JSON 格式被所有主流语言支持
- 调试方便: 可以使用浏览器、Postman、curl 等工具直接调试
java
// HTTP REST 调用示例 —— 文本协议,短连接
@RestController
@RequestMapping("/api/redis")
public class RedisController {
@DubboReference
private IRedisService redisService;
@PostMapping("/key")
public ApiResult<?> setKey(@RequestParam String key, @RequestParam String value) {
return ApiResult.success(redisService.setKey(key, value));
}
}1.2.2 性能对比分析
在相同的硬件条件下,RPC 和 HTTP 的性能差异主要体现在以下几个方面:
| 对比维度 | Dubbo RPC | HTTP REST |
|---|---|---|
| 序列化方式 | Hessian2/Kryo(二进制) | JSON(文本) |
| 通信协议 | TCP 长连接 | HTTP/1.1 短连接 |
| 序列化性能 | 高(微秒级) | 中(毫秒级) |
| 网络开销 | 低(紧凑二进制帧) | 高(HTTP Header + Body) |
| 连接复用 | 是(长连接池) | 否(每次请求新建连接) |
| 跨语言支持 | 需要对应语言的 SDK | 天然支持 |
| 调试便利性 | 需要专用工具 | 浏览器/curl/Postman |
| 网关穿透 | 需要特殊配置 | 天然支持 |
1.2.3 本项目的架构选择
在 smart-scaffold-dubbo 项目中,我们采用了 RPC + HTTP 混合架构:
- 服务间通信(Provider <-> Consumer): 使用 Dubbo RPC,利用其高性能、低延迟的特性
- 外部通信(浏览器/客户端 <-> Consumer): 使用 HTTP REST API,利用其通用性、易调试的特性
这种混合架构充分发挥了两种通信模型的优势。Consumer 端作为 BFF(Backend for Frontend)层,对外暴露 REST API,对内通过 RPC 调用 Provider 端的服务。这种架构模式在企业级应用中非常常见。
浏览器/客户端
|
| HTTP REST API
v
+---------------------+
| Consumer (BFF) | 端口: 8080
| - Controller 层 |
| - Filter 层 |
| - Thymeleaf 模板 |
+---------+-----------+
|
| Dubbo RPC (二进制协议)
v
+---------------------+
| Provider (服务层) | 端口: 8081
| - 中间件服务 |
| - DAO 层 |
| - AI 服务 |
+---------------------+1.2.4 何时选择 RPC,何时选择 HTTP
在实际项目中进行技术选型时,可以参考以下原则:
适合使用 RPC 的场景:
- 内部微服务之间的频繁调用
- 对性能和延迟有较高要求的场景
- 服务接口相对稳定,不需要频繁变更
- 调用链路较深,需要传递大量上下文信息
适合使用 HTTP 的场景:
- 对外暴露的 API 接口
- 需要跨语言调用的场景
- 接口变更较频繁的场景
- 需要通过网关、代理等中间件的场景
- 前端直接调用的场景
1.3 服务提供者/消费者/注册中心三层架构
Dubbo 的核心架构由三个基本角色组成:服务提供者(Provider)、服务消费者(Consumer)和注册中心(Registry)。这三个角色构成了 Dubbo 分布式服务架构的基石。
1.3.1 架构角色详解
服务提供者(Provider):
服务提供者是服务的实现方,负责暴露服务接口供其他服务调用。在 smart-scaffold-dubbo 项目中,smart-scaffold-provider 模块就是服务提供者。它实现了 12 个 Dubbo 服务接口,包括 Redis、Elasticsearch、Kafka、MongoDB、RabbitMQ、RocketMQ 等中间件服务,以及 AI 聊天、模型管理、嵌入配置、用户管理、部门管理、写作风格等服务。
Provider 的核心职责包括:
- 实现服务接口的业务逻辑
- 将服务注册到注册中心
- 处理来自 Consumer 的 RPC 调用请求
- 管理服务的生命周期(启动、运行、关闭)
java
// 服务提供者示例:Redis 服务的实现
@DubboService
@Service("RedisService")
public class RedisService implements IRedisService {
private final StringRedisTemplate redisTemplate;
@Autowired
public RedisService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public String setKey(String key, String value) {
redisTemplate.opsForValue().set(key, value);
return "Set key: " + key + " with value: " + value;
}
}服务消费者(Consumer):
服务消费者是服务的调用方,负责从注册中心订阅需要的服务,并通过 RPC 协议调用远程服务。在 smart-scaffold-dubbo 项目中,smart-scaffold-consumer 模块就是服务消费者。它引用了 Provider 暴露的 12 个服务,并通过 Controller 层将 RPC 服务封装为 REST API 供前端调用。
Consumer 的核心职责包括:
- 从注册中心订阅所需的服务
- 通过 RPC 协议调用远程服务
- 管理本地服务引用的缓存
- 处理服务调用失败的重试和降级
java
// 服务消费者示例:通过 @DubboReference 引用远程服务
@RestController
@RequestMapping("/api/redis")
public class RedisController {
@DubboReference
private IRedisService redisService;
@PostMapping("/key")
public ApiResult<?> setKey(@RequestParam String key, @RequestParam String value) {
return ApiResult.success(redisService.setKey(key, value));
}
}注册中心(Registry):
注册中心是整个架构的枢纽,负责管理服务的注册与发现。在 smart-scaffold-dubbo 项目中,我们使用 Zookeeper 作为注册中心。注册中心的核心职责包括:
- 接收 Provider 的服务注册请求
- 维护服务地址列表
- 向 Consumer 推送服务地址变更通知
- 监控 Provider 的健康状态
1.3.2 服务调用完整流程
一个完整的 Dubbo 服务调用流程如下:
步骤 1: Provider 启动
Provider --注册--> Zookeeper
(将服务信息写入 /dubbo/config/... 和 /dubbo/metadata/... 节点)
步骤 2: Consumer 启动
Consumer --订阅--> Zookeeper
(从 /dubbo/... 节点读取服务提供者列表)
步骤 3: Consumer 发起调用
Consumer --RPC 调用--> Provider
(通过负载均衡选择一个 Provider 实例,建立 TCP 连接,发送二进制请求)
步骤 4: Provider 处理请求
Provider --执行业务逻辑--> 返回结果
(Provider 接收请求,执行业务方法,将结果序列化后返回)
步骤 5: Consumer 接收响应
Consumer --反序列化--> 获取结果
(Consumer 接收二进制响应,反序列化为 Java 对象)1.3.3 高可用机制
Dubbo 的三层架构内置了多种高可用机制:
- 注册中心高可用: Zookeeper 本身支持集群部署,当某个节点宕机时,其他节点可以继续提供服务
- 服务提供者高可用: 支持多实例部署,Consumer 通过负载均衡将请求分发到不同的 Provider 实例
- 容错机制: 当某个 Provider 实例不可用时,Dubbo 会自动将其从可用列表中移除,并将请求转发到其他可用实例
- 优雅上下线: Provider 在关闭时会主动从注册中心注销,避免 Consumer 将请求发送到已关闭的实例
1.4 与 Spring Cloud 的定位差异
Dubbo 和 Spring Cloud 是当前 Java 微服务领域最主流的两个技术栈,它们在定位和设计理念上存在显著差异。
1.4.1 设计理念对比
Dubbo 的设计理念:
Dubbo 的核心定位是 高性能 RPC 框架,它专注于解决服务间的远程调用问题。Dubbo 的设计哲学是"面向接口编程",开发者只需要定义服务接口,Dubbo 会自动处理服务的注册、发现、负载均衡、容错等底层细节。
Dubbo 核心:RPC 调用
+-- 服务注册与发现(Zookeeper/Nacos)
+-- 负载均衡(Random/RoundRobin/ConsistentHash)
+-- 容错机制(Failover/Failfast/Failsafe)
+-- 序列化(Hessian2/Kryo/Protobuf)
+-- 路由规则(条件/脚本/标签)
+-- 流量治理(限流/降级/熔断)Spring Cloud 的设计理念:
Spring Cloud 的核心定位是 微服务全家桶,它提供了一整套微服务治理方案,涵盖了从服务注册发现、配置管理、负载均衡、熔断器、网关到分布式链路追踪等各个方面。Spring Cloud 的设计哲学是"约定优于配置",通过自动配置和约定减少开发者的工作量。
Spring Cloud 核心:微服务全家桶
+-- 服务注册与发现(Eureka/Consul/Nacos)
+-- 配置中心(Spring Cloud Config/Nacos)
+-- 负载均衡(Spring Cloud LoadBalancer)
+-- 熔断器(Resilience4j/Sentinel)
+-- 网关(Spring Cloud Gateway)
+-- 分布式链路追踪(Sleuth/Micrometer Tracing)
+-- 分布式事务(Seata)
+-- 消息总线(Spring Cloud Bus)1.4.2 通信模型对比
| 对比维度 | Dubbo | Spring Cloud |
|---|---|---|
| 通信协议 | 二进制 RPC(Dubbo 协议) | HTTP REST(基于 Spring MVC) |
| 序列化 | Hessian2/Kryo/Protobuf | JSON/Jackson |
| 性能 | 高(二进制协议 + 长连接) | 中(HTTP + 短连接) |
| 跨语言 | 支持(需要 SDK) | 天然支持(HTTP 协议) |
| 学习曲线 | 中等 | 较低 |
| 生态丰富度 | RPC 相关生态丰富 | 微服务全家桶生态丰富 |
1.4.3 适用场景对比
适合选择 Dubbo 的场景:
- 内部微服务之间调用频繁,对性能要求高
- 团队以 Java 技术栈为主,跨语言需求较少
- 需要精细化的流量治理能力
- 已有 Dubbo 基础设施和运维经验
- 服务接口相对稳定,变更频率较低
适合选择 Spring Cloud 的场景:
- 需要完整的微服务治理方案
- 团队技术栈多元化,有跨语言需求
- 对 HTTP 协议有强依赖(如需要通过 API 网关)
- 希望利用 Spring 生态的丰富组件
- 项目处于快速迭代阶段,接口变更频繁
1.4.4 本项目的选型考量
在 smart-scaffold-dubbo 项目中,我们选择 Dubbo 作为 RPC 框架,主要基于以下考量:
- 性能需求: 项目集成了七大中间件,Consumer 需要频繁调用 Provider 的服务,RPC 的高性能特性能够有效降低通信延迟
- 接口稳定性: 中间件服务的接口定义相对稳定,不会频繁变更,适合使用 RPC
- 团队技术栈: 项目完全基于 Java 技术栈,不需要跨语言支持
- 精细治理: Dubbo 提供的服务版本管理、超时控制、重试策略等精细化治理能力,对于中间件集成场景非常有用
同时,我们也注意到 Spring Cloud 版本的脚手架(smart-scaffold-springcloud)也在同一代码库中维护,为不同技术栈偏好的团队提供了选择。
第二章 项目模块结构设计
2.1 三模块架构总览
smart-scaffold-dubbo 项目采用了经典的三模块架构设计,这种设计在 Dubbo 分布式服务项目中具有广泛的代表性。三个模块各司其职,通过 Maven 依赖关系进行组织,形成了一个清晰、可维护的项目结构。
smart-scaffold-dubbo/ # 父工程(POM 聚合)
+-- pom.xml # 父 POM,管理依赖版本和插件
+-- smart-scaffold-api/ # API 接口定义模块
| +-- pom.xml
+-- smart-scaffold-provider/ # 服务提供者模块
| +-- pom.xml
| +-- Dockerfile
| +-- entrypoint.sh
+-- smart-scaffold-consumer/ # 服务消费者模块
| +-- pom.xml
| +-- Dockerfile
| +-- entrypoint.sh
+-- smart-scaffold.sql # 数据库初始化脚本这种三模块架构的设计理念是 "契约优先"(Contract-First):先定义服务契约(API 接口),再分别实现服务提供者和服务消费者。API 模块作为契约的载体,被 Provider 和 Consumer 共同依赖,确保了服务接口的一致性。
2.1.1 父工程 POM 配置
父工程的 POM 文件是整个项目的"总指挥",它定义了项目的坐标、模块列表、依赖管理和构建配置。
xml
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>cc.bima.scaffold</groupId>
<artifactId>smart-scaffold-dubbo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>smart-scaffold-dubbo</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.12</version>
</parent>
<modules>
<module>smart-scaffold-api</module>
<module>smart-scaffold-provider</module>
<module>smart-scaffold-consumer</module>
</modules>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>关键配置解析:
packaging=pom: 表示这是一个聚合工程,本身不产生构建产物,仅用于管理子模块parent继承 Spring Boot Starter Parent: 统一管理 Spring Boot 及其依赖的版本,避免版本冲突modules定义子模块: Maven 在构建时会按照模块列表的顺序进行编译java.version=17: 使用 Java 17 作为编译和运行的目标版本- 全局 Lombok 依赖: 所有子模块都可以使用 Lombok 注解(
@Data、@Getter、@Setter等)
2.2 smart-scaffold-api:API 接口定义层
API 模块是整个项目的"契约层",它定义了所有 Dubbo 服务接口和相关的数据传输对象。这个模块不包含任何业务实现,只包含接口定义和数据结构。
2.2.1 模块定位与职责
API 模块的核心职责包括:
- 定义 Dubbo 服务接口: 所有跨服务调用的接口都在此定义
- 定义数据传输对象(DTO): 服务间传递的数据结构
- 定义实体类(Entity): 与数据库表对应的数据结构
- 定义持久化对象(PO): 数据库操作的中间对象
- 定义枚举类(Enum): 系统中使用的枚举常量
- 定义常量类(Constants): 系统中使用的常量值
2.2.2 包结构设计
smart-scaffold-api/
+-- src/main/java/cc/bima/scaffold/api/
+-- face/ # Dubbo 服务接口定义(12个接口)
| +-- IChatClientFactory.java
| +-- IModelService.java
| +-- IEmbeddingConfig.java
| +-- IElasticsearchService.java
| +-- IKafkaService.java
| +-- IMongoService.java
| +-- IRabbitmqService.java
| +-- IRedisService.java
| +-- IRocketmqService.java
| +-- IMybatisUserModelService.java
| +-- IMybatisDepartmentInfoService.java
| +-- IWritingStyleService.java
+-- dto/ # 数据传输对象
| +-- UserModelDTO.java
| +-- UserModelQueryDTO.java
| +-- DepartmentInfoDTO.java
| +-- DepartmentInfoQueryDTO.java
+-- entity/ # 实体类
| +-- UserModel.java
| +-- DepartmentInfo.java
+-- po/ # 持久化对象
| +-- ModelPO.java
+-- enums/ # 枚举类
| +-- BaseResultEnum.java
+-- constants/ # 常量类
| +-- Constants.java
+-- service/ # 通用服务类
+-- ApiResult.java # 统一返回结果
+-- PageDTO.java # 分页数据传输对象
+-- PageQueryDTO.java # 分页查询参数2.2.3 依赖配置
API 模块的依赖非常精简,只包含基础工具库,不依赖任何中间件或框架:
xml
<dependencies>
<!-- 基础依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Spring WebFlux 依赖,用于支持 SSE 流式响应 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>依赖设计原则:
API 模块作为"契约层",其依赖应该尽可能精简。这是因为 API 模块会被 Provider 和 Consumer 同时依赖,如果 API 模块引入了过多的依赖,会导致传递依赖膨胀,增加冲突风险。在当前配置中,我们注意到引入了 spring-boot-starter-webflux 依赖,这是因为 IChatClientFactory 接口中的 chatStream 方法返回了 Flux<String> 类型,需要 Reactor 的支持。
2.3 smart-scaffold-provider:服务提供者
Provider 模块是整个项目的"业务核心",它实现了 API 模块中定义的所有服务接口,并集成了各种中间件。
2.3.1 模块定位与职责
Provider 模块的核心职责包括:
- 实现 Dubbo 服务接口: 提供 12 个服务的具体实现
- 集成中间件: 集成 Redis、Elasticsearch、Kafka、MongoDB、RabbitMQ、RocketMQ
- 数据访问: 通过 MyBatis 访问 MySQL 数据库
- AI 服务: 集成 Ollama 和 OpenAI API
- 服务导出: 将实现的服务通过 Dubbo 框架导出为 RPC 服务
2.3.2 依赖配置
Provider 模块的依赖最为丰富,包含了所有中间件的客户端库:
xml
<dependencies>
<!-- API 依赖 -->
<dependency>
<groupId>cc.bima.scaffold</groupId>
<artifactId>smart-scaffold-api</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<!-- Spring Boot 基础依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Dubbo 依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.9.1</version>
</dependency>
<!-- MyBatis 依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<!-- 中间件依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.3</version>
</dependency>
</dependencies>2.3.3 代码结构
smart-scaffold-provider/
+-- src/main/java/cc/bima/scaffold/provider/
+-- ProviderWebApplication.java # 启动类
+-- base/ # 基础类
| +-- BaseMapper.java # 通用 Mapper 接口
| +-- BaseService.java # 通用 Service 基类
+-- config/ # 配置类
| +-- SmartScaffold1Config.java # 数据源1配置
| +-- SmartScaffold2Config.java # 数据源2配置
+-- exception/ # 异常处理
| +-- GlobalExceptionHandler.java # 全局异常处理器
+-- dao/ # 数据访问层
| +-- db1/UserModelMapper.java # 用户模型 Mapper
| +-- db2/DepartmentInfoMapper.java # 部门信息 Mapper
+-- service/ # 服务实现层
| +-- ai/ # AI 服务
| | +-- ChatClientFactory.java # 聊天客户端工厂
| | +-- ModelService.java # 模型配置服务
| | +-- EmbeddingConfig.java # 嵌入配置服务
| | +-- writing/ # 写作服务
| | +-- WritingStyleService.java
| | +-- WritingPromptService.java
| +-- middleware/ # 中间件服务
| +-- RedisService.java
| +-- ElasticsearchService.java
| +-- KafkaService.java
| +-- MongoService.java
| +-- RabbitmqService.java
| +-- RocketmqService.java
| +-- MybatisUserModelService.java
| +-- MybatisDepartmentInfoService.java
+-- controller/ # 控制器(Provider 内部测试用)
+-- ai/AIController.java2.4 smart-scaffold-consumer:服务消费者
Consumer 模块是整个项目的"接入层",它引用 Provider 暴露的 Dubbo 服务,并通过 REST API 和 Web 页面提供给前端使用。
2.4.1 模块定位与职责
Consumer 模块的核心职责包括:
- 引用 Dubbo 服务: 通过
@DubboReference或 XML 配置引用 Provider 的服务 - REST API 封装: 将 RPC 服务封装为 REST API 供前端调用
- Web 页面渲染: 通过 Thymeleaf 模板引擎渲染前端页面
- 用户认证: 通过 OAuth 2.0 Filter 实现用户认证
- 请求过滤: 通过 Filter 机制实现统一的请求处理
2.4.2 依赖配置
Consumer 模块的依赖相对精简,不直接依赖中间件,而是通过 Dubbo RPC 调用 Provider 的服务:
xml
<dependencies>
<!-- API 依赖 -->
<dependency>
<groupId>cc.bima.scaffold</groupId>
<artifactId>smart-scaffold-api</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<!-- Spring Boot 基础依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Dubbo 依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.9.1</version>
</dependency>
</dependencies>注意: Consumer 模块排除了大量 Spring Boot 自动配置类(DataSource、Mongo、Redis、Kafka、RabbitMQ、Elasticsearch),因为这些中间件的连接配置和客户端 Bean 都在 Provider 端管理,Consumer 端不需要这些自动配置。
java
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
MongoDataAutoConfiguration.class,
MongoAutoConfiguration.class,
RedisAutoConfiguration.class,
RedisRepositoriesAutoConfiguration.class,
KafkaAutoConfiguration.class,
RabbitAutoConfiguration.class,
ElasticsearchDataAutoConfiguration.class
})2.4.3 代码结构
smart-scaffold-consumer/
+-- src/main/java/cc/bima/scaffold/consumer/
+-- ConsumerWebApplication.java # 启动类
+-- controller/ # 控制器层
| +-- MainController.java # 主控制器
| +-- FrontController.java # 前端页面控制器
| +-- LoginController.java # 登录控制器
| +-- ai/AIController.java # AI 功能控制器
| +-- middleware/ # 中间件控制器
| +-- RedisController.java
| +-- ElasticsearchController.java
| +-- KafkaController.java
| +-- MongoController.java
| +-- RabbitmqController.java
| +-- RocketmqController.java
| +-- MybatisUserModelController.java
| +-- MybatisDepartmentInfoController.java
+-- filter/ # 过滤器
| +-- OAuthFilter.java # OAuth 认证过滤器
| +-- FilterConfig.java # 过滤器配置
| +-- TokenRequestWrapper.java # Token 请求包装器
+-- service/ # 服务层
| +-- OAuthService.java # OAuth 服务
| +-- RestTemplateCustom.java # RestTemplate 配置
| +-- RequestLoggingInterceptor.java # 请求日志拦截器
+-- exception/ # 异常处理
+-- GlobalExceptionHandler.java # 全局异常处理器2.5 依赖链与构建顺序
2.5.1 Maven 依赖关系图
smart-scaffold-dubbo (父工程, pom)
|
+-- smart-scaffold-api (jar)
| +-- 依赖: lombok, commons-lang3, jackson, webflux
|
+-- smart-scaffold-provider (jar)
| +-- 依赖: smart-scaffold-api
| dubbo-spring-boot-starter
| curator-recipes, zookeeper
| mybatis, mysql, druid
| redis, kafka, rabbitmq, mongodb, elasticsearch, rocketmq
| httpclient5
|
+-- smart-scaffold-consumer (jar)
+-- 依赖: smart-scaffold-api
dubbo-spring-boot-starter
curator-recipes, zookeeper
web, webflux, thymeleaf
httpclient52.5.2 构建顺序
Maven Reactor 会按照依赖关系自动确定构建顺序:
1. smart-scaffold-api (无内部依赖,最先构建)
2. smart-scaffold-provider (依赖 api)
3. smart-scaffold-consumer (依赖 api)在 Jenkins CI/CD 环境中,构建命令为:
bash
# 根目录编译,-T 1C 表示每个 CPU 核心使用一个线程
mvn clean package -DskipTests -T 1C2.5.3 依赖隔离原则
本项目的依赖设计遵循严格的隔离原则:
- API 模块: 只依赖基础工具库,不依赖任何框架或中间件
- Provider 模块: 依赖所有中间件客户端,但不依赖 Web 层框架(Thymeleaf)
- Consumer 模块: 依赖 Web 层框架和 Dubbo,但不直接依赖中间件客户端
这种隔离设计的好处是:
- 减少依赖冲突: 各模块的依赖范围清晰,不易产生版本冲突
- 独立部署: Provider 和 Consumer 可以独立打包、独立部署
- 按需扩展: 如果需要添加新的中间件,只需要在 Provider 端添加依赖和实现
第三章 API 模块设计模式
3.1 face 包:Dubbo 服务接口定义规范
在 smart-scaffold-dubbo 项目中,所有 Dubbo 服务接口都定义在 cc.bima.scaffold.api.face 包下。使用 face 作为包名是一种独特的设计约定,它形象地表达了"面向接口编程"的理念——接口是服务的"面孔",Consumer 只需要看到服务的"面孔",而不需要了解其内部实现。
3.1.1 接口定义规范
Dubbo 服务接口的定义需要遵循以下规范:
1. 接口命名规范:
所有 Dubbo 服务接口以 I 前缀开头,后跟服务名称,以 Service 结尾:
java
public interface IRedisService { } // Redis 服务接口
public interface IKafkaService { } // Kafka 服务接口
public interface IElasticsearchService { } // Elasticsearch 服务接口2. 方法设计原则:
- 输入参数: 使用基本类型或简单的 Java 对象(POJO),避免使用复杂的数据结构
- 返回值: 使用 String 或自定义 DTO 作为返回值,便于序列化传输
- 异常处理: 接口方法声明中不抛出受检异常,异常通过 RuntimeException 传递
java
// 良好的接口设计示例
public interface IRedisService {
String testConnection();
String setKey(String key, String value);
String getKey(String key);
String deleteKey(String key);
}
// 避免在接口中使用复杂类型
public interface IMybatisUserModelService {
UserModelDTO save(UserModelDTO dto, String userId, String userName);
UserModelDTO get(Long id);
PageDTO<UserModelDTO> selectPageBy(UserModelQueryDTO queryDTO);
}3. 接口文档注释:
每个接口和接口方法都应该有清晰的 JavaDoc 注释,说明其功能、参数含义和返回值:
java
/**
* Redis 服务接口
* <p>
* 提供 Redis 相关的操作方法,包括键值操作、批量操作等
* </p>
*
* @author 必码 bima.cc
*/
public interface IRedisService {
/**
* 设置键值对
*
* @param key 键
* @param value 值
* @return 设置结果
*/
String setKey(String key, String value);
}3.1.2 接口分类体系
项目中的 12 个 Dubbo 服务接口可以按照功能分为三大类:
AI 服务类(4 个):
| 接口 | 功能 | 方法数 |
|---|---|---|
IChatClientFactory | 聊天客户端工厂,支持普通聊天和流式聊天 | 2 |
IModelService | 模型配置管理,支持多 AI 提供商 | 3 |
IWritingStyleService | 写作风格管理 | 3 |
IEmbeddingConfig | 嵌入向量配置 | 4 |
中间件服务类(6 个):
| 接口 | 功能 | 方法数 |
|---|---|---|
IRedisService | Redis 键值操作 | 12 |
IElasticsearchService | Elasticsearch 索引和文档操作 | 6 |
IKafkaService | Kafka 消息发送和接收 | 6 |
IMongoService | MongoDB 文档操作 | 5 |
IRabbitmqService | RabbitMQ 消息和队列管理 | 8 |
IRocketmqService | RocketMQ 消息和主题管理 | 9 |
数据服务类(2 个):
| 接口 | 功能 | 方法数 |
|---|---|---|
IMybatisUserModelService | 用户模型 CRUD | 8 |
IMybatisDepartmentInfoService | 部门信息 CRUD | 7 |
3.2 十二大 Dubbo 服务接口详解
3.2.1 IChatClientFactory -- 聊天客户端工厂
IChatClientFactory 是项目中最为核心的 AI 服务接口,它提供了与 AI 模型交互的能力,支持普通聊天和 SSE 流式聊天两种模式。
java
package cc.bima.scaffold.api.face;
import reactor.core.publisher.Flux;
/**
* 聊天客户端工厂接口
* <p>
* 提供创建聊天客户端和处理聊天请求的方法
* </p>
*
* @author 必码 bima.cc
*/
public interface IChatClientFactory {
/**
* 普通聊天
*
* @param userId 用户ID
* @param message 消息内容
* @param isCustom 是否使用自定义模型
* @return 聊天响应内容
*/
String chat(Long userId, String message, Boolean isCustom);
/**
* 流式聊天(SSE)
*
* @param userId 用户ID
* @param message 消息内容
* @param isCustom 是否使用自定义模型
* @return 流式响应内容
*/
Flux<String> chatStream(Long userId, String message, Boolean isCustom);
}设计要点:
chat方法返回String,适用于一次性获取完整响应的场景chatStream方法返回Flux<String>,基于 Reactor 的响应式流,适用于 SSE 流式推送场景isCustom参数允许用户选择使用系统默认模型或自定义模型- 接口返回
Flux<String>类型,这是 Dubbo 3.x Triple 协议支持流式通信的体现
3.2.2 IModelService -- 模型配置服务
IModelService 负责管理 AI 模型的配置信息,支持 Ollama、OpenAI 和兼容 OpenAI 的自定义 API 三种类型。
java
public interface IModelService {
/**
* 创建大模型服务配置
*/
Object createModelService(Long userId, Boolean isCustom);
/**
* 创建向量嵌入模型配置
*/
Object createEmbeddinService();
/**
* 获取当前使用的 API 类型
*/
String getCurrentApiType();
}设计要点:
createModelService方法返回Object类型,实际返回的是ModelService.ModelConfig内部类实例。这种设计将配置对象封装在实现类中,API 层不需要暴露配置细节isCustom参数区分系统默认配置和用户自定义配置getCurrentApiType方法返回当前使用的 API 类型(OLLAMA/OPENAI/CUSTOM)
3.2.3 IRedisService -- Redis 服务
IRedisService 提供了完整的 Redis 操作接口,涵盖键值操作、过期时间管理、批量操作等功能。
java
public interface IRedisService {
String testConnection();
String setKey(String key, String value);
String getKey(String key);
String deleteKey(String key);
String expireKey(String key, long seconds);
String setKeyWithExpiry(String key, String value, long seconds);
String incrementKey(String key);
String getConfig();
String batchSet(String batchData);
String batchGet(String batchData);
String keys(String pattern);
String flush();
}设计要点:
- 所有方法返回
String类型,统一了返回值格式,便于 Consumer 端处理 batchSet和batchGet方法使用字符串格式传递批量数据(key1=value1\nkey2=value2),避免了自定义序列化协议keys方法支持通配符查询,方便前端进行模糊搜索flush方法用于清空所有键,仅用于测试环境
3.2.4 IElasticsearchService -- Elasticsearch 服务
java
public interface IElasticsearchService {
String testConnection();
String createIndex(String indexName);
String addDocument(String indexName, String id, String document);
String searchDocument(String indexName, String query);
String deleteDocument(String indexName, String id);
String deleteIndex(String indexName);
}3.2.5 IKafkaService -- Kafka 服务
java
public interface IKafkaService {
String testConnection();
String sendMessage(String topic, String message);
String sendMessageWithKey(String topic, String key, String message);
String receiveMessage(String topic);
String clearReceivedMessages();
String clearReceivedMessagesByTopic(String topic);
}3.2.6 IMongoService -- MongoDB 服务
java
public interface IMongoService {
String testConnection();
String insertDocument(String collectionName, String document);
String findDocument(String collectionName, String query);
String updateDocument(String collectionName, String query, String update);
String deleteDocument(String collectionName, String query);
}3.2.7 IRabbitmqService -- RabbitMQ 服务
java
public interface IRabbitmqService {
String testConnection();
String sendMessage(String exchange, String routingKey, String message);
String receiveMessage(String queue);
String createQueue(String queue);
String bindQueue(String queue, String exchange, String routingKey);
String sendMessageToQueue(String queue, String message);
String createExchange(String exchange);
String bindQueueToExchange(String queue, String exchange, String routingKey);
}3.2.8 IRocketmqService -- RocketMQ 服务
java
public interface IRocketmqService {
String testConnection();
String getNameServer();
String sendMessage(String topic, String message);
String sendMessageWithTags(String topic, String tags, String message);
String receiveMessage(String topic);
String sendMessageAsync(String topic, String tag, String message);
String receiveMessageWithTags(String topic, String tag);
String getSentMessages(String topic);
boolean checkAndCreateTopic(String topic);
}3.2.9 IEmbeddingConfig -- 嵌入配置服务
java
public interface IEmbeddingConfig {
String getBaseUrl(Object config);
String getApiPath(String apiType, boolean isStream);
List<List<Double>> embed(Long userId, List<String> texts);
List<Double> embed(Long userId, String text);
}3.2.10 IMybatisUserModelService -- 用户模型服务
java
public interface IMybatisUserModelService {
UserModelDTO save(UserModelDTO dto, String userId, String userName);
UserModelDTO get(Long id);
void update(UserModelDTO dto, String userId, String userName);
void delete(Long id);
Object list(UserModelQueryDTO queryDTO);
PageDTO<UserModelDTO> selectPageBy(UserModelQueryDTO queryDTO);
List<UserModelDTO> selectBy(UserModelQueryDTO queryDTO);
void remove(Long id);
}3.2.11 IMybatisDepartmentInfoService -- 部门信息服务
java
public interface IMybatisDepartmentInfoService {
Long save(DepartmentInfoDTO dto, String userId, String userName);
DepartmentInfoDTO get(Long id);
void remove(Long id);
PageDTO<DepartmentInfoDTO> selectPageBy(DepartmentInfoQueryDTO queryDTO);
List<DepartmentInfoDTO> selectBy(DepartmentInfoQueryDTO queryDTO);
DepartmentInfoDTO uniqueBy(DepartmentInfoQueryDTO queryDTO);
ApiResult<?> checkSaveInput(DepartmentInfoDTO dto);
ApiResult<?> checkRemove(Long id);
}3.2.12 IWritingStyleService -- 写作风格服务
java
public interface IWritingStyleService {
String getAllPresets();
String getPresetStyle(String styleCode);
String applyStyleToPrompt(String basePrompt, String styleCode);
}3.3 DTO/Entity/PO/Enum/Constants 共享类体系
在分布式服务架构中,Provider 和 Consumer 之间传递的数据对象需要在 API 模块中统一定义。本项目建立了一套完整的共享类体系。
3.3.1 分层对象模型
+-----------------------------------------------------+
| API 模块 |
| |
| Entity(实体类)------- 与数据库表对应 |
| | |
| v |
| DTO(数据传输对象)---- 服务间传递的数据 |
| | |
| v |
| PO(持久化对象)------- 数据库操作的中间对象 |
| | |
| v |
| QueryDTO(查询对象)---- 查询条件封装 |
+-----------------------------------------------------+3.3.2 ApiResult -- 统一返回结果
ApiResult<T> 是整个项目的统一返回结果封装类,它为所有 API 响应提供了统一的数据结构。
java
@Accessors(chain = true)
public class ApiResult<T> implements Serializable {
private Integer code; // 状态码:0-成功,1-失败,403-无权限
private String message; // 状态消息
private T data; // 业务数据
// 返回成功数据
public static <T> ApiResult<T> success(T data) {
return new ApiResult<>(BaseResultEnum.SUCCESS.getCode(),
BaseResultEnum.SUCCESS.getValue(), data);
}
// 返回错误数据
public static <T> ApiResult<T> fail(String msg) {
return new ApiResult<>(BaseResultEnum.FAIL.getCode(), msg, null);
}
}使用示例:
java
// Controller 中使用
@PostMapping("/key")
public ApiResult<?> setKey(@RequestParam String key, @RequestParam String value) {
return ApiResult.success(redisService.setKey(key, value));
}返回 JSON 格式:
json
{
"code": 0,
"message": "success",
"data": "Set key: username with value: zhangsan"
}3.3.3 PageDTO -- 分页数据传输对象
PageDTO<T> 是分页查询的统一返回对象,封装了分页数据和分页信息。
java
@Setter
@Getter
@ToString
public class PageDTO<T> implements Serializable {
private List<T> list; // 数据列表
private Integer page; // 当前页码
private Integer pageSize; // 每页大小
private Integer count; // 总记录数
}3.3.4 BaseResultEnum -- 基础结果枚举
java
public enum BaseResultEnum {
SUCCESS(0, "success"),
FAIL(1, "fail"),
NO_AUTH(403, "no auth");
private final Integer code;
private final String value;
}3.3.5 Constants -- 系统常量
java
public class Constants {
// OAuth 2.0 配置
public static final String ACCESS_TOKEN_KEY = "access_token";
public static final String REFRESH_TOKEN_KEY = "refresh_token";
public static final String USER_ID_KEY = "USER_ID_KEY";
public static final String USER_NAME_KEY = "USER_NAME_KEY";
// 默认分页每页记录数
public static final Integer PAGE_SIZE = 20;
}3.3.6 ModelPO -- 模型配置持久化对象
java
@Data
public class ModelPO implements Serializable {
private String apiType; // AI提供商类型: OPENAI, OLLAMA等
private String configName; // 配置名称
private String apiKey; // API密钥
private String baseUrl; // API基础URL
private String modelName; // 模型名称
private BigDecimal temperature; // 温度参数
private Integer maxTokens; // 最大Token数
private String embeddingModel; // 嵌入模型名称
private String remark; // 备注信息
}3.4 API 模块的最佳设计模式
3.4.1 契约优先设计(Contract-First Design)
本项目的 API 模块严格遵循"契约优先"的设计原则:
- 先定义接口,后实现: 所有服务接口在 API 模块中定义完成后,Provider 和 Consumer 才开始各自的实现和引用
- 接口与实现分离: API 模块只包含接口定义,不包含任何实现代码
- 版本化管理: 通过 Maven 版本号管理 API 模块的变更
这种设计的好处是:
- Provider 和 Consumer 可以并行开发
- 接口变更时,编译期就能发现不兼容问题
- API 模块可以独立发布,供其他项目引用
3.4.2 面向字符串的接口设计
本项目中的中间件服务接口(如 IRedisService、IElasticsearchService)大量使用了 String 类型作为输入和输出参数。这种设计选择有其合理性:
优势:
- 通用性强: String 类型可以被任何序列化框架正确处理
- 灵活性高: 可以传递 JSON、XML 等任意格式的数据
- 调试方便: 日志中可以直接看到参数内容
- 前后端友好: 前端可以直接传递和接收字符串
适用场景:
- 中间件操作类接口(CRUD 操作)
- 测试验证类接口
- 管理工具类接口
不适用场景:
- 强类型业务接口(应使用 DTO)
- 需要参数校验的接口(应使用 DTO + JSR-303)
- 复杂查询接口(应使用 QueryDTO)
3.4.3 统一返回值封装
所有接口的返回值都通过 ApiResult<T> 进行统一封装,确保了 API 响应格式的一致性:
json
// 成功响应
{
"code": 0,
"message": "success",
"data": { ... }
}
// 失败响应
{
"code": 1,
"message": "操作失败的具体原因",
"data": null
}
// 无权限响应
{
"code": 403,
"message": "no auth",
"data": null
}第四章 Zookeeper 三中心合一
4.1 注册中心(Registry)
注册中心是 Dubbo 架构中最核心的基础设施组件。在 smart-scaffold-dubbo 项目中,我们使用 Zookeeper 作为注册中心,实现了服务的自动注册与发现。
4.1.1 注册中心的工作原理
Zookeeper 作为注册中心,利用其树形数据结构(ZNode)来存储服务信息。Dubbo 在 Zookeeper 中创建的节点结构如下:
/dubbo/
+-- cc.bima.scaffold.api.face.IRedisService/
| +-- providers/
| | +-- dubbo://192.168.1.100:20880/cc.bima.scaffold.api.face.IRedisService
| | ?anyhost=true&application=smart-scaffold-dubbo-provider&version=1.0.0&...
| +-- consumers/
| | +-- consumer://192.168.1.200/cc.bima.scaffold.api.face.IRedisService
| | ?application=smart-scaffold-dubbo-consumer&version=1.0.0&...
| +-- routers/
| +-- ...
+-- cc.bima.scaffold.api.face.IElasticsearchService/
| +-- providers/
| +-- consumers/
+-- ...节点类型说明:
- providers 节点: 临时节点(EPHEMERAL),存储服务提供者的地址信息。当 Provider 宕机时,Zookeeper 会自动删除对应的节点,Consumer 会收到通知并更新本地缓存
- consumers 节点: 临时节点,存储服务消费者的订阅信息
- routers 节点: 持久节点,存储路由规则
4.1.2 服务注册流程
Provider 启动
|
v
1. 读取 dubbo-provider.xml 配置
|
v
2. 初始化 Zookeeper 连接(通过 Curator)
|
v
3. 在 /dubbo/{interface}/providers/ 下创建临时节点
节点内容: dubbo://{ip}:{port}/{interface}?{parameters}
|
v
4. 注册完成,开始接受 RPC 调用4.1.3 服务发现流程
Consumer 启动
|
v
1. 读取 dubbo-consumer.xml 配置
|
v
2. 初始化 Zookeeper 连接(通过 Curator)
|
v
3. 在 /dubbo/{interface}/providers/ 下注册子节点监听
|
v
4. 获取当前所有 Provider 地址列表
|
v
5. 创建 Invoker 代理对象
|
v
6. 当 Provider 地址变更时,通过 Watcher 收到通知
重新获取地址列表,更新本地缓存4.2 配置中心(Config Center)
Dubbo 3.x 的配置中心用于集中管理服务的配置信息,包括服务超时、重试策略、负载均衡策略等。在本项目中,我们将配置中心也指向了 Zookeeper,实现了"三中心合一"的简化架构。
4.2.1 配置中心的数据结构
/dubbo/config/
+-- smart-scaffold-dubbo-provider/
| +-- dubbo.properties # 应用级别配置
| +-- cc.bima.scaffold.api.face.IRedisService/
| +-- dubbo.properties # 服务级别配置
+-- smart-scaffold-dubbo-consumer/
| +-- dubbo.properties
| +-- ...
+-- global/
+-- dubbo.properties # 全局配置4.2.2 配置优先级
Dubbo 的配置加载遵循以下优先级(从高到低):
- JVM -D 参数
- 外部配置文件(application.yml / application.properties)
- Dubbo XML 配置(dubbo-provider.xml / dubbo-consumer.xml)
- 配置中心(Zookeeper Config Center)
- Dubbo 默认值
4.3 元数据中心(Metadata Report)
元数据中心用于存储服务的接口定义、方法签名、参数类型等元数据信息。在 Dubbo 3.x 的应用级服务发现模式下,元数据中心的作用更加重要。
4.3.1 元数据的作用
- 接口校验: Consumer 在引用服务时,可以通过元数据中心获取接口的完整定义,进行参数校验
- 服务治理: 流量治理规则(如路由规则、降级规则)需要依赖元数据信息
- 服务文档: 元数据可以作为服务的自动文档,方便开发者查看
4.3.2 元数据存储结构
/dubbo/metadata/
+-- smart-scaffold-dubbo-provider/
+-- revision # 应用 revision
+-- cc.bima.scaffold.api.face.IRedisService/
+-- 1.0.0/ # 版本号
| +-- metadata.json # 接口元数据
+-- ...
+-- ...4.4 dubbo-provider.xml 配置详解
Provider 端的 Dubbo 配置文件定义了服务提供者的基本行为。以下是本项目的完整配置:
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- Dubbo 应用名称 -->
<dubbo:application name="smart-scaffold-dubbo-provider" />
<!-- 注册中心配置 -->
<dubbo:registry protocol="zookeeper"
address="zookeeper://192.168.1.30:2181"
simplified="true" />
<!-- 配置中心 -->
<dubbo:config-center protocol="zookeeper"
address="zookeeper://192.168.1.30:2181" />
<!-- 元数据中心 -->
<dubbo:metadata-report
address="zookeeper://192.168.1.30:2181" />
</beans>逐行解析:
<dubbo:application name="smart-scaffold-dubbo-provider" />
- 定义 Dubbo 应用的名称,这个名称会作为 Zookeeper 中节点的标识
- 在 Dubbo Admin 控制台中,可以通过这个名称找到对应的应用
- 建议使用有意义的名称,便于运维管理
<dubbo:registry protocol="zookeeper" address="zookeeper://192.168.1.30:2181" simplified="true" />
protocol="zookeeper":指定注册中心协议为 Zookeeperaddress="zookeeper://192.168.1.30:2181":Zookeeper 服务地址,支持集群模式(多个地址用逗号分隔)simplified="true":启用简化模式,在 Zookeeper 中只注册应用级信息,减少注册数据量
<dubbo:config-center protocol="zookeeper" address="zookeeper://192.168.1.30:2181" />
- 配置中心使用 Zookeeper 作为存储后端
- 如果不显式配置,Dubbo 会尝试使用注册中心作为配置中心
<dubbo:metadata-report address="zookeeper://192.168.1.30:2181" />
- 元数据中心使用 Zookeeper 作为存储后端
- 在应用级服务发现模式下,元数据中心是必须的
4.5 dubbo-consumer.xml 配置详解
Consumer 端的 Dubbo 配置文件不仅定义了基本配置,还声明了所有需要引用的服务。
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 全局 Consumer 配置 -->
<dubbo:consumer check="false" />
<!-- Dubbo 应用名称 -->
<dubbo:application name="smart-scaffold-dubbo-consumer"
qos-enable="false" qos-port="22223" />
<!-- 注册中心配置 -->
<dubbo:registry protocol="zookeeper"
address="zookeeper://192.168.1.30:2181" />
<!-- 配置中心 -->
<dubbo:config-center protocol="zookeeper"
address="zookeeper://192.168.1.30:2181" />
<!-- 元数据中心 -->
<dubbo:metadata-report
address="zookeeper://192.168.1.30:2181" />
<!-- 12 个服务引用配置 -->
<dubbo:reference interface="cc.bima.scaffold.api.face.IChatClientFactory"
id="chatClientFactory" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IElasticsearchService"
id="elasticsearchService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IEmbeddingConfig"
id="embeddingConfig" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IKafkaService"
id="kafkaService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IModelService"
id="modelService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IMongoService"
id="mongoService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IMybatisDepartmentInfoService"
id="mybatisDepartmentService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IMybatisUserModelService"
id="mybatisUserModelService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IRabbitmqService"
id="rabbitmqService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IRedisService"
id="redisService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IRocketmqService"
id="rocketmqService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IWritingStyleService"
id="writingStyleService" cache="false" version="1.0.0"
timeout="5000" check="false" />
</beans>关键配置参数解析:
| 参数 | 值 | 说明 |
|---|---|---|
interface | cc.bima.scaffold.api.face.IXxxService | 服务接口的全限定名 |
id | xxxService | Spring 容器中的 Bean 名称 |
cache | false | 禁用结果缓存,确保每次调用都获取最新数据 |
version | 1.0.0 | 服务版本号,必须与 Provider 端一致 |
timeout | 5000 | 调用超时时间(毫秒),5 秒 |
check | false | 启动时不检查服务是否可用,避免循环依赖 |
4.6 Zookeeper 3.9.1 集成要点
4.6.1 版本选择
本项目使用 Zookeeper 3.9.1 版本。选择这个版本的原因包括:
- 稳定性: 3.9.x 是 Zookeeper 的稳定版本线
- 性能优化: 相比 3.5.x,3.9.x 在网络 I/O 和内存管理方面有显著优化
- 安全增强: 支持 SASL 认证和 ACL 权限控制
- Java 17 兼容: 完全兼容 Java 17 运行环境
4.6.2 依赖配置
在 Provider 和 Consumer 的 POM 文件中,Zookeeper 的依赖配置如下:
xml
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.9.1</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>关键点: 排除了 slf4j-log4j12 依赖,避免与 Spring Boot 默认的 Logback 日志框架冲突。这是一个常见的依赖冲突处理方式。
4.6.3 Zookeeper 集群部署建议
在生产环境中,Zookeeper 应该以集群模式部署,至少需要 3 个节点(奇数个)以保证高可用:
yaml
# Zookeeper 集群地址配置(3 节点)
zookeeper://zk1.example.com:2181,zk2.example.com:2181,zk3.example.com:21814.7 Curator 5.1.0 客户端集成
4.7.1 Curator 简介
Apache Curator 是 Zookeeper 的 Java 客户端库,它封装了 Zookeeper 原生 API,提供了更高层次的抽象和更丰富的功能。Dubbo 使用 Curator 来与 Zookeeper 进行交互。
4.7.2 依赖配置
xml
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
<version>5.1.0</version>
</dependency>curator-recipes:提供分布式锁、Leader 选举、缓存监听等常用"配方"curator-x-discovery:提供服务发现功能,Dubbo 的应用级服务发现依赖此模块
4.7.3 Curator 与 Zookeeper 版本兼容性
| Curator 版本 | Zookeeper 版本 | Java 版本 |
|---|---|---|
| 5.1.0 | 3.6.x - 3.9.x | 8+ |
| 5.5.0 | 3.7.x - 3.9.x | 8+ |
| 5.7.0 | 3.8.x - 3.9.x | 11+ |
本项目选择 Curator 5.1.0 + Zookeeper 3.9.1 的组合,经过充分验证,运行稳定。
第五章 服务提供者配置深度解析
5.1 @EnableDubbo + @ComponentScan 启动机制
Provider 端的启动类是整个服务的入口,通过注解配置启用了 Dubbo 框架和 Spring 组件扫描。
java
@SpringBootApplication
@EnableDubbo
@ComponentScan({ "cc.bima.scaffold" })
public class ProviderWebApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderWebApplication.class, args);
}
}5.1.1 @SpringBootApplication 注解
@SpringBootApplication 是 Spring Boot 的核心注解,它是一个组合注解,等价于:
java
@SpringBootConfiguration // 标识为配置类
@EnableAutoConfiguration // 启用自动配置
@ComponentScan // 组件扫描(默认扫描当前包及子包)5.1.2 @EnableDubbo 注解
@EnableDubbo 是 Dubbo Spring Boot Starter 提供的注解,它的核心作用包括:
- 启用 Dubbo 注解扫描: 扫描
@DubboService注解,将标注了该注解的类注册为 Dubbo 服务 - 启用 Dubbo 配置: 加载 Dubbo 相关的配置文件(如 dubbo-provider.xml)
- 初始化 Dubbo SPI: 加载 Dubbo 的扩展点
@EnableDubbo 注解的源码(简化版):
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@EnableDubboConfig // 启用 Dubbo 配置
@DubboComponentScan // 启用 Dubbo 组件扫描
public @interface EnableDubbo {
// 指定扫描的基础包
String[] basePackages() default {};
// 指定扫描的注解类
Class<?>[] basePackageClasses() default {};
}5.1.3 @ComponentScan 注解
@ComponentScan({ "cc.bima.scaffold" }) 指定了 Spring 组件扫描的基础包。这个配置确保了以下组件能够被正确扫描和注册:
cc.bima.scaffold.provider.service下的@Service注解类cc.bima.scaffold.provider.config下的@Configuration注解类cc.bima.scaffold.provider.dao下的@Mapper注解类
注意: @ComponentScan 的包路径设置为 cc.bima.scaffold(而不是 cc.bima.scaffold.provider),这是为了确保能够扫描到 API 模块中的共享类(如 ApiResult、Constants 等)。
5.2 Provider WebApplication 启动类
Provider 的启动流程可以分为以下几个阶段:
ProviderWebApplication.main()
|
v
1. Spring Boot 启动
+-- 加载 application.yml 配置
+-- 初始化数据源(MySQL)
+-- 初始化中间件客户端(Redis、Kafka、MongoDB 等)
+-- 初始化 MyBatis Mapper
|
v
2. @EnableDubbo 生效
+-- 扫描 @DubboService 注解
+-- 加载 dubbo-provider.xml 配置
+-- 连接 Zookeeper
+-- 注册服务到 Zookeeper
|
v
3. 服务就绪
+-- 开始监听 RPC 端口(默认 20880)
+-- 接受 Consumer 的 RPC 调用
+-- 发布健康检查端点5.3 Dubbo 服务导出注解 @DubboService
@DubboService 是 Dubbo 3.x 提供的服务导出注解,用于将一个 Spring Bean 标记为 Dubbo 服务提供者。
5.3.1 注解属性
@DubboService 注解支持以下常用属性:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
version | String | "0.0.0" | 服务版本号 |
group | String | "" | 服务分组 |
interfaceClass | Class<?> | 接口类 | 指定服务接口 |
timeout | int | 1000 | 调用超时时间(毫秒) |
retries | int | 2 | 失败重试次数 |
loadbalance | String | "random" | 负载均衡策略 |
actives | int | 0 | 最大并发调用数 |
async | boolean | false | 是否异步执行 |
validation | String | "" | 参数校验方式 |
5.3.2 使用示例
在本项目中,@DubboService 注解的使用方式有以下几种:
方式一:仅使用 @DubboService(默认配置)
java
@DubboService
@Service("RedisService")
public class RedisService implements IRedisService {
// ...
}方式二:指定版本号
java
@DubboService(version = "1.0.0")
@Service("ElasticsearchService")
public class ElasticsearchService implements IElasticsearchService {
// ...
}方式三:同时使用 @DubboService 和 @Component
java
@DubboService
@Slf4j
@Component
@RequiredArgsConstructor
public class ChatClientFactory implements IChatClientFactory {
// ...
}设计说明: 在实际使用中,@DubboService 和 @Service(或 @Component)通常一起使用。@DubboService 负责将服务导出为 RPC 服务,@Service(或 @Component)负责将类注册为 Spring Bean,使其能够使用 Spring 的依赖注入功能。
5.4 服务版本管理(version="1.0.0")
5.4.1 版本号的作用
在 Dubbo 中,版本号是服务路由的重要依据。当同一个接口有多个实现时,可以通过版本号来区分不同的实现。
/dubbo/cc.bima.scaffold.api.face.IRedisService/providers/
+-- dubbo://...?version=1.0.0&... # 版本 1.0.0 的 Provider
+-- dubbo://...?version=2.0.0&... # 版本 2.0.0 的 Provider
+-- ...Consumer 在引用服务时,必须指定版本号,Dubbo 会根据版本号将请求路由到对应的 Provider。
5.4.2 版本号配置方式
Provider 端(注解方式):
java
@DubboService(version = "1.0.0")
public class RedisService implements IRedisService { }Consumer 端(XML 方式):
xml
<dubbo:reference interface="cc.bima.scaffold.api.face.IRedisService"
id="redisService" version="1.0.0" />Consumer 端(注解方式):
java
@DubboReference(version = "1.0.0")
private IRedisService redisService;5.4.3 版本迁移策略
在实际项目中,服务版本升级是一个常见的需求。推荐的版本迁移策略如下:
阶段 1: 部署新版本 Provider(v2.0.0),旧版本 Provider(v1.0.0)继续运行
阶段 2: 逐步将 Consumer 切换到 v2.0.0
阶段 3: 确认所有 Consumer 已切换后,下线 v1.0.0 的 Provider5.5 超时/重试配置(timeout="5000")
5.5.1 超时配置
超时时间是 RPC 调用中最重要的配置之一。在本项目中,所有服务的超时时间统一设置为 5000 毫秒(5 秒)。
Consumer 端配置:
xml
<dubbo:reference interface="cc.bima.scaffold.api.face.IRedisService"
id="redisService" timeout="5000" />超时配置的优先级:
- 方法级配置 > 接口级配置 > 全局配置 > 默认值(1000ms)
超时配置建议:
| 服务类型 | 建议超时时间 | 说明 |
|---|---|---|
| Redis 操作 | 1000-3000ms | 内存操作,响应快 |
| 数据库查询 | 3000-5000ms | 涉及磁盘 I/O |
| Elasticsearch 搜索 | 5000-10000ms | 全文搜索可能较慢 |
| AI 聊天 | 30000-60000ms | 大模型推理耗时较长 |
| Kafka 发送 | 3000-5000ms | 网络通信 |
5.5.2 重试配置
Dubbo 默认的重试次数为 2 次(即总共执行 3 次)。重试机制在遇到网络抖动、Provider 临时不可用等场景下非常有用,但在某些场景下需要谨慎使用。
不适合重试的场景:
- 写操作(如数据库插入、更新)
- 非幂等操作(如消息发送)
- 需要严格顺序的操作
适合重试的场景:
- 读操作(如查询缓存、查询数据库)
- 幂等操作(如重复查询)
5.6 Provider 内部完整实现架构
Provider 端的内部实现可以分为四个层次:基础层、数据访问层、服务实现层和控制器层。
5.6.1 基础层(Base)
基础层提供了通用的 CRUD 操作抽象,减少了重复代码。
BaseService 通用服务基类:
java
public abstract class BaseService<M extends BaseMapper<T, Q>, T, Q extends PageQueryDTO> {
@Autowired
protected M mapper;
// 通过主键查询
public T get(Long id) {
return handleQueryResult(mapper.selectByPrimaryKey(id), null);
}
// 通过主键删除
public void remove(Long id) {
mapper.deleteByPrimaryKey(id);
}
// 分页查询
public PageDTO<T> selectPageBy(Q queryDTO) {
queryDTO.setIsPage(true);
handleQueryParam(queryDTO);
return new PageDTO<>(
handleQueryResult(mapper.selectBy(queryDTO)),
mapper.countBy(queryDTO),
queryDTO.getPage(),
queryDTO.getPageSize()
);
}
// 条件查询
public List<T> selectBy(Q queryDTO) {
queryDTO.setIsPage(false);
return handleQueryResult(mapper.selectBy(queryDTO));
}
// 子类可重写:查询前的入参处理
public Q handleQueryParam(Q queryDTO) { return queryDTO; }
// 子类可重写:查询后的结果处理
public T handleQueryResult(T dto) { return dto; }
// 子类可重写:新增数据参数校验
public ApiResult<?> checkSaveInput(T dto) { return ApiResult.success(); }
// 子类可重写:删除数据参数校验
public ApiResult<?> checkRemove(Long id) { return ApiResult.success(); }
}设计亮点:
- 泛型设计:
BaseService<M, T, Q>使用三个泛型参数,分别对应 Mapper 类型、实体类型和查询类型 - 模板方法模式:
handleQueryParam、handleQueryResult、checkSaveInput、checkRemove是模板方法,子类可以按需重写 - 统一分页:
selectPageBy方法封装了分页查询的通用逻辑
5.6.2 数据访问层(DAO)
数据访问层使用 MyBatis 框架,支持多数据源配置。
dao/
+-- db1/
| +-- UserModelMapper.java # 数据源1:用户模型
+-- db2/
+-- DepartmentInfoMapper.java # 数据源2:部门信息多数据源通过不同的配置类进行管理:
java
@Configuration
@MapperScan(basePackages = "cc.bima.scaffold.provider.dao.db1",
sqlSessionFactoryRef = "db1SqlSessionFactory")
public class SmartScaffold1Config {
// 数据源1的配置
}
@Configuration
@MapperScan(basePackages = "cc.bima.scaffold.provider.dao.db2",
sqlSessionFactoryRef = "db2SqlSessionFactory")
public class SmartScaffold2Config {
// 数据源2的配置
}5.6.3 服务实现层(Service)
服务实现层是 Provider 的核心,分为 AI 服务和中间件服务两大类。
AI 服务实现示例 -- ChatClientFactory:
ChatClientFactory 是项目中最为复杂的实现类之一,它支持多种 AI API 类型(Ollama、OpenAI、自定义 OpenAI 兼容 API),并提供了普通聊天和流式聊天两种模式。
java
@DubboService
@Slf4j
@Component
@RequiredArgsConstructor
public class ChatClientFactory implements IChatClientFactory {
private final ModelService modelService;
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public String chat(Long userId, String message, Boolean isCustom) {
// 1. 获取模型配置
Object config = modelService.createModelService(userId, isCustom);
String baseUrl = getBaseUrl(config);
String model = getModelName(config);
String apiType = getApiType(config);
String apiPath = getApiPath(apiType, false);
// 2. 构建 WebClient
WebClient webClient = WebClient.builder()
.baseUrl(baseUrl).build();
// 3. 构建请求体并发送请求
Map<String, Object> requestBody = buildRequestBody(config, model, message, false);
String responseBody = webClient.post()
.uri(apiPath)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(requestBody))
.retrieve()
.bodyToMono(String.class)
.block();
// 4. 解析响应(支持多种格式)
Map<String, Object> parsed = objectMapper.readValue(responseBody, Map.class);
// OLLAMA 格式
if (parsed.containsKey("response")) {
return (String) parsed.get("response");
}
// OpenAI 兼容格式
if (parsed.containsKey("choices")) {
List<Map<String, Object>> choices =
(List<Map<String, Object>>) parsed.get("choices");
Map<String, Object> message =
(Map<String, Object>) choices.get(0).get("message");
return (String) message.get("content");
}
throw new RuntimeException("无法解析响应格式");
}
@Override
public Flux<String> chatStream(Long userId, String message, Boolean isCustom) {
// 流式聊天,使用 WebClient 的 SSE 能力
Object configObj = modelService.createModelService(userId, isCustom);
ModelService.ModelConfig config = (ModelService.ModelConfig) configObj;
WebClient webClient = WebClient.builder()
.baseUrl(getBaseUrl(config)).build();
Map<String, Object> requestBody = buildRequestBody(config,
getModelName(config), message, true);
return webClient.post()
.uri(getApiPath(config.getApiType(), true))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.TEXT_EVENT_STREAM)
.body(BodyInserters.fromValue(requestBody))
.retrieve()
.bodyToFlux(String.class);
}
}中间件服务实现示例 -- KafkaService:
java
@DubboService(version = "1.0.0")
@Service("KafkaService")
public class KafkaService implements IKafkaService {
private final KafkaTemplate<String, String> kafkaTemplate;
private final String bootstrapServers;
private final Map<String, List<String>> receivedMessages = new ConcurrentHashMap<>();
@Autowired
public KafkaService(KafkaTemplate<String, String> kafkaTemplate,
@Value("${spring.kafka.bootstrap-servers}") String bootstrapServers) {
this.kafkaTemplate = kafkaTemplate;
this.bootstrapServers = bootstrapServers;
}
// Kafka 消息监听器
@KafkaListener(topicPattern = ".*", groupId = "smart-scaffold-group")
public void listen(ConsumerRecord<String, String> record) {
String topic = record.topic();
String message = record.key() != null
? "[" + record.key() + "] " + record.value()
: record.value();
receivedMessages.computeIfAbsent(topic, k -> new ArrayList<>())
.add(message);
}
@Override
public String sendMessage(String topic, String message) {
createTopicIfNotExists(topic);
CompletableFuture<SendResult<String, String>> future =
kafkaTemplate.send(topic, message);
SendResult<String, String> result = future.get(5, TimeUnit.SECONDS);
return "Sent message to topic: " + topic
+ " (Partition: " + result.getRecordMetadata().partition()
+ ", Offset: " + result.getRecordMetadata().offset() + ")";
}
}5.6.4 控制器层(Controller)
Provider 端的控制器主要用于内部测试,不对外暴露。例如 AIController 用于测试 AI 服务:
java
@RestController
@RequestMapping("/ai")
public class AIController {
// Provider 内部的测试控制器
}第六章 服务消费者配置深度解析
6.1 dubbo:reference 声明式服务引用
在 Dubbo 中,Consumer 端引用远程服务有两种方式:XML 声明式和注解式。本项目在 XML 配置文件中统一声明了所有服务引用,同时在 Controller 中使用 @DubboReference 注解进行注入。
6.1.1 XML 声明式引用
在 dubbo-consumer.xml 中,通过 <dubbo:reference> 标签声明服务引用:
xml
<dubbo:reference interface="cc.bima.scaffold.api.face.IRedisService"
id="redisService"
cache="false"
version="1.0.0"
timeout="5000"
check="false" />XML 声明式引用的优势:
- 集中管理: 所有服务引用配置集中在一个文件中,便于统一管理
- 配置灵活: 可以方便地修改超时、重试、负载均衡等参数
- 与 Spring 集成: 声明的服务会自动注册为 Spring Bean,可以在其他 Bean 中通过
@Autowired注入
6.1.2 注解式引用
在 Controller 中,通过 @DubboReference 注解注入远程服务:
java
@RestController
@RequestMapping("/api/redis")
public class RedisController {
@DubboReference
private IRedisService redisService;
@PostMapping("/key")
public ApiResult<?> setKey(@RequestParam String key, @RequestParam String value) {
return ApiResult.success(redisService.setKey(key, value));
}
}注解式引用的优势:
- 简洁直观: 不需要 XML 配置,直接在字段上添加注解
- IDE 友好: IDE 可以直接跳转到接口定义,便于查看方法签名
- 类型安全: 编译期即可检查接口类型是否正确
6.1.3 两种方式的对比
| 对比维度 | XML 声明式 | 注解式 |
|---|---|---|
| 配置位置 | 集中在 XML 文件 | 分散在各 Controller 中 |
| 配置灵活性 | 高(支持所有属性) | 中(常用属性) |
| IDE 支持 | 一般 | 好 |
| 代码侵入性 | 低 | 中 |
| 适用场景 | 需要精细配置的场景 | 快速开发的场景 |
本项目选择: 本项目采用了 XML + 注解混合模式。在 dubbo-consumer.xml 中统一声明所有服务引用(配置 version、timeout、cache 等参数),在 Controller 中通过 @DubboReference 注解注入服务引用。
6.2 Consumer WebApplication 启动类
Consumer 端的启动类与 Provider 端有显著不同,主要体现在自动配置排除和配置文件导入。
java
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
MongoDataAutoConfiguration.class,
MongoAutoConfiguration.class,
RedisAutoConfiguration.class,
RedisRepositoriesAutoConfiguration.class,
KafkaAutoConfiguration.class,
RabbitAutoConfiguration.class,
ElasticsearchDataAutoConfiguration.class
})
@ComponentScan({ "cc.bima.scaffold" })
@ImportResource(locations = { "classpath:dubbo-consumer.xml" })
public class ConsumerWebApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerWebApplication.class, args);
}
}6.2.1 自动配置排除
Consumer 端排除了大量 Spring Boot 自动配置类,这是因为:
- Consumer 不直接连接中间件: 所有中间件操作都通过 RPC 调用 Provider 完成
- 避免启动失败: 如果不禁用这些自动配置,Spring Boot 会在启动时尝试连接中间件(如 Redis、MongoDB),如果连接不上就会启动失败
- 减少资源消耗: 不需要创建中间件的连接池和客户端 Bean
排除的自动配置类说明:
| 排除的自动配置 | 原因 |
|---|---|
DataSourceAutoConfiguration | Consumer 不直接连接数据库 |
MongoAutoConfiguration | Consumer 不直接连接 MongoDB |
RedisAutoConfiguration | Consumer 不直接连接 Redis |
KafkaAutoConfiguration | Consumer 不直接连接 Kafka |
RabbitAutoConfiguration | Consumer 不直接连接 RabbitMQ |
ElasticsearchDataAutoConfiguration | Consumer 不直接连接 Elasticsearch |
6.2.2 @ImportResource 导入 Dubbo 配置
java
@ImportResource(locations = { "classpath:dubbo-consumer.xml" })@ImportResource 注解将 dubbo-consumer.xml 配置文件导入到 Spring 容器中。这使得 XML 中声明的 <dubbo:reference> 能够被 Spring 管理,并通过 @DubboReference 或 @Autowired 注入到 Controller 中。
6.3 十二个服务的引用配置
Consumer 端在 dubbo-consumer.xml 中声明了 12 个服务的引用配置,每个服务的配置参数保持一致:
xml
<!-- AI 服务 -->
<dubbo:reference interface="cc.bima.scaffold.api.face.IChatClientFactory"
id="chatClientFactory" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IModelService"
id="modelService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IEmbeddingConfig"
id="embeddingConfig" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IWritingStyleService"
id="writingStyleService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<!-- 中间件服务 -->
<dubbo:reference interface="cc.bima.scaffold.api.face.IRedisService"
id="redisService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IElasticsearchService"
id="elasticsearchService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IKafkaService"
id="kafkaService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IMongoService"
id="mongoService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IRabbitmqService"
id="rabbitmqService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IRocketmqService"
id="rocketmqService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<!-- 数据服务 -->
<dubbo:reference interface="cc.bima.scaffold.api.face.IMybatisUserModelService"
id="mybatisUserModelService" cache="false" version="1.0.0"
timeout="5000" check="false" />
<dubbo:reference interface="cc.bima.scaffold.api.face.IMybatisDepartmentInfoService"
id="mybatisDepartmentService" cache="false" version="1.0.0"
timeout="5000" check="false" />统一配置参数说明:
cache="false": 禁用结果缓存。对于中间件操作类服务,每次调用都应该获取最新结果,因此禁用缓存version="1.0.0": 统一使用 1.0.0 版本,与 Provider 端保持一致timeout="5000": 统一设置 5 秒超时。对于 AI 聊天等耗时操作,可以在方法级别覆盖此配置check="false": 启动时不检查服务是否可用。由于 Consumer 启动时 Provider 可能还未完全启动,设置check="false"可以避免启动失败
6.4 QoS 配置:qos-enable 与 qos-port
6.4.1 QoS 简介
Dubbo QoS(Quality of Service)是 Dubbo 提供的在线运维管理工具。它通过 HTTP 协议暴露了一系列管理接口,允许运维人员在服务运行期间动态查看和修改服务配置。
6.4.2 配置说明
在本项目中,Consumer 端的 QoS 配置如下:
xml
<dubbo:application name="smart-scaffold-dubbo-consumer"
qos-enable="false" qos-port="22223" />qos-enable="false": 禁用 QoS 功能。在生产环境中,建议禁用 QoS,因为它可能暴露服务内部信息qos-port="22223": QoS 服务端口。如果启用了 QoS,可以通过http://localhost:22223访问管理接口
注意: 当 Consumer 和 Provider 部署在同一台机器上时,它们的 QoS 端口不能相同,否则会端口冲突。本项目将 Consumer 的 QoS 端口设置为 22223(Dubbo 默认端口为 22222),避免了与 Provider 的冲突。
6.4.3 QoS 常用命令
如果需要启用 QoS,以下是一些常用的管理命令:
bash
# 查看已注册的服务列表
curl http://localhost:22223/governance/services
# 查看服务提供者信息
curl http://localhost:22223/governance/providers
# 查看服务消费者信息
curl http://localhost:22223/governance/consumers
# 在线修改服务超时时间
curl -X POST "http://localhost:22223/config/services/cc.bima.scaffold.api.face.IRedisService?timeout=3000"6.5 Web 层架构(Controller/Filter/OAuth/Thymeleaf)
Consumer 端的 Web 层是整个系统与外部交互的入口,它将 Dubbo RPC 服务封装为 REST API 和 Web 页面。
6.5.1 Controller 层
Controller 层分为两类:REST API 控制器和页面控制器。
REST API 控制器(@RestController):
REST API 控制器以 JSON 格式返回数据,供前端 AJAX 调用。
java
@RestController
@RequestMapping("/api/redis")
public class RedisController {
@DubboReference
private IRedisService redisService;
@GetMapping("/test")
public ApiResult<?> testConnection() {
try {
return ApiResult.success(redisService.testConnection());
} catch (Exception e) {
return ApiResult.success("Failed: " + e.getMessage());
}
}
@PostMapping("/key")
public ApiResult<?> setKey(@RequestParam String key, @RequestParam String value) {
try {
return ApiResult.success(redisService.setKey(key, value));
} catch (Exception e) {
return ApiResult.success("Failed: " + e.getMessage());
}
}
@GetMapping("/key")
public ApiResult<?> getKey(@RequestParam String key) {
try {
return ApiResult.success(redisService.getKey(key));
} catch (Exception e) {
return ApiResult.success("Failed: " + e.getMessage());
}
}
@DeleteMapping("/key")
public ApiResult<?> deleteKey(@RequestParam String key) {
try {
return ApiResult.success(redisService.deleteKey(key));
} catch (Exception e) {
return ApiResult.success("Failed: " + e.getMessage());
}
}
}页面控制器(@Controller):
页面控制器使用 Thymeleaf 模板引擎渲染 HTML 页面。
java
@Controller
@RequestMapping("/")
public class FrontController {
@GetMapping({ "", "/", "/front/index" })
public String index(Model model,
@RequestParam(value = Constants.USER_ID_KEY, required = false) String userId,
@RequestParam(value = Constants.USER_NAME_KEY, required = false) String userName,
@RequestParam(value = "access_token", required = false) String accessToken,
@RequestParam(value = "refresh_token", required = false) String refreshToken) {
addUserInfoToModel(model, userId, userName, accessToken, refreshToken);
return "index";
}
@GetMapping("/front/redis")
public String redis(Model model, ...) {
addUserInfoToModel(model, userId, userName, accessToken, refreshToken);
return "redis/index";
}
@GetMapping("/front/redis/test")
public String redisTest(Model model, ...) {
addUserInfoToModel(model, userId, userName, accessToken, refreshToken);
return "redis/test";
}
// ... 其他页面路由
}6.5.2 Filter 层 -- OAuth 认证
OAuthFilter 是 Consumer 端的核心安全组件,它实现了基于 OAuth 2.0 的接口鉴权机制。
java
@WebFilter(filterName = "OAuthFilter", urlPatterns = "/*")
@Component
public class OAuthFilter implements Filter {
@Value("${bima.oauth2.client-id}")
private String clientId;
@Autowired
private OAuthService oAuthService;
// 免鉴权接口列表
private static String[] ignoreURI = {
"/layui-v2.13.5", "/layui-v2.13.5/**",
"/js", "/js/**",
"/bima-logo-large-dark.png", "/favicon.ico",
"/main.htm", "/login", "/callback", "/refresh-token"
};
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String uri = req.getServletPath();
// 1. 获取 Token
String accessToken = req.getHeader(Constants.ACCESS_TOKEN_KEY);
if (StringUtils.isBlank(accessToken)) {
accessToken = req.getParameter("access_token");
}
// 2. 免鉴权接口直接放行
for (String ignore : ignoreURI) {
if (uri.equals(ignore) ||
(ignore.endsWith("/**") &&
uri.startsWith(ignore.substring(0, ignore.length() - 3)))) {
chain.doFilter(request, response);
return;
}
}
// 3. Token 校验
if (StringUtils.isBlank(accessToken)) {
redirect(response); // 重定向到登录页
return;
}
Map<String, String> userInfoMap = oAuthService.checkToken(accessToken);
if (userInfoMap == null) {
redirect(response);
return;
}
String userId = userInfoMap.get("userId");
String userName = userInfoMap.get("userName");
if (StringUtils.isBlank(userId) || StringUtils.isBlank(userName)) {
redirect(response);
return;
}
// 4. 将用户信息包装到请求中
chain.doFilter(new TokenRequestWrapper(req, accessToken,
refreshToken, userId, userName), response);
}
}OAuth 认证流程:
浏览器请求
|
v
OAuthFilter.doFilter()
|
+-- 免鉴权接口? --是--> 直接放行
|
+-- 获取 access_token
| +-- Header 中获取
| +-- Request Parameter 中获取
|
+-- Token 为空? --是--> 重定向到登录页
|
+-- 调用 oAuthService.checkToken() 校验 Token
| +-- 校验成功 --> 包装用户信息,放行
| +-- 校验失败 --> 重定向到登录页
|
v
Controller 处理请求6.5.3 Thymeleaf 模板
Consumer 端使用 Thymeleaf 作为模板引擎,前端页面基于 LayUI 框架构建。
templates/
+-- index.html # 功能演示首页
+-- login/ # 登录相关页面
| +-- index.html
| +-- userinfo.html
+-- ai/ # AI 功能页面
| +-- index.html
| +-- chat.html
| +-- chat-stream.html
| +-- writing.html
+-- redis/ # Redis 功能页面
| +-- index.html
| +-- test.html
+-- elasticsearch/ # Elasticsearch 功能页面
| +-- index.html
| +-- test.html
+-- kafka/ # Kafka 功能页面
| +-- index.html
| +-- test.html
+-- mongo/ # MongoDB 功能页面
| +-- index.html
| +-- test.html
+-- mybatis/ # MyBatis 功能页面
| +-- index.html
| +-- user.html
| +-- department.html
+-- rabbitmq/ # RabbitMQ 功能页面
| +-- index.html
| +-- test.html
+-- rocketmq/ # RocketMQ 功能页面
+-- index.html
+-- test.html第七章 Provider/Consumer 独立 Docker 化
7.1 各自独立的 Dockerfile
Provider 和 Consumer 各自拥有独立的 Dockerfile,实现了完全的容器化隔离。
7.1.1 Provider Dockerfile
dockerfile
FROM maven:3.9.9-eclipse-temurin-17 AS builder
WORKDIR /app
COPY . .
RUN echo "=== 复制 provider jar ===" \
&& mkdir -p /app/build \
&& jar=$(ls /app/smart-scaffold-provider/target/*.jar | grep -v original | head -n 1) \
&& cp "$jar" /app/build/app.jar
FROM azul/zulu-openjdk:17
WORKDIR /docker
COPY --from=builder /app/build/app.jar app.jar
COPY ./smart-scaffold-provider/entrypoint.sh .
RUN chmod +x entrypoint.sh
ENTRYPOINT ["/docker/entrypoint.sh"]7.1.2 Consumer Dockerfile
dockerfile
FROM maven:3.9.9-eclipse-temurin-17 AS builder
WORKDIR /app
COPY . .
RUN echo "=== 复制 consumer jar ===" \
&& mkdir -p /app/build \
&& jar=$(ls /app/smart-scaffold-consumer/target/*.jar | grep -v original | head -n 1) \
&& cp "$jar" /app/build/app.jar
FROM azul/zulu-openjdk:17
WORKDIR /docker
COPY --from=builder /app/build/app.jar app.jar
COPY ./smart-scaffold-consumer/entrypoint.sh .
RUN chmod +x entrypoint.sh
ENTRYPOINT ["/docker/entrypoint.sh"]7.1.3 入口脚本
Provider 和 Consumer 共享相同的入口脚本逻辑:
bash
#!/bin/bash
# 启动 Spring Boot 应用
java -jar /docker/app.jar7.2 多阶段构建策略
两个 Dockerfile 都采用了多阶段构建(Multi-Stage Build)策略,这是 Docker 构建的最佳实践。
7.2.1 构建阶段分析
第一阶段(builder):
dockerfile
FROM maven:3.9.9-eclipse-temurin-17 AS builder
WORKDIR /app
COPY . .- 使用
maven:3.9.9-eclipse-temurin-17作为构建基础镜像,包含了 Maven 3.9.9 和 JDK 17 - 将整个项目源码复制到容器中
- 注意:实际的 Maven 编译在 Jenkins 中完成,这里只是复制已编译好的 JAR 文件
第二阶段(runtime):
dockerfile
FROM azul/zulu-openjdk:17
WORKDIR /docker
COPY --from=builder /app/build/app.jar app.jar- 使用
azul/zulu-openjdk:17作为运行时基础镜像,这是一个精简的 JDK 17 镜像 - 从 builder 阶段复制编译好的 JAR 文件
- 最终镜像只包含 JRE 和应用 JAR,体积更小
7.2.2 镜像体积对比
| 构建方式 | 镜像大小 | 说明 |
|---|---|---|
| 单阶段(maven 镜像) | ~800MB | 包含 Maven、JDK、源码 |
| 多阶段(jre 镜像) | ~250MB | 只包含 JRE 和 JAR |
多阶段构建将最终镜像体积减少了约 70%,显著降低了镜像传输和部署时间。
7.3 网络配置与端口分配
7.3.1 端口分配
| 服务 | 端口 | 说明 |
|---|---|---|
| Provider | 8081 | Provider Web 端口(HTTP) |
| Provider | 20880 | Dubbo RPC 端口(默认) |
| Consumer | 8080 | Consumer Web 端口(HTTP) |
| Zookeeper | 2181 | Zookeeper 客户端端口 |
7.3.2 Docker 容器启动命令
bash
# 启动 Provider
docker run -d \
--name smart-scaffold-provider-dubbo \
-p 8081:8081 \
--restart=always \
smart-scaffold-provider-dubbo:latest
# 启动 Consumer
docker run -d \
--name smart-scaffold-consumer-dubbo \
-p 8080:8080 \
--restart=always \
smart-scaffold-consumer-dubbo:latest7.3.3 网络架构
Internet
|
v
+------------------------+
| Docker Host |
| |
| +----------+ |
| | Consumer | | Port: 8080
| | :8080 |---------> 对外暴露
| +----+-----+ |
| | |
| | RPC |
| v |
| +----------+ |
| | Provider | | Port: 8081
| | :8081 |---------> 对外暴露(可选)
| | :20880 | | Dubbo RPC 端口
| +----------+ |
| |
| +----------+ |
| |Zookeeper | | Port: 2181
| | :2181 | |
| +----------+ |
+------------------------+7.4 Jenkins CI/CD 集成方案
本项目提供了完整的 Jenkins CI/CD 集成方案,包括构建和部署两个阶段。
7.4.1 构建阶段(Build Steps)
bash
#!/bin/bash
set -euo pipefail
# 1. 环境变量
unset JAVA_HOME
unset PATH
export JAVA_HOME=/usr/local/jdk17
export PATH=$JAVA_HOME/bin:/usr/bin:/bin:/usr/sbin:/sbin
export M2_HOME=/usr/local/maven
export PATH=$M2_HOME/bin:$PATH
# 2. Maven 阿里源
mkdir -p ~/.m2
tee ~/.m2/settings.xml <<-'EOF'
<settings>
<localRepository>/tmp/m2repo</localRepository>
<mirrors>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>central</mirrorOf>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
</settings>
EOF
# 3. 根目录编译
echo "===== 根目录 Maven 构建 ======"
mvn clean package -DskipTests -T 1C
# 4. 构建 provider 镜像
echo "===== 构建 provider 镜像 ======"
docker build \
-t smart-scaffold-provider-dubbo:latest \
-f ./smart-scaffold-provider/Dockerfile \
.
# 5. 构建 consumer 镜像
echo "===== 构建 consumer 镜像 ======"
docker build \
-t smart-scaffold-consumer-dubbo:latest \
-f ./smart-scaffold-consumer/Dockerfile \
.
# 6. 查看结果
echo "===== 构建完成 ======"
docker images | grep smart-scaffold7.4.2 部署阶段(Send build artifacts over SSH)
bash
#!/bin/bash
set -euo pipefail
DOCKER_CMD="/usr/local/bin/docker"
# 1. 清理旧容器
$DOCKER_CMD stop smart-scaffold-provider-dubbo 2>/dev/null || true
$DOCKER_CMD rm smart-scaffold-provider-dubbo 2>/dev/null || true
$DOCKER_CMD stop smart-scaffold-consumer-dubbo 2>/dev/null || true
$DOCKER_CMD rm smart-scaffold-consumer-dubbo 2>/dev/null || true
# 2. 启动 Provider
$DOCKER_CMD run -d \
--name smart-scaffold-provider-dubbo \
-p 8081:8081 \
--restart=always \
smart-scaffold-provider-dubbo:latest
# 3. 启动 Consumer
$DOCKER_CMD run -d \
--name smart-scaffold-consumer-dubbo \
-p 8080:8080 \
--restart=always \
smart-scaffold-consumer-dubbo:latest
# 4. 验证状态
$DOCKER_CMD ps | grep smart-scaffold || {
echo "启动失败!"
echo "Provider日志: $DOCKER_CMD logs smart-scaffold-provider-dubbo"
echo "Consumer日志: $DOCKER_CMD logs smart-scaffold-consumer-dubbo"
}
echo "=== 部署完成 ==="第八章 Dubbo 3.x 新特性深度剖析
8.1 服务发现机制升级
Dubbo 3.x 在服务发现机制上进行了重大升级,从接口级服务发现升级为应用级服务发现。
8.1.1 接口级服务发现(Dubbo 2.x)
在 Dubbo 2.x 中,每个服务接口都会在注册中心注册一条信息:
/dubbo/cc.bima.scaffold.api.face.IRedisService/providers/
dubbo://192.168.1.100:20880/...?version=1.0.0
/dubbo/cc.bima.scaffold.api.face.IElasticsearchService/providers/
dubbo://192.168.1.100:20880/...?version=1.0.0
/dubbo/cc.bima.scaffold.api.face.IKafkaService/providers/
dubbo://192.168.1.100:20880/...?version=1.0.0
... (12 个接口 = 12 条注册信息)问题: 当一个应用暴露 100 个接口,部署 50 个实例时,注册中心需要存储 5000 条信息。每次实例上下线,需要推送 100 次变更通知。
8.1.2 应用级服务发现(Dubbo 3.x)
在 Dubbo 3.x 中,一个应用只注册一条信息:
/dubbo/metadata/smart-scaffold-dubbo-provider/
revision: xxxxxx
instances:
- 192.168.1.100:20880
- 192.168.1.101:20880接口级别的元数据通过独立的元数据中心管理,Consumer 在首次引用服务时从元数据中心获取接口定义,之后缓存在本地。
优势:
- 注册中心数据量降低 90% 以上
- 推送变更通知的频率大幅降低
- 与 Kubernetes Service 模型对齐,便于云原生部署
8.1.3 本项目中的体现
在本项目的 dubbo-provider.xml 配置中:
xml
<dubbo:registry protocol="zookeeper"
address="zookeeper://192.168.1.30:2181"
simplified="true" />simplified="true" 参数启用了简化注册模式,这是 Dubbo 3.x 应用级服务发现的配置方式。启用后,Provider 在 Zookeeper 中只注册一条应用实例信息,而不是 12 条接口信息。
8.2 应用级服务发现
8.2.1 工作原理
应用级服务发现的核心思想是将"服务发现"和"接口元数据"分离:
+------------------------------------------------------+
| 注册中心 |
| |
| /dubbo/metadata/smart-scaffold-dubbo-provider/ |
| +-- revision: "abc123" |
| +-- instances: |
| +-- 192.168.1.100:20880 |
| +-- 192.168.1.101:20880 |
+------------------------------------------------------+
+------------------------------------------------------+
| 元数据中心 |
| |
| /dubbo/metadata/smart-scaffold-dubbo-provider/ |
| +-- revision: "abc123" |
| +-- IRedisService (方法签名、参数类型等) |
| +-- IElasticsearchService |
| +-- IKafkaService |
| +-- ... (12 个接口的元数据) |
+------------------------------------------------------+8.2.2 Consumer 端的服务引用流程
1. Consumer 启动,读取 dubbo-consumer.xml 配置
|
v
2. 从注册中心获取 Provider 的实例列表
(192.168.1.100:20880, 192.168.1.101:20880)
|
v
3. 从元数据中心获取接口元数据
(IRedisService 的方法签名、参数类型等)
|
v
4. 在本地创建服务代理(Invoker)
|
v
5. 通过负载均衡选择一个 Provider 实例
|
v
6. 发起 RPC 调用8.3 流量治理
Dubbo 3.x 内置了丰富的流量治理能力,可以在不修改代码的情况下对服务流量进行精细化管理。
8.3.1 流量治理能力概览
| 治理能力 | 说明 | 配置方式 |
|---|---|---|
| 超时控制 | 设置服务调用的最大等待时间 | timeout 属性 |
| 重试策略 | 设置失败后的重试次数 | retries 属性 |
| 负载均衡 | 选择请求分发的策略 | loadbalance 属性 |
| 路由规则 | 根据条件将请求路由到特定实例 | 路由规则配置 |
| 限流 | 限制服务的最大并发数 | actives 属性 |
| 降级 | 当服务不可用时返回默认值 | 降级规则配置 |
| 灰度发布 | 按比例将流量导向新版本 | 标签路由 |
8.3.2 本项目中的流量治理实践
在本项目中,通过 XML 配置实现了基础的流量治理:
超时控制:
xml
<dubbo:reference interface="cc.bima.scaffold.api.face.IRedisService"
timeout="5000" />启动检查关闭:
xml
<dubbo:consumer check="false" />结果缓存关闭:
xml
<dubbo:reference interface="cc.bima.scaffold.api.face.IRedisService"
cache="false" />8.4 路由规则
Dubbo 3.x 支持多种路由规则,可以根据请求参数、来源 IP、标签等信息将请求路由到特定的服务实例。
8.4.1 条件路由
条件路由是最常用的路由规则,它根据请求参数和实例属性进行匹配。
yaml
# 条件路由配置示例
# 将 userId 为 9527 的用户请求路由到标记为 "vip" 的实例
conditions:
- => host != 192.168.1.100 # 排除特定实例
- userId = 9527 => tag = vip # 特定用户路由到 VIP 实例8.4.2 标签路由
标签路由是 Dubbo 3.x 引入的新特性,它通过为服务实例打标签来实现流量分组。
yaml
# Provider 端打标签
dubbo.consumer.tag = gray
# 路由规则:带有 "gray" 标签的请求只路由到标记为 "gray" 的实例8.4.3 脚本路由
脚本路由允许使用 JavaScript 脚本来定义复杂的路由逻辑。
javascript
// 脚本路由示例
function route(invokers, invocation) {
var userId = invocation.getAttachment("userId");
if (userId == "9527") {
return invokers.filter(function(invoker) {
return invoker.getUrl().getParameter("tag") == "vip";
});
}
return invokers;
}8.5 负载均衡策略
Dubbo 3.x 内置了多种负载均衡策略,可以根据业务场景选择合适的策略。
8.5.1 负载均衡策略对比
| 策略 | 说明 | 适用场景 |
|---|---|---|
random(默认) | 随机选择一个实例 | 通用场景,性能最优 |
roundrobin | 轮询选择实例 | 请求量均匀的场景 |
consistenthash | 一致性哈希 | 需要会话保持的场景 |
leastactive | 最少活跃调用数 | 请求处理时间差异大的场景 |
shortestresponse | 最短响应时间 | 对响应时间敏感的场景 |
p2c | Power of Two Choices | 高并发场景 |
8.5.2 配置方式
全局配置:
xml
<dubbo:consumer loadbalance="random" />服务级别配置:
xml
<dubbo:reference interface="cc.bima.scaffold.api.face.IRedisService"
loadbalance="roundrobin" />方法级别配置:
xml
<dubbo:reference interface="cc.bima.scaffold.api.face.IRedisService">
<dubbo:method name="getKey" loadbalance="leastactive" />
</dubbo:reference>8.5.3 一致性哈希策略详解
一致性哈希策略在需要"会话保持"的场景中非常有用。例如,在缓存场景中,同一个 Key 的请求总是路由到同一个 Provider 实例,可以充分利用本地缓存。
请求 Key: "user:1001"
|
v
一致性哈希环计算
|
v
路由到 Provider A(192.168.1.100:20880)
请求 Key: "user:1001"(第二次请求)
|
v
一致性哈希环计算(相同结果)
|
v
仍然路由到 Provider A(充分利用本地缓存)8.5.4 P2C 策略详解
P2C(Power of Two Choices)是 Dubbo 3.x 引入的新负载均衡策略。它从所有可用实例中随机选择两个,然后选择其中活跃调用数较少的一个。
可用实例: [A(5), B(3), C(8), D(2), E(6)]
|
v
随机选择两个: C(8) 和 D(2)
|
v
选择活跃数较少的: D(2)
|
v
请求路由到 DP2C 策略在保持随机策略高性能的同时,有效避免了请求集中在负载较高的实例上。
第九章 实战总结与最佳实践
9.1 架构设计总结
9.1.1 整体架构回顾
smart-scaffold-dubbo 项目展示了一个完整的 Dubbo 分布式服务架构实践,其核心架构可以总结为:
+------------------------------------------------------------------+
| 客户端(浏览器) |
| HTTP/HTTPS |
+--------------------------------+---------------------------------+
|
v
+------------------------------------------------------------------+
| Consumer(BFF 层) |
| +----------+ +----------+ +----------+ +----------+ |
| |Controller| | Filter | | OAuth | |Thymeleaf | |
| | (REST) | | (认证) | | (CAS) | | (模板) | |
| +----+-----+ +----------+ +----------+ +----------+ |
| | |
| | @DubboReference |
| | Dubbo RPC |
+-------+---------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| Provider(服务层) |
| +----------+ +----------+ +----------+ +----------+ |
| | Redis | | ES | | Kafka | | Mongo | |
| | Service | | Service | | Service | | Service | |
| +----------+ +----------+ +----------+ +----------+ |
| +----------+ +----------+ +----------+ +----------+ |
| | RabbitMQ | | RocketMQ | | MyBatis | | AI | |
| | Service | | Service | | Service | | Service | |
| +----------+ +----------+ +----------+ +----------+ |
| | |
| | @DubboService |
| | 注册到 Zookeeper |
+-------+---------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| Zookeeper(注册/配置/元数据中心) |
| +----------+ +----------+ +----------+ |
| | Registry | | Config | | Metadata | |
| | (注册) | | (配置) | | (元数据) | |
| +----------+ +----------+ +----------+ |
+------------------------------------------------------------------+9.1.2 关键设计决策
| 设计决策 | 选择 | 理由 |
|---|---|---|
| RPC 框架 | Dubbo 3.2.0 | 高性能、精细化治理 |
| 注册中心 | Zookeeper 3.9.1 | 成熟稳定、三中心合一 |
| 序列化 | Hessian2 | Dubbo 默认、性能优良 |
| 通信协议 | Dubbo 协议 | 二进制、长连接、高性能 |
| Java 版本 | Java 17 | LTS 版本、性能优化 |
| Spring Boot | 3.5.12 | 最新稳定版、自动配置 |
| 容器化 | Docker 多阶段构建 | 镜像精简、部署高效 |
| 前端框架 | LayUI + Thymeleaf | 轻量、服务端渲染 |
9.2 生产环境注意事项
9.2.1 高可用部署
Zookeeper 集群:
生产环境中,Zookeeper 必须以集群模式部署(至少 3 个节点):
zookeeper://zk1:2181,zk2:2181,zk3:2181Provider 多实例:
Provider 应该部署多个实例,通过 Dubbo 的负载均衡实现高可用:
bash
# 实例 1
docker run -d --name provider-1 -p 8081:8081 provider:latest
# 实例 2
docker run -d --name provider-2 -p 8082:8081 provider:latest
# 实例 3
docker run -d --name provider-3 -p 8083:8081 provider:latestConsumer 多实例:
Consumer 也应该部署多个实例,通过 Nginx 或负载均衡器进行流量分发:
nginx
upstream consumer {
server consumer-1:8080;
server consumer-2:8080;
server consumer-3:8080;
}9.2.2 配置安全
Zookeeper 认证:
在生产环境中,应该为 Zookeeper 配置 SASL 认证:
xml
<dubbo:registry protocol="zookeeper"
address="zookeeper://username:password@zk1:2181" />敏感信息加密:
数据库密码、API Key 等敏感信息应该使用加密存储,而不是明文写在配置文件中。
9.2.3 日志管理
生产环境中,应该使用统一的日志收集方案(如 ELK Stack),并配置合理的日志级别:
yaml
# application.yml
logging:
level:
root: INFO
cc.bima.scaffold: INFO
org.apache.dubbo: WARN9.3 性能调优建议
9.3.1 Dubbo 性能优化
| 优化项 | 配置 | 说明 |
|---|---|---|
| 连接数 | connections=5 | 每个 Consumer 与每个 Provider 建立的连接数 |
| 序列化 | 使用 Kryo 或 Protobuf | 比 Hessian2 性能更高 |
| 线程池 | threads=200 | Dubbo 业务线程池大小 |
| IO 线程 | iothreads=CPU+1 | Dubbo 网络 IO 线程数 |
| 超时时间 | 根据业务合理设置 | 避免设置过长导致线程阻塞 |
9.3.2 JVM 优化
bash
java -jar app.jar \
-Xms2g -Xmx2g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/docker/logs/9.3.3 数据库优化
- 配置合理的连接池大小(Druid 默认 8 个连接)
- 为常用查询字段添加索引
- 使用读写分离降低主库压力
- 开启 MyBatis 二级缓存(谨慎使用)
9.4 常见问题与解决方案
9.4.1 服务注册失败
现象: Provider 启动后,Consumer 无法发现服务。
排查步骤:
- 检查 Zookeeper 是否正常运行
- 检查网络连接是否正常(
telnet zk-host 2181) - 检查 Dubbo 配置中的 Zookeeper 地址是否正确
- 查看 Provider 启动日志中是否有注册失败的错误信息
9.4.2 服务调用超时
现象: Consumer 调用 Provider 服务时出现超时异常。
排查步骤:
- 检查
timeout配置是否合理 - 检查 Provider 端的业务逻辑是否有性能瓶颈
- 检查网络延迟是否过高
- 检查 Provider 端的线程池是否已满
9.4.3 版本不匹配
现象: Consumer 引用服务时出现 No provider available 异常。
排查步骤:
- 检查 Consumer 和 Provider 的
version配置是否一致 - 检查 Consumer 和 Provider 的
group配置是否一致 - 检查 API 模块的版本是否一致
9.4.4 序列化异常
现象: RPC 调用时出现序列化/反序列化异常。
解决方案:
- 确保传输的对象实现了
Serializable接口 - 确保传输的对象有一个无参构造函数
- 确保 Provider 和 Consumer 的 API 模块版本一致
- 避免在传输对象中使用不可序列化的字段
9.4.5 Zookeeper 连接断开
现象: 服务运行一段时间后,Consumer 无法调用 Provider 的服务。
排查步骤:
- 检查 Zookeeper 的
session-timeout配置(默认 60 秒) - 检查网络是否稳定
- 检查 Zookeeper 服务器的负载情况
- 检查 Curator 的重连策略配置
附录
A. 技术栈版本清单
| 技术 | 版本 | 说明 |
|---|---|---|
| Java | 17 | LTS 版本 |
| Spring Boot | 3.5.12 | Web 应用框架 |
| Apache Dubbo | 3.2.0 | RPC 框架 |
| Zookeeper | 3.9.1 | 注册/配置/元数据中心 |
| Curator | 5.1.0 | Zookeeper 客户端 |
| MyBatis | 3.5.14 | ORM 框架 |
| MySQL | 8.0+ | 关系型数据库 |
| Druid | 1.2.22 | 数据库连接池 |
| Redis | 5.0+ | 缓存中间件 |
| Elasticsearch | 7.0+ | 搜索引擎 |
| Kafka | 2.0+ | 消息队列 |
| MongoDB | 4.0+ | 文档数据库 |
| RabbitMQ | 3.0+ | 消息队列 |
| RocketMQ | 4.0+ | 消息队列 |
| LayUI | 2.13.5 | 前端 UI 框架 |
| Thymeleaf | - | 模板引擎 |
| Lombok | - | 代码简化工具 |
| Docker | - | 容器化部署 |
B. 项目端口清单
| 端口 | 服务 | 协议 | 说明 |
|---|---|---|---|
| 2181 | Zookeeper | TCP | 注册中心客户端端口 |
| 20880 | Provider | TCP | Dubbo RPC 默认端口 |
| 8081 | Provider | HTTP | Provider Web 端口 |
| 8080 | Consumer | HTTP | Consumer Web 端口 |
| 22222 | Dubbo QoS | HTTP | QoS 默认端口(已禁用) |
| 22223 | Consumer QoS | HTTP | Consumer QoS 端口(已禁用) |
C. Maven 构建命令速查
bash
# 编译项目
mvn clean compile
# 打包项目(跳过测试)
mvn clean package -DskipTests
# 并行构建
mvn clean package -DskipTests -T 1C
# 运行测试
mvn test
# 启动 Provider
cd smart-scaffold-provider && mvn spring-boot:run
# 启动 Consumer
cd smart-scaffold-consumer && mvn spring-boot:run
# 构建 Docker 镜像
docker build -t smart-scaffold-provider-dubbo:latest \
-f ./smart-scaffold-provider/Dockerfile .
docker build -t smart-scaffold-consumer-dubbo:latest \
-f ./smart-scaffold-consumer/Dockerfile .D. 参考资源
版权声明: 本文为必码(bima.cc)原创技术文章,仅供学习交流。
本文内容基于实际项目源码解析整理,代码示例均为教学简化版本,仅供学习参考。
文档内容提取自项目源码与配置文件,如需获取完整项目代码,请访问 bima.cc。