本文档聚焦技术栈、系统架构、核心链路与组件设计原理。 构建、部署、运行、压测、运行时操作等运维 / 操作类说明见 DEPLOYMENT.md / OPERATIONS.md;可视化部署入口见 WEB_CONSOLE.md。
Web 控制台(deploy/web-console,见 WEB_CONSOLE.md)是整套平台的可视化控制面:它不实现部署逻辑,只通过 Web UI 触发 start-k3d.sh / run-loadtest.sh / kubectl 等脚本并以 SSE 流式回传输出。设计原则:控制逻辑只在 .sh 脚本、脚本可脱离控制台独立运行、后端只做薄封装(exec + 转发 + 只读状态聚合)、前端只做展示与触发。这样保证控制台与命令行两条路径行为一致、可互相替代。
| 能力 | 组件 |
|---|---|
| 网关 | 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) |
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 汇总表)
┌──────────────┐
│ 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 自动剔除。
- 客户端 →(经 Nginx 边缘网关
:80转发)→ 网关POST /api/order:网关先做 熔断 + 限流 防护(Redis 限流、Resilience4j 熔断),再转发 - order-service 通过 Feign 隐式调用 user-service 校验用户(Feign 层熔断降级,user-service 不可用时走
UserClientFallback) - 订单落库后发送 Kafka 事件
order-created - user-service 消费事件,累加用户下单数
- user-service 读取/写入用户时走 Redis 缓存(
user:{id},TTL 300s,increaseOrderCount后删除缓存保证一致) - cloud-job 定时巡检 / 归档(Feign 调用业务服务)
- 全链路追踪:请求自网关进入即生成
traceId,经 Feign / WebClient 调用时通过 B3 头跨进程传播;各服务的 Span 采样后上报 Zipkin,可在 Zipkin UI 按traceId查看完整调用链与耗时
核心目标:压测 / 演练 / 应急时随时放开或调高限流,无需重启网关、更不重建镜像。
网关 order 路由的 Redis 令牌桶限流被设计为运行时可改写的开关:参数来自一个可变 bean,过滤器每个请求实时读取,因此改动即时生效。
- 默认 每 IP 每秒 100 次、突发 200(
replenishRate=100/burstCapacity=200/requestedTokens=1)——演示级合理值,生产按容量收紧。 - 维度:
KeyResolver=remoteAddrKeyResolver(按客户端 IP 限流)。 enabled=true(默认)按阈值限流;enabled=false直接放行(不访问 Redis、不返 429)。
| 类 / 文件 | 职责 |
|---|---|
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:每个请求实时读 RateLimitSettings,enabled=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 端点网关已接入 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.yaml、Group = DEFAULT_GROUP、格式 = yaml,加入限流段:
gateway:
ratelimit:
enabled: false # 改为 false 并"发布"→ 立即放开限流(不访问 Redis、不返 429)
replenishRate: 100 # 每 IP 每秒补充令牌数
burstCapacity: 200 # 突发容量(须 ≥ replenishRate)
requestedTokens: 1- 修改任意字段后点「发布」,Nacos 推送
@RefreshEvent→ Spring CloudConfigurationPropertiesRebinder重新绑定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=...(每次@RefreshScoperebind 都会打印,含启动首绑)。- 之后
GET /actuator/ratelimit也会返回新值,与日志一致。
- 生效前提(重要):
application.yml里不要写gateway.ratelimit.*字面值,也不要用${ENV:default}占位符,更不能向容器/进程注入GATEWAY_RATELIMIT_*环境变量——@RefreshScoperebind 时,这些同名分量都会以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。
user-service / order-service 路由挂 Resilience4j 路由级熔断(name: order-service,fallbackUri: forward:/fallback):被路由的实例全部不可用时,网关直接 fallback,不再穿透到已宕机的下游,避免雪崩。
- 触发:下游无健康实例(如
docker stop cloud-user-service)→POST /api/order命中/fallback,返回降级结果(SERVICE_UNAVAILABLE)。 - 恢复:下游实例恢复注册(Nacos 约 30s 重新发现)后,熔断自动半开→闭合,恢复正常转发。
- 与限流配合:限流在熔断之前(先限流后熔断),两者串行作用于
order路由。
cloud-gateway 的 AuthGlobalFilter 全局鉴权过滤器(HS256),默认 gateway.security.enabled=false 关闭以保持演示可用。
- 生产启用:
--gateway.security.enabled=true --gateway.security.secret=<强密钥>,或写入 Nacos 配置。 - 建议认证服务签发、网关只验签(RS256)。
启用方式(运行时操作)见 README §4.4。
- Sentinel 依赖已引入、Dashboard 随可观测栈一键拉起(见 README §2.4),可在本节补充「Sentinel 规则可视化配置」小节。
- 后续可扩展:灰度路由、防刷、热点参数限流等——每类治理能力按 §4.1~§4.3 的「代码位置 + 配置 + 运行时操作」结构补一节即可。
本工程在「多形态部署」「可移植性」「一体化编排」上做了系统性设计,是区别于一般 Demo、可直接作为落地参考的关键。
同一份代码与配置,可在三种形态间切换,无需为部署改业务代码(仅 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 脚本)。
本工程把"换机器即可复现、不被环境问题卡住"作为一等目标,在多处做了显式设计:
- 构建 JDK 锁版本(核心):根 pom 用
maven-toolchains-plugin(require 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。
- 启动/停止脚本统一命名
start-*.sh/stop-*.sh,由start-docker.sh/stop-docker.sh按依赖序编排:中间件 → 业务 → 边缘 → 可观测; - 停止按反向依赖序(先断入口流量,再停业务,中间件最后停),配合应用
server.shutdown=graceful+lifecycle.timeout-per-shutdown-phase=30s与docker compose down -t 35(多 5s 余量)形成完整优雅停机链路(见 README §2.4.3),关闭期 DB/Kafka/Redis/Nacos 始终在线,避免Communications link failure/ Nacos 掉线。
build-images.sh是唯一构建入口,钉死JAVA_HOME作兜底,镜像/进程双模式复用,避免"构建用的是哪个 JDK"这类隐性问题;- 容器采用
host网络直连宿主中间件,与应用配置默认的127.0.0.1一致,同一份application.yml通吃三种形态。
- 可观测: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)保证本地与流水线行为一致。