Skip to content

Latest commit

 

History

History
258 lines (203 loc) · 21.5 KB

File metadata and controls

258 lines (203 loc) · 21.5 KB

cloud-platform 架构与技术设计

本文档聚焦技术栈、系统架构、核心链路与组件设计原理。 构建、部署、运行、压测、运行时操作等运维 / 操作类说明DEPLOYMENT.md / OPERATIONS.md;可视化部署入口见 WEB_CONSOLE.md

0. 控制台作为控制面

Web 控制台(deploy/web-console,见 WEB_CONSOLE.md)是整套平台的可视化控制面:它不实现部署逻辑,只通过 Web UI 触发 start-k3d.sh / run-loadtest.sh / kubectl 等脚本并以 SSE 流式回传输出。设计原则:控制逻辑只在 .sh 脚本脚本可脱离控制台独立运行后端只做薄封装(exec + 转发 + 只读状态聚合)前端只做展示与触发。这样保证控制台与命令行两条路径行为一致、可互相替代。

1. 技术栈

能力 组件
网关 Spring Cloud Gateway
注册发现 / 配置中心 Nacos
隐式接口调用 OpenFeign + LoadBalancer + Resilience4j(熔断降级)
网关防护 Spring Cloud CircuitBreaker(Resilience4j) 路由级熔断 + 基于 Redis 的 RequestRateLimiter 限流
缓存 Redis(common-redis 统一封装,JSON 序列化模板 + RedisService)
消息 Spring Kafka(common-kafka 统一封装)
链路追踪 Micrometer Tracing(Brave 桥接)+ Zipkin 上报(common-tracing 统一封装)
SQL 数据访问 MyBatis-Plus + MySQL(分页、逻辑删除、字段自动填充)
定时任务 Spring Scheduling(cloud-job 独立服务)
部署 Docker + Kubernetes(探针、ConfigMap/Secret)

2. 模块结构

cloud-platform
├── cloud-common                 公共库
│   ├── common-core              统一返回 Result / 业务异常
│   ├── common-web               全局异常处理(自动装配)
│   ├── common-kafka             Kafka 生产者封装(自动装配)
│   ├── common-mybatis           MyBatis-Plus 插件/填充(自动装配)
│   ├── common-redis             Redis 访问封装(JSON RedisTemplate + RedisService,自动装配)
│   └── common-tracing           链路追踪封装(Micrometer Tracing + Brave + Zipkin,不绑 servlet/webflux)
├── cloud-api                    Feign 接口契约层
│   ├── user-api                 UserClient + UserDTO + 降级
│   └── order-api                OrderClient + OrderDTO + OrderCreatedEvent + 降级
├── cloud-gateway   :8080        网关(路由 /api/user/**、/api/order/**;路由熔断 + 限流)
├── cloud-service
│   ├── user-service  :8081      用户服务(MySQL + Redis 缓存 + 消费 Kafka)
│   ├── order-service :8082      订单服务(MySQL + Feign 调用用户 + 发送 Kafka)
│   └── consumer-service :8083   纯消费端(订阅 Kafka 主/死信 Topic,内存累计统计,无数据库)
├── build-images.sh             一键打包 + 构建 5 个业务镜像(供镜像模式 / K8s)
├── cloud-job       :8084        定时任务服务(@Scheduled + Feign)
├── deploy
│   ├── docker/
│   │   ├── Dockerfile           通用镜像(ARG JAR_FILE)
│   │   ├── middleware/          start-middleware.sh + docker-compose.yml(MySQL/Kafka/Nacos/Redis/Zipkin)
│   │   ├── services/            start-services.sh + docker-compose.yml(业务服务,双模式)
│   │   ├── edge/                start-edge.sh + docker-compose.yml + nginx.conf(独立 Nginx 边缘网关,host 网络 80 -> gateway:8080)
│   │   ├── observability/       start-observability.sh + docker-compose.yml(Prometheus/Grafana/Loki/Promtail/Sentinel Dashboard)
│   │   └── loadtest/            docker-compose.yml(k6 部署:拉镜像 / bridge+extra_hosts 经宿主地址打网关,非 loopback;由 --with-k6 选项部署)
│   ├── kubernetes/*.yaml        K8s 清单(按编号分层:0x 基础 / 1x 业务 / 2x 可观测治理 / 3x 测试工具;见 README §2.5.1)
│   │   ├── 30-loadtest-k6.yaml  k6 部署(k8s Job;由 --with-k6 选项部署,测试/工具层)
│   │   ├── 31-k6-operator.yaml  k6-operator(CRD + RBAC + 控制器;由 --with-k6-operator 选项部署,测试/工具层)
│   │   ├── 40-hpa.yaml          HPA(gateway/user/order/consumer,CPU 70%,2-4 副本;由 --with-governance 部署)
│   │   ├── 41-metrics-server.yaml  metrics-server(HPA 指标来源;由 --with-governance 部署)
│   │   ├── 42-pdb.yaml          PodDisruptionBudget(各服务至少 1 副本可用;由 --with-governance 部署)
│   │   ├── 43-resourcequota.yaml  ResourceQuota(Pod 数上限 40;CPU 12/32 核、内存 20/32Gi 配额;由 --with-governance 部署)
│   │   └── 44-limitrange.yaml  LimitRange(自动给临时/未声明 Pod 补默认 requests/limits;由 --with-governance 部署)
└── test/loadtest/                k6 压测(测试环境准备 + 测试逻辑;k6 部署清单在 deploy/)
    ├── script/                  config.js / mixed.js / user-read.js / order-flow.js(k6 场景脚本,唯一真源)
    ├── loadtest-common.sh       三种形态共用的测试逻辑(参数默认值 / 场景映射 / 限流开关)
    ├── proc/                    start-loadtest-proc.sh(宿主直接跑 k6,需本机 k6)
    ├── docker/                  start-loadtest-docker.sh(引用 deploy/docker/loadtest 的 compose,run 时挂卷跑 k6)
    ├── k8s/                     start-loadtest-k8s.sh(引用 deploy/kubernetes/30-loadtest-k6.yaml,建 ConfigMap + 跑 Job);start-loadtest-operator.sh(k6-operator:生成 TestRun 并 apply);testrun-template.yaml(TestRun 模板)
    ├── k6-operator/             testrun-example.yaml(Headlamp / kubectl 直接触发的 TestRun 示例)
    ├── run-loadtest.sh         业务服务 1-4 节点梯度压测统一入口(纯测量,不扩副本):读实际副本 + 校验 + 梯度加压 + 保存每档日志/汇总(单档/全量同一脚本)
    └── results/                 压测过程与结果(每档 k6 日志 + INDEX.md 汇总表)

2.1 架构图

                              ┌──────────────┐
                              │   Client     │
                              └──────┬───────┘
                                     │ HTTP :80
                          ┌──────────▼──────────┐
                          │   Nginx 边缘网关     │ :80
                          │  反向代理(无 rate limit)   │
                          └──────────┬──────────┘
                                     │ HTTP :8080
                          ┌──────────▼──────────┐
                          │   cloud-gateway      │ :8080
                          │  熔断 + Redis 限流   │
                          └──────────┬──────────┘
                                     │  lb:// (LoadBalancer 轮询实例)
        ┌────────────────────────────┼────────────────────────────┐
        ▼                            ▼                             ▼
┌───────────────┐          ┌───────────────┐          ┌──────────────────┐
│ user-service  │          │ order-service │          │  consumer-service│
│ 8081/8085/8086│          │ 8082/8087/8088│          │      :8083       │
└───────┬───────┘          └───────┬───────┘          └─────────┬────────┘
        │ Feign(降级)               │ Feign 校验用户             │ 订阅 Kafka
        │ └──────────┬──────────────┘                          │ (主/死信 Topic)
        │            │ Kafka 生产 order-created                │
        │            ▼                                          │
        │     ┌──────────────┐                                 │
        │     │  Kafka 9092  │◀────────────────────────────────┘
        │     └──────────────┘
        │ 消费事件 → 累加 order_count
        ▼
┌──────────┐   ┌──────────┐
│ MySQL3306│   │ Redis 6379│  ← user 缓存 user:{id}
│cloud_user│   └──────────┘
│cloud_order│
└──────────┘

  注册/发现/配置中心:Nacos    API 8848 / 控制台 8849
  链路追踪上报:          Zipkin 9411
  定时巡检:              cloud-job :8084 (Feign 调用业务服务)

当前本地每服务起 3 实例做负载均衡:user-service(8081/8085/8086)、order-service(8082/8087/8088); 网关经 lb:// 由 Spring Cloud LoadBalancer 轮询转发,实例宕机后 Nacos 约 30s 自动剔除。

3. 核心链路

  1. 客户端 →(经 Nginx 边缘网关 :80 转发)→ 网关 POST /api/order:网关先做 熔断 + 限流 防护(Redis 限流、Resilience4j 熔断),再转发
  2. order-service 通过 Feign 隐式调用 user-service 校验用户(Feign 层熔断降级,user-service 不可用时走 UserClientFallback
  3. 订单落库后发送 Kafka 事件 order-created
  4. user-service 消费事件,累加用户下单数
  5. user-service 读取/写入用户时走 Redis 缓存user:{id},TTL 300s,increaseOrderCount 后删除缓存保证一致)
  6. cloud-job 定时巡检 / 归档(Feign 调用业务服务)
  7. 全链路追踪:请求自网关进入即生成 traceId,经 Feign / WebClient 调用时通过 B3 头跨进程传播;各服务的 Span 采样后上报 Zipkin,可在 Zipkin UI 按 traceId 查看完整调用链与耗时

4. 防护与治理设计

4.1 网关限流(运行时动态开关)设计

核心目标:压测 / 演练 / 应急时随时放开或调高限流,无需重启网关、更不重建镜像

网关 order 路由的 Redis 令牌桶限流被设计为运行时可改写的开关:参数来自一个可变 bean,过滤器每个请求实时读取,因此改动即时生效。

4.1.1 默认阈值与维度

  • 默认 每 IP 每秒 100 次、突发 200replenishRate=100 / burstCapacity=200 / requestedTokens=1)——演示级合理值,生产按容量收紧。
  • 维度:KeyResolver=remoteAddrKeyResolver(按客户端 IP 限流)。
  • enabled=true(默认)按阈值限流;enabled=false 直接放行(不访问 Redis、不返 429)。

4.1.2 实现组成(代码位置)

类 / 文件 职责
cloud-gateway/.../config/RateLimitSettings 动态配置 bean:@Component @RefreshScope @ConfigurationProperties("gateway.ratelimit"),字段 enabled / replenishRate / burstCapacity / requestedTokens(默认值写在 Java 字段里,不要用 ${ENV:default} 占位符,否则 @RefreshScope rebind 时占位符不重新解析,Nacos 推送失效);@RefreshScope 使其能被 Nacos 配置中心动态推送刷新
cloud-gateway/.../config/RateLimitController Actuator 端点 /actuator/ratelimit@RestControllerEndpoint(id="ratelimit")):GET 查当前配置,POST 运行时改写
cloud-gateway/.../config/RateLimitGatewayFilterFactory 重写的限流过滤器,包装内置 RequestRateLimiter每个请求实时读 RateLimitSettingsenabled=false 直接放行;true 时按当前路由 id 写入 Redis 令牌桶配置后委托内置限流器,与 YAML 原生等价

配置(cloud-gateway/src/main/resources/application.yml 仅暴露端点;gateway.ratelimit.* 不写在此处,避免与 Nacos 同前缀竞争优先级,Nacos 才是权威源):

# application.yml 中 gateway 段只保留 security 等,ratelimit 不写死:
# gateway:
#   ratelimit:        # ← 故意不在此出现,默认值由 RateLimitSettings.java 字段提供
#     enabled: true
#     ...

management:
  endpoints:
    web:
      exposure:
        include: health,info,gateway,prometheus,ratelimit   # 必须暴露 ratelimit 端点

4.1.3 通过 Nacos 配置中心管理(首选)

网关已接入 Nacos 配置中心(spring.config.import: optional:nacos:cloud-gateway.yaml),RateLimitSettings@RefreshScope @ConfigurationProperties("gateway.ratelimit"),因此把限流开关作为 cloud-gateway.yaml 的一个配置项,在 Nacos 控制台修改并发布即动态生效,无需重启 / 不重建镜像——无需依赖 Actuator 端点。

在 Nacos 控制台(http://localhost:8849)创建 / 编辑 Data ID = cloud-gateway.yamlGroup = DEFAULT_GROUP格式 = yaml,加入限流段:

gateway:
  ratelimit:
    enabled: false          # 改为 false 并"发布"→ 立即放开限流(不访问 Redis、不返 429)
    replenishRate: 100      # 每 IP 每秒补充令牌数
    burstCapacity: 200      # 突发容量(须 ≥ replenishRate)
    requestedTokens: 1
  • 修改任意字段后点「发布」,Nacos 推送 @RefreshEvent → Spring Cloud ConfigurationPropertiesRebinder 重新绑定 RateLimitSettings下一个请求即生效
  • 如何确认推送真的生效(日志):网关日志会打印两处标记,按此排查——
    • RefreshEvent 到达网关:[ratelimit-refresh] RefreshEvent received (source=...);rebound gateway.ratelimit -> enabled=..., replenishRate=..., burstCapacity=..., requestedTokens=...(由 RateLimitRefreshLogger 监听 org.springframework.cloud.endpoint.event.RefreshEvent 输出)。改了 Nacos 却看不到这行 = 推送没到网关(检查 namespace / dataId / 网络 / refresh-enabled)。
    • RateLimitSettings 被重建绑定:[ratelimit] RateLimitSettings (re)bound -> enabled=...(每次 @RefreshScope rebind 都会打印,含启动首绑)。
    • 之后 GET /actuator/ratelimit 也会返回新值,与日志一致。
  • 生效前提(重要)application.yml不要写 gateway.ratelimit.* 字面值,也不要用 ${ENV:default} 占位符,更不能向容器/进程注入 GATEWAY_RATELIMIT_* 环境变量——@RefreshScope rebind 时,这些同名分量都会以 gateway.ratelimit.* 的来源参与解析并优先级胜出,表现为"RefreshEvent 触发了、日志也打了,但值仍是本地/环境变量旧值(永远 true)"。当前 application.yml 已不写该段、docker-compose 也已移除 GATEWAY_RATELIMIT_*,默认值由 RateLimitSettings 的 Java 字段提供;Nacos 推送才始终是权威值。
  • 优先级(权威源):Nacos 配置 > Actuator 端点临时修改 > RateLimitSettings 的 Java 字段默认值。即 Nacos 推送会覆盖端点设的值;Actuator 端点适合本地无 Nacos 控制台或应急临时调整,但下次 Nacos 推送会把它覆盖回 Nacos 的值。
  • 若 Nacos 中尚未建 cloud-gateway.yaml,则回退到 RateLimitSettings 的 Java 字段默认值(enabled=true 等,optional: 保证不报错);此时可用 Actuator 端点做动态开关。

运行时如何操作限流(Actuator 端点 / 与压测脚本集成)见 README §4。

4.2 网关熔断(Resilience4j)设计

user-service / order-service 路由挂 Resilience4j 路由级熔断(name: order-servicefallbackUri: forward:/fallback):被路由的实例全部不可用时,网关直接 fallback,不再穿透到已宕机的下游,避免雪崩。

  • 触发:下游无健康实例(如 docker stop cloud-user-service)→ POST /api/order 命中 /fallback,返回降级结果(SERVICE_UNAVAILABLE)。
  • 恢复:下游实例恢复注册(Nacos 约 30s 重新发现)后,熔断自动半开→闭合,恢复正常转发。
  • 与限流配合:限流在熔断之前(先限流后熔断),两者串行作用于 order 路由。

4.3 网关鉴权(JWT)设计

cloud-gatewayAuthGlobalFilter 全局鉴权过滤器(HS256),默认 gateway.security.enabled=false 关闭以保持演示可用。

  • 生产启用:--gateway.security.enabled=true --gateway.security.secret=<强密钥>,或写入 Nacos 配置。
  • 建议认证服务签发、网关只验签(RS256)。

启用方式(运行时操作)见 README §4.4。

4.4 (预留)Sentinel / 其它治理能力

  • Sentinel 依赖已引入、Dashboard 随可观测栈一键拉起(见 README §2.4),可在本节补充「Sentinel 规则可视化配置」小节。
  • 后续可扩展:灰度路由、防刷、热点参数限流等——每类治理能力按 §4.1~§4.3 的「代码位置 + 配置 + 运行时操作」结构补一节即可。

5. 项目设计特色

本工程在「多形态部署」「可移植性」「一体化编排」上做了系统性设计,是区别于一般 Demo、可直接作为落地参考的关键。

5.1 多形态部署(proc / docker / k8s)

同一份代码与配置,可在三种形态间切换,无需为部署改业务代码(仅 MODE / 启动脚本不同):

形态 运行方式 中间件 外部入口 适用场景
进程 proc 宿主 java -jar Docker 容器(localhost 互通) gateway :8080 本地调试、快速验证
镜像 docker docker compose 容器(host 网络) Docker 容器 Nginx :80 → gateway 单机完整栈、演示
K8s k3d k3d 集群 Pod(本地镜像 import) 集群内 Pod(演示级无 PVC) NodePort 30080 贴近生产、探针/ConfigMap 验证
  • 三者共用 build-images.sh 构建的 cloud/<name> 镜像与同一套 application.yml
  • 进程 / 镜像靠 MODE 变量切换(见 README §2.2);K8s 走 start-k3d.sh(见 README §2.5),stop-k3d.sh 即销毁重建,start+stop 循环等价于 reset(无需独立 reset 脚本)。

5.2 可移植性

本工程把"换机器即可复现、不被环境问题卡住"作为一等目标,在多处做了显式设计:

  • 构建 JDK 锁版本(核心):根 pom 用 maven-toolchains-pluginrequire JDK 17)+ 仓库内置 .mvn/toolchains.xml + .mvn/maven.config,让 mvn 自动用 JDK 17 的 javac 编译,与运行 mvn 的 JDK 无关。即使环境默认 JDK 是 26,编译依旧走 17,从根本上消除 Lombok 在更高 JDK 上的 TypeTag :: UNKNOWN 崩溃。JDK 17 缺失时构建直接失败而非悄悄退回错误版本;换机器只需改 .mvn/toolchains.xml<jdkHome>
  • 统一构建入口build-images.sh 是所有镜像/打包的唯一入口,内部钉死 JAVA_HOME(进程模式 java -jar、非 toolchain 场景的兜底),与 toolchains 一致不冲突。
  • 双运行形态零改配置:业务服务同时支持「进程模式 java -jar」与「镜像模式 Docker 容器」,靠 MODE 变量切换,同一份代码与配置,容器用 host 网络直连宿主中间件,无需为不同部署改任何 application.yml
  • Docker / K8s 双栈同源:Docker 栈(docker compose)与 K8s 栈(k3d + 清单)共用同一套镜像与配置;k3d 栈的 stop-k3d.sh 即销毁重建(含数据),start+stop 循环即等价重置,无需独立 reset 脚本。
  • 环境自检可预期:README §0 给出「环境准备」清单与一键自检命令,配合 CI(.github/workflows/ci.yml,JDK17 + mvn package)保证本地与流水线行为一致。

唯一与机器绑定的点:.mvn/toolchains.xml<jdkHome> 是绝对路径(toolchains 机制本身限制)。这是"显式声明、失败可见"的取舍——宁可换机器改一行路径,也不要静默用错 JDK。

5.3 一体化编排与优雅停机

  • 启动/停止脚本统一命名 start-*.sh / stop-*.sh,由 start-docker.sh / stop-docker.sh依赖序编排:中间件 → 业务 → 边缘 → 可观测;
  • 停止按反向依赖序(先断入口流量,再停业务,中间件最后停),配合应用 server.shutdown=graceful + lifecycle.timeout-per-shutdown-phase=30sdocker compose down -t 35(多 5s 余量)形成完整优雅停机链路(见 README §2.4.3),关闭期 DB/Kafka/Redis/Nacos 始终在线,避免 Communications link failure / Nacos 掉线。

5.4 构建与运行形态解耦

  • build-images.sh 是唯一构建入口,钉死 JAVA_HOME 作兜底,镜像/进程双模式复用,避免"构建用的是哪个 JDK"这类隐性问题;
  • 容器采用 host 网络直连宿主中间件,与应用配置默认的 127.0.0.1 一致,同一份 application.yml 通吃三种形态

5.5 内置可观测与防护治理

  • 可观测:Prometheus / Grafana / Loki / Promtail 一键拉起,实测 9 实例 target 全 up、Loki 查到真实日志(见 README §2.4、§5.1);
  • 防护治理:网关 Resilience4j 路由级熔断 + Redis 限流、Feign 层降级、Kafka 消费幂等(见 §4);
  • CICD.github/workflows/ci.yml(JDK17 + mvn package + 上传 jar)保证本地与流水线行为一致。