Skip to content

WalkCloud/springcloud-demo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Spring Cloud Demo

灵雀云 Alauda 容器云平台 · 售前演示用的微服务 Demo。基于 Spring Cloud + Nacos 构建的微服务架构示例,前端为云平台控制台风格的实时 Dashboard,直观展示服务拓扑、多 Pod 实例、健康状态与弹性伸缩。

运行环境

OpenJDK 版本:eclipse-temurin 21.0.11_10
Spring Boot 版本:3.2.5
Spring Cloud 版本:2023.0.1
Spring Cloud For Alibaba 版本:2023.0.1.0
Nacos 版本:2.2.x ~ 2.4.x
容器编排:Kubernetes / Docker

技术栈

技术 角色
OpenJDK 21 运行时
Spring Boot 3.2 应用框架
Spring Cloud 微服务框架
Spring Cloud Alibaba 微服务组件(Nacos 注册/配置中心)
Kubernetes 容器编排
Redis(可选) 缓存(consumer 缓存聚合结果)
MySQL 8(可选) 持久化(provider-a/b 业务数据)
Kafka(可选) 消息队列(consumer 异步上报访问事件)
Thymeleaf 模板引擎(仅 consumer 使用)

架构

浏览器 → consumer (8080, 控制台 Dashboard)
              ├─ Redis   (缓存聚合结果,ENABLE_REDIS=true 时启用)
              ├─ Kafka   (异步上报访问事件,ENABLE_KAFKA=true 时启用)
              ↓ DiscoveryClient 从 Nacos 获取全部实例并逐一调用
         Nacos 注册中心 / 配置中心
              ↓
    ┌─────────┴──────────┐
 provider-a (8081)   provider-b (8082)
   商品服务            库存服务
   └─ MySQL ──┐  └─ MySQL ──┘  (ENABLE_MYSQL=true 时启用,各自独立库)
  • consumer:对外入口(端口 8080)。通过 DiscoveryClient 从 Nacos 获取 provider 的全部实例(多 Pod),逐一调用每个实例的 /info,从而在页面上展示全部 Pod 信息(而非负载均衡只显示一个)。前端为云平台控制台风格的实时 Dashboard。
  • provider-a:模拟商品服务(端口 8081)。返回主机名、IP、星级评分 + 商品列表(SKU/名称/价格/状态)。
  • provider-b:模拟库存服务(端口 8082)。返回主机名、IP、星级评分 + 库存汇总(SKU 总数/库存总量/仓库数/低库存预警)和库存明细。
  • 两个 provider 的星级(颜色/数量)通过 Nacos 配置中心动态下发,支持 @RefreshScope 热刷新。
  • 可选中间件(Redis/MySQL/Kafka)通过开关控制,默认关闭,详见下文「中间件条件接入」。

Consumer 接口

路径 说明
GET / 渲染主页面(Thymeleaf 模板,页面内 JS 轮询填充数据)
GET /api/overview 返回聚合后的服务总览 JSON(consumer + 两个 provider 的全部实例 + 全局汇总),供前端 5 秒轮询刷新

Dashboard 页面能力

页面纯原生 HTML + CSS + JS(不依赖任何外部 CDN,确保内网/离线演示可用),包含:

  • 架构拓扑图(SVG):浏览器 → Consumer → Nacos → Provider A/B 的调用与注册关系,调用线带数据流动动画
  • 集群概览 KPI:服务总数 / 实例总数 / 在线实例 / 离线实例
  • 技术栈卡片:展示基础技术,并根据 /api/overview 动态显示已启用的 Redis、MySQL 和 Kafka
  • 服务详情卡片:consumer / provider-a / provider-b 三张卡片,各展示副本数、每个 Pod 的实例 ID、IP:端口、健康状态徽标(UP/DOWN)、业务数据;Consumer 卡片同时展示 Redis 缓存与 Kafka 事件上报状态
  • Nacos 注册表:三列展示(服务名 / 实例数 / 实例 IP),实时同步自 Nacos
  • 实时刷新:每 5 秒轮询 /api/overview,弹性伸缩(Pod 增减)时页面实时变化
  • 品牌标识:灵雀云 Alauda 品牌露出

构建与运行

每个服务独立构建(无父 POM,各自是独立的 Spring Boot 应用):

# 构建某个服务(在服务目录下执行)
./mvnw clean package

# 本地运行(需先启动 Nacos)
java -jar target/<artifact>-1.0.jar

Docker 构建(在服务目录下):

docker build -t <service-name> .

Docker Hub 镜像

已发布的 linux/amd64 演示镜像:

服务 镜像
Consumer kevinlee822/consumer:latest
Provider A kevinlee822/providera:latest
Provider B kevinlee822/providerb:latest
docker pull kevinlee822/consumer:latest
docker pull kevinlee822/providera:latest
docker pull kevinlee822/providerb:latest

Kubernetes 部署

本项目目标运行环境为 Kubernetes。部署应用前,请确保集群中已存在可访问的 Nacos、Redis、MySQL 和 Kafka,并且以下 Service DNS 名称可用:

  • nacos:8848
  • redis:6379
  • mysql:3306
  • kafka:9092

创建命名空间和 MySQL 密码 Secret:

kubectl create namespace springcloud-demo
kubectl -n springcloud-demo create secret generic middleware-secret \
  --from-literal=mysql-password='<MySQL密码>'

将以下内容保存为 springcloud-demo.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: provider-a
  namespace: springcloud-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: provider-a
  template:
    metadata:
      labels:
        app: provider-a
    spec:
      containers:
        - name: provider-a
          image: kevinlee822/providera:latest
          imagePullPolicy: Always
          ports:
            - name: http
              containerPort: 8081
          env:
            - name: NACOS_SERVER_ADDR
              value: nacos:8848
            - name: NACOS_NAMESPACE
              value: public
            - name: ENABLE_MYSQL
              value: "true"
            - name: MYSQL_HOST
              value: mysql
            - name: MYSQL_PORT
              value: "3306"
            - name: MYSQL_USER
              value: root
            - name: MYSQL_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: middleware-secret
                  key: mysql-password
            - name: MYSQL_DATABASE
              value: provider_a_db
          readinessProbe:
            httpGet:
              path: /actuator/health
              port: http
            initialDelaySeconds: 20
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /actuator/health
              port: http
            initialDelaySeconds: 40
            periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
  name: provider-a
  namespace: springcloud-demo
spec:
  selector:
    app: provider-a
  ports:
    - name: http
      port: 8081
      targetPort: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: provider-b
  namespace: springcloud-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: provider-b
  template:
    metadata:
      labels:
        app: provider-b
    spec:
      containers:
        - name: provider-b
          image: kevinlee822/providerb:latest
          imagePullPolicy: Always
          ports:
            - name: http
              containerPort: 8082
          env:
            - name: NACOS_SERVER_ADDR
              value: nacos:8848
            - name: NACOS_NAMESPACE
              value: public
            - name: ENABLE_MYSQL
              value: "true"
            - name: MYSQL_HOST
              value: mysql
            - name: MYSQL_PORT
              value: "3306"
            - name: MYSQL_USER
              value: root
            - name: MYSQL_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: middleware-secret
                  key: mysql-password
            - name: MYSQL_DATABASE
              value: provider_b_db
          readinessProbe:
            httpGet:
              path: /actuator/health
              port: http
            initialDelaySeconds: 20
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /actuator/health
              port: http
            initialDelaySeconds: 40
            periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
  name: provider-b
  namespace: springcloud-demo
spec:
  selector:
    app: provider-b
  ports:
    - name: http
      port: 8082
      targetPort: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: consumer
  namespace: springcloud-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: consumer
  template:
    metadata:
      labels:
        app: consumer
    spec:
      containers:
        - name: consumer
          image: kevinlee822/consumer:latest
          imagePullPolicy: Always
          ports:
            - name: http
              containerPort: 8080
          env:
            - name: NACOS_SERVER_ADDR
              value: nacos:8848
            - name: NACOS_NAMESPACE
              value: public
            - name: ENABLE_REDIS
              value: "true"
            - name: REDIS_HOST
              value: redis
            - name: REDIS_PORT
              value: "6379"
            - name: ENABLE_KAFKA
              value: "true"
            - name: KAFKA_SERVER
              value: kafka:9092
          readinessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 30
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 60
            periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
  name: consumer
  namespace: springcloud-demo
spec:
  type: NodePort
  selector:
    app: consumer
  ports:
    - name: http
      port: 8080
      targetPort: http
      nodePort: 30080

部署并检查状态:

kubectl apply -f springcloud-demo.yaml
kubectl -n springcloud-demo get pods,svc
kubectl -n springcloud-demo rollout status deployment/provider-a
kubectl -n springcloud-demo rollout status deployment/provider-b
kubectl -n springcloud-demo rollout status deployment/consumer

通过任意 Kubernetes 节点的 30080 端口访问 Dashboard:

http://<Node-IP>:30080/

也可以使用端口转发进行本地访问:

kubectl -n springcloud-demo port-forward service/consumer 8080:8080

然后访问 http://localhost:8080/

弹性伸缩 Provider 副本:

kubectl -n springcloud-demo scale deployment/provider-a --replicas=3
kubectl -n springcloud-demo scale deployment/provider-b --replicas=3

如果 Nacos 开启了认证,请将用户名和密码保存为 Kubernetes Secret,并通过 NACOS_USERNAMENACOS_PASSWORD 环境变量注入三个 Deployment;未开启认证时不要配置无效凭据。

停止并删除应用资源:

kubectl delete -f springcloud-demo.yaml

必备基础设施

  • Nacos 2.2.x ~ 2.4.x 必须先于任何服务启动
  • 三个服务均通过环境变量配置 Nacos 连接:
    • NACOS_SERVER_ADDR:Nacos 访问地址,如 192.168.0.1:8848
    • NACOS_NAMESPACE:命名空间,如 public
    • NACOS_USERNAME:用户名,如 nacos
    • NACOS_PASSWORD:密码,如 nacos

Nacos 配置中心

provider-a / provider-b 通过 spring.config.import 从 Nacos 拉取星级配置,支持动态刷新。

provider-a 的 application.yml

spring:
  config:
    import: "optional:nacos:provider-a.yml"

Nacos 配置中心中 dataId = provider-a.yml(YAML 格式)的内容:

star:
  color: blue   # red/blue/green/yellow/gold/silver/orange/purple/pink/black/white
  count: 5      # 星星数量

application.yml 中的 import 值必须与 Nacos 配置中心的 Data ID 一致,配置才能下发到应用。provider-b 同理(provider-b.yml)。

中间件条件接入(Redis / MySQL / Kafka)

三个中间件通过环境变量开关控制是否启用,默认全部关闭。不启用时 demo 行为与纯微服务完全一致;启用后连不上则自动降级,绝不中断。

中间件 接入服务 用途 启用开关
Redis consumer 缓存 /api/overview 聚合结果(TTL 10s) ENABLE_REDIS=true
MySQL provider-a / provider-b 业务数据持久化(按 SKU 明细存储) ENABLE_MYSQL=true
Kafka consumer 异步上报访问事件到 topic consumer-access-event ENABLE_KAFKA=true

设计原则

  • 开关控制:开关关 → 对应 Bean 不创建、不连接(用 @ConditionalOnProperty + autoconfigure.exclude 双保险),demo 与改造前行为 100% 一致。
  • 运行时降级:开关开但连不上 → try-catch 回退到内存默认数据,demo 继续可用。
  • 接口零变化/info 返回结构前后一致,Dashboard 前端无需改动。provider 返回额外带 dataSource 字段("mysql""memory")标识数据来源,便于演示区分。

数据来源对照

场景 provider 数据来源 演示价值
不接 MySQL 内存硬编码(固定) 基础展示,数据写死、改不了
接 MySQL 数据库实时查询 现场往库里 INSERT 新数据,Dashboard 5 秒内实时刷新显示 ← 核心演示卖点

环境变量

# Consumer(Redis + Kafka,按需开启)
ENABLE_REDIS=true
REDIS_HOST=<redis-host>
REDIS_PORT=6379
REDIS_PASSWORD=<可选>

ENABLE_KAFKA=true
KAFKA_SERVER=<kafka-host>:9092

# Provider-a(MySQL,库 provider_a_db)
ENABLE_MYSQL=true
MYSQL_HOST=<mysql-host>
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=<密码>
MYSQL_DATABASE=provider_a_db

# Provider-b(MySQL,库 provider_b_db)
ENABLE_MYSQL=true
MYSQL_HOST=<mysql-host>
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=<密码>
MYSQL_DATABASE=provider_b_db

不设置任何 ENABLE_XXX 即为纯微服务模式,无需 Redis/MySQL/Kafka。

演示:进入 MySQL 创建测试数据

Provider A/B 启动时会自动创建 provider_a_db.productprovider_b_db.inventory 表并写入初始数据。正常部署不需要手动建库或建表。

下面的命令只用于现场演示时进入数据库并新增或修改测试数据,以验证 Dashboard 能否实时展示 MySQL 中的变化。

第 1 步:登录 MySQL

在安装了 MySQL 客户端的机器上执行:

mysql -h <MySQL地址> -P 3306 -u root -p

例如 MySQL 地址是 192.168.1.100

mysql -h 192.168.1.100 -P 3306 -u root -p

执行后输入 MySQL 密码。输入密码时终端不会显示字符,这是正常现象。登录成功后会看到:

mysql>

以下 SQL 都在 mysql> 提示符后执行,并且必须以英文分号 ; 结尾。

第 2 步:进入商品数据库并创建测试商品

先进入 Provider A 使用的数据库:

USE provider_a_db;

看到 Database changed 表示已经成功切换数据库。可以执行以下命令确认当前数据库和商品表:

SELECT DATABASE();
SHOW TABLES;

结果中应看到 product 表。然后新增一条演示商品:

INSERT INTO product (sku, name, price, status)
VALUES ('DEMO-P-001', '云原生安全实战', 99.00, '热销')
ON DUPLICATE KEY UPDATE
  name = VALUES(name),
  price = VALUES(price),
  status = VALUES(status);

查询确认测试商品已经写入:

SELECT id, sku, name, price, status
FROM product
WHERE sku = 'DEMO-P-001';

等待最多 5 秒,Dashboard 的 Provider A 商品列表中应出现“云原生安全实战”。

继续修改商品价格,可以演示数据实时更新:

UPDATE product
SET price = 199.00, status = '热销'
WHERE sku = 'DEMO-P-001';

SELECT id, sku, name, price, status
FROM product
WHERE sku = 'DEMO-P-001';

第 3 步:切换到库存数据库并创建测试库存

不需要退出 MySQL,直接使用 USE 切换到 Provider B 的数据库:

USE provider_b_db;

确认当前数据库和库存表:

SELECT DATABASE();
SHOW TABLES;

结果中应看到 inventory 表。然后新增一条演示库存:

INSERT INTO inventory (sku, name, stock, warehouse, low_stock_threshold)
VALUES ('DEMO-SKU-001', 'ArgoCD 实战', 600, 'wh-1', 200)
ON DUPLICATE KEY UPDATE
  name = VALUES(name),
  stock = VALUES(stock),
  warehouse = VALUES(warehouse),
  low_stock_threshold = VALUES(low_stock_threshold);

查询确认测试库存已经写入,并查看当前库存状态:

SELECT sku, name, stock, warehouse, low_stock_threshold,
       CASE WHEN stock < low_stock_threshold THEN '低库存' ELSE '正常' END AS status
FROM inventory
WHERE sku = 'DEMO-SKU-001';

此时库存为 600、预警阈值为 200,状态应为“正常”。等待最多 5 秒,Dashboard 的 Provider B 库存列表和库存总量应发生变化。

将库存改为低于预警阈值,用于演示低库存告警:

UPDATE inventory
SET stock = 10
WHERE sku = 'DEMO-SKU-001';

再次查询:

SELECT sku, name, stock, low_stock_threshold,
       CASE WHEN stock < low_stock_threshold THEN '低库存' ELSE '正常' END AS status
FROM inventory
WHERE sku = 'DEMO-SKU-001';

此时状态应变为“低库存”,Dashboard 的低库存预警数量也会随之变化。

第 4 步:清理演示数据并退出

演示结束后,如果不希望保留测试数据,可以执行:

USE provider_a_db;
DELETE FROM product WHERE sku = 'DEMO-P-001';

USE provider_b_db;
DELETE FROM inventory WHERE sku = 'DEMO-SKU-001';

退出 MySQL:

EXIT;

如果执行 USE provider_a_dbUSE provider_b_db 时提示数据库不存在,或者 SHOW TABLES 看不到对应表,请先确认 Provider 已使用 ENABLE_MYSQL=true 成功启动,并检查 Provider 日志中的 MySQL 连接错误。Dashboard 中 Provider 的 dataSource 应为 mysql;如果显示 memory,说明当前使用的是内存降级数据,数据库修改不会反映到页面。

示例效果

访问 http://<consumer 所在主机 IP>:8080/ 即可看到 Dashboard:

  • 集群概览:3 个服务、N 个实例、全部在线
  • 拓扑图:直观展示浏览器 → Consumer → Provider 的调用链与 Nacos 注册关系
  • 服务详情:每个 provider 列出所有 Pod(多副本时每个 Pod 的 IP/状态都展示),provider-a 显示商品列表,provider-b 显示库存指标
  • 弹性伸缩:演示扩缩容 provider 副本,页面 5 秒内自动更新 Pod 数量与列表
  • 中间件联动:接 MySQL 后现场 INSERT/UPDATE,Dashboard 实时刷新数据(详见上文「中间件条件接入」)

访问关系

浏览器访问 consumer,consumer 通过 Nacos 注册中心发现并直接调用 provider-a、provider-b 的全部实例,在页面上聚合展示所有 Pod 信息。

About

A Kubernetes-native Spring Cloud microservices demo featuring Nacos service discovery, a real-time dashboard, multi-replica visualization, and optional MySQL, Redis, and Kafka integration.

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages