Skip to content

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 RPCHTTP 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 通信模型对比

对比维度DubboSpring Cloud
通信协议二进制 RPC(Dubbo 协议)HTTP REST(基于 Spring MVC)
序列化Hessian2/Kryo/ProtobufJSON/Jackson
性能高(二进制协议 + 长连接)中(HTTP + 短连接)
跨语言支持(需要 SDK)天然支持(HTTP 协议)
学习曲线中等较低
生态丰富度RPC 相关生态丰富微服务全家桶生态丰富

1.4.3 适用场景对比

适合选择 Dubbo 的场景:

  • 内部微服务之间调用频繁,对性能要求高
  • 团队以 Java 技术栈为主,跨语言需求较少
  • 需要精细化的流量治理能力
  • 已有 Dubbo 基础设施和运维经验
  • 服务接口相对稳定,变更频率较低

适合选择 Spring Cloud 的场景:

  • 需要完整的微服务治理方案
  • 团队技术栈多元化,有跨语言需求
  • 对 HTTP 协议有强依赖(如需要通过 API 网关)
  • 希望利用 Spring 生态的丰富组件
  • 项目处于快速迭代阶段,接口变更频繁

1.4.4 本项目的选型考量

在 smart-scaffold-dubbo 项目中,我们选择 Dubbo 作为 RPC 框架,主要基于以下考量:

  1. 性能需求: 项目集成了七大中间件,Consumer 需要频繁调用 Provider 的服务,RPC 的高性能特性能够有效降低通信延迟
  2. 接口稳定性: 中间件服务的接口定义相对稳定,不会频繁变更,适合使用 RPC
  3. 团队技术栈: 项目完全基于 Java 技术栈,不需要跨语言支持
  4. 精细治理: 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 模块的核心职责包括:

  1. 定义 Dubbo 服务接口: 所有跨服务调用的接口都在此定义
  2. 定义数据传输对象(DTO): 服务间传递的数据结构
  3. 定义实体类(Entity): 与数据库表对应的数据结构
  4. 定义持久化对象(PO): 数据库操作的中间对象
  5. 定义枚举类(Enum): 系统中使用的枚举常量
  6. 定义常量类(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 模块的核心职责包括:

  1. 实现 Dubbo 服务接口: 提供 12 个服务的具体实现
  2. 集成中间件: 集成 Redis、Elasticsearch、Kafka、MongoDB、RabbitMQ、RocketMQ
  3. 数据访问: 通过 MyBatis 访问 MySQL 数据库
  4. AI 服务: 集成 Ollama 和 OpenAI API
  5. 服务导出: 将实现的服务通过 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.java

2.4 smart-scaffold-consumer:服务消费者

Consumer 模块是整个项目的"接入层",它引用 Provider 暴露的 Dubbo 服务,并通过 REST API 和 Web 页面提供给前端使用。

2.4.1 模块定位与职责

Consumer 模块的核心职责包括:

  1. 引用 Dubbo 服务: 通过 @DubboReference 或 XML 配置引用 Provider 的服务
  2. REST API 封装: 将 RPC 服务封装为 REST API 供前端调用
  3. Web 页面渲染: 通过 Thymeleaf 模板引擎渲染前端页面
  4. 用户认证: 通过 OAuth 2.0 Filter 实现用户认证
  5. 请求过滤: 通过 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
                 httpclient5

2.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 1C

2.5.3 依赖隔离原则

本项目的依赖设计遵循严格的隔离原则:

  • API 模块: 只依赖基础工具库,不依赖任何框架或中间件
  • Provider 模块: 依赖所有中间件客户端,但不依赖 Web 层框架(Thymeleaf)
  • Consumer 模块: 依赖 Web 层框架和 Dubbo,但不直接依赖中间件客户端

这种隔离设计的好处是:

  1. 减少依赖冲突: 各模块的依赖范围清晰,不易产生版本冲突
  2. 独立部署: Provider 和 Consumer 可以独立打包、独立部署
  3. 按需扩展: 如果需要添加新的中间件,只需要在 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 个):

接口功能方法数
IRedisServiceRedis 键值操作12
IElasticsearchServiceElasticsearch 索引和文档操作6
IKafkaServiceKafka 消息发送和接收6
IMongoServiceMongoDB 文档操作5
IRabbitmqServiceRabbitMQ 消息和队列管理8
IRocketmqServiceRocketMQ 消息和主题管理9

数据服务类(2 个):

接口功能方法数
IMybatisUserModelService用户模型 CRUD8
IMybatisDepartmentInfoService部门信息 CRUD7

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 端处理
  • batchSetbatchGet 方法使用字符串格式传递批量数据(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 模块严格遵循"契约优先"的设计原则:

  1. 先定义接口,后实现: 所有服务接口在 API 模块中定义完成后,Provider 和 Consumer 才开始各自的实现和引用
  2. 接口与实现分离: API 模块只包含接口定义,不包含任何实现代码
  3. 版本化管理: 通过 Maven 版本号管理 API 模块的变更

这种设计的好处是:

  • Provider 和 Consumer 可以并行开发
  • 接口变更时,编译期就能发现不兼容问题
  • API 模块可以独立发布,供其他项目引用

3.4.2 面向字符串的接口设计

本项目中的中间件服务接口(如 IRedisServiceIElasticsearchService)大量使用了 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 的配置加载遵循以下优先级(从高到低):

  1. JVM -D 参数
  2. 外部配置文件(application.yml / application.properties)
  3. Dubbo XML 配置(dubbo-provider.xml / dubbo-consumer.xml)
  4. 配置中心(Zookeeper Config Center)
  5. 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":指定注册中心协议为 Zookeeper
  • address="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>

关键配置参数解析:

参数说明
interfacecc.bima.scaffold.api.face.IXxxService服务接口的全限定名
idxxxServiceSpring 容器中的 Bean 名称
cachefalse禁用结果缓存,确保每次调用都获取最新数据
version1.0.0服务版本号,必须与 Provider 端一致
timeout5000调用超时时间(毫秒),5 秒
checkfalse启动时不检查服务是否可用,避免循环依赖

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:2181

4.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.03.6.x - 3.9.x8+
5.5.03.7.x - 3.9.x8+
5.7.03.8.x - 3.9.x11+

本项目选择 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 提供的注解,它的核心作用包括:

  1. 启用 Dubbo 注解扫描: 扫描 @DubboService 注解,将标注了该注解的类注册为 Dubbo 服务
  2. 启用 Dubbo 配置: 加载 Dubbo 相关的配置文件(如 dubbo-provider.xml)
  3. 初始化 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 模块中的共享类(如 ApiResultConstants 等)。

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 注解支持以下常用属性:

属性类型默认值说明
versionString"0.0.0"服务版本号
groupString""服务分组
interfaceClassClass<?>接口类指定服务接口
timeoutint1000调用超时时间(毫秒)
retriesint2失败重试次数
loadbalanceString"random"负载均衡策略
activesint0最大并发调用数
asyncbooleanfalse是否异步执行
validationString""参数校验方式

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 的 Provider

5.5 超时/重试配置(timeout="5000")

5.5.1 超时配置

超时时间是 RPC 调用中最重要的配置之一。在本项目中,所有服务的超时时间统一设置为 5000 毫秒(5 秒)。

Consumer 端配置:

xml
<dubbo:reference interface="cc.bima.scaffold.api.face.IRedisService"
    id="redisService" timeout="5000" />

超时配置的优先级:

  1. 方法级配置 > 接口级配置 > 全局配置 > 默认值(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 类型、实体类型和查询类型
  • 模板方法模式: handleQueryParamhandleQueryResultcheckSaveInputcheckRemove 是模板方法,子类可以按需重写
  • 统一分页: 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 中统一声明所有服务引用(配置 versiontimeoutcache 等参数),在 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 自动配置类,这是因为:

  1. Consumer 不直接连接中间件: 所有中间件操作都通过 RPC 调用 Provider 完成
  2. 避免启动失败: 如果不禁用这些自动配置,Spring Boot 会在启动时尝试连接中间件(如 Redis、MongoDB),如果连接不上就会启动失败
  3. 减少资源消耗: 不需要创建中间件的连接池和客户端 Bean

排除的自动配置类说明:

排除的自动配置原因
DataSourceAutoConfigurationConsumer 不直接连接数据库
MongoAutoConfigurationConsumer 不直接连接 MongoDB
RedisAutoConfigurationConsumer 不直接连接 Redis
KafkaAutoConfigurationConsumer 不直接连接 Kafka
RabbitAutoConfigurationConsumer 不直接连接 RabbitMQ
ElasticsearchDataAutoConfigurationConsumer 不直接连接 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.jar

7.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 端口分配

服务端口说明
Provider8081Provider Web 端口(HTTP)
Provider20880Dubbo RPC 端口(默认)
Consumer8080Consumer Web 端口(HTTP)
Zookeeper2181Zookeeper 客户端端口

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:latest

7.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-scaffold

7.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最短响应时间对响应时间敏感的场景
p2cPower 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
请求路由到 D

P2C 策略在保持随机策略高性能的同时,有效避免了请求集中在负载较高的实例上。


第九章 实战总结与最佳实践

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成熟稳定、三中心合一
序列化Hessian2Dubbo 默认、性能优良
通信协议Dubbo 协议二进制、长连接、高性能
Java 版本Java 17LTS 版本、性能优化
Spring Boot3.5.12最新稳定版、自动配置
容器化Docker 多阶段构建镜像精简、部署高效
前端框架LayUI + Thymeleaf轻量、服务端渲染

9.2 生产环境注意事项

9.2.1 高可用部署

Zookeeper 集群:

生产环境中,Zookeeper 必须以集群模式部署(至少 3 个节点):

zookeeper://zk1:2181,zk2:2181,zk3:2181

Provider 多实例:

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:latest

Consumer 多实例:

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: WARN

9.3 性能调优建议

9.3.1 Dubbo 性能优化

优化项配置说明
连接数connections=5每个 Consumer 与每个 Provider 建立的连接数
序列化使用 Kryo 或 Protobuf比 Hessian2 性能更高
线程池threads=200Dubbo 业务线程池大小
IO 线程iothreads=CPU+1Dubbo 网络 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 无法发现服务。

排查步骤:

  1. 检查 Zookeeper 是否正常运行
  2. 检查网络连接是否正常(telnet zk-host 2181
  3. 检查 Dubbo 配置中的 Zookeeper 地址是否正确
  4. 查看 Provider 启动日志中是否有注册失败的错误信息

9.4.2 服务调用超时

现象: Consumer 调用 Provider 服务时出现超时异常。

排查步骤:

  1. 检查 timeout 配置是否合理
  2. 检查 Provider 端的业务逻辑是否有性能瓶颈
  3. 检查网络延迟是否过高
  4. 检查 Provider 端的线程池是否已满

9.4.3 版本不匹配

现象: Consumer 引用服务时出现 No provider available 异常。

排查步骤:

  1. 检查 Consumer 和 Provider 的 version 配置是否一致
  2. 检查 Consumer 和 Provider 的 group 配置是否一致
  3. 检查 API 模块的版本是否一致

9.4.4 序列化异常

现象: RPC 调用时出现序列化/反序列化异常。

解决方案:

  1. 确保传输的对象实现了 Serializable 接口
  2. 确保传输的对象有一个无参构造函数
  3. 确保 Provider 和 Consumer 的 API 模块版本一致
  4. 避免在传输对象中使用不可序列化的字段

9.4.5 Zookeeper 连接断开

现象: 服务运行一段时间后,Consumer 无法调用 Provider 的服务。

排查步骤:

  1. 检查 Zookeeper 的 session-timeout 配置(默认 60 秒)
  2. 检查网络是否稳定
  3. 检查 Zookeeper 服务器的负载情况
  4. 检查 Curator 的重连策略配置

附录

A. 技术栈版本清单

技术版本说明
Java17LTS 版本
Spring Boot3.5.12Web 应用框架
Apache Dubbo3.2.0RPC 框架
Zookeeper3.9.1注册/配置/元数据中心
Curator5.1.0Zookeeper 客户端
MyBatis3.5.14ORM 框架
MySQL8.0+关系型数据库
Druid1.2.22数据库连接池
Redis5.0+缓存中间件
Elasticsearch7.0+搜索引擎
Kafka2.0+消息队列
MongoDB4.0+文档数据库
RabbitMQ3.0+消息队列
RocketMQ4.0+消息队列
LayUI2.13.5前端 UI 框架
Thymeleaf-模板引擎
Lombok-代码简化工具
Docker-容器化部署

B. 项目端口清单

端口服务协议说明
2181ZookeeperTCP注册中心客户端端口
20880ProviderTCPDubbo RPC 默认端口
8081ProviderHTTPProvider Web 端口
8080ConsumerHTTPConsumer Web 端口
22222Dubbo QoSHTTPQoS 默认端口(已禁用)
22223Consumer QoSHTTPConsumer 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