从零部署Spring Cloud微服务系统(Kiwi-Hub)

1. 项目概述与架构设计

1.1 项目背景

本文将以我的个人开源项目 Kiwi-Hub (奇异果社区) 为例,详细记录如何在一台 阿里云 2核2G 的服务器上,通过 Docker + Nginx + Nacos 成功部署一套包含三个微服务的完整系统,并实现 Swagger/Knife4j 接口文档的聚合。

Kiwi-Hub(奇异果社区)是一个基于微服务架构设计的社交内容分享平台。该项目旨在模拟主流社交媒体的核心功能,包括用户管理、内容发布与互动、以及短链接生成服务。

1.2 架构演进说明

项目仓库主要包含 mainnginx 两个分支。

  • Main 分支:采用 Spring Cloud Gateway 作为统一网关入口,基于 Netty 响应式编程模型。
  • Nginx 分支(当前部署版本):鉴于生产环境服务器配置的限制(2核 CPU,2GB 内存),Java 编写的 Spring Cloud Gateway 启动后需占用约 300MB-500MB 内存,对系统资源造成较大压力。因此,当前部署方案移除了 Spring Cloud Gateway,改用轻量级的高性能 Web 服务器 Nginx 作为反向代理网关。

1.3 微服务拆分

系统被拆分为三个核心无状态服务,通过 HTTP RESTful API 进行通信:

  1. kiwi-user (用户服务):负责用户注册、登录认证、个人信息管理。
  2. kiwi-content (内容服务):核心业务模块,负责帖子发布、评论管理、点赞逻辑。
  3. kiwi-link (短链服务):负责长链接到短链接的映射生成与解析跳转。

1.4 技术栈与基础设施

  • 计算资源:阿里云 ECS(2 vCPU, 2 GiB RAM, CentOS Stream 10)。
  • 容器化技术:Docker Engine, Docker Compose。
  • 注册与配置中心:Nacos (Standalone 模式)。
  • 反向代理与网关:Nginx。
  • 数据存储与消息队列(SaaS 托管)
    • Redis: Upstash (Serverless Redis)。
    • MongoDB: MongoDB Atlas。
    • RabbitMQ: CloudAMQP。

2. 服务器基础环境配置

在低配置服务器上运行 Java 微服务集群,操作系统级别的参数调优是保障服务稳定运行的前提。

2.1 操作系统初始化与依赖安装

首先更新系统软件包索引,并安装 Docker 及 Docker Compose。

bash 复制代码
# 更新 Yum 源
sudo yum update -y

# 安装必要的工具包
sudo yum install -y yum-utils device-mapper-persistent-data lvm2 git vim curl

# 添加 Docker 官方源
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# 安装 Docker Engine
sudo yum install -y docker-ce docker-ce-cli containerd.io

# 启动 Docker 并设置开机自启
sudo systemctl start docker
sudo systemctl enable docker

# 安装 Docker Compose (独立二进制文件方式)
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

2.2 虚拟内存(Swap)配置详解

由于服务器物理内存仅为 2GB,而三个 Spring Boot 应用加上 Nacos 的堆内存需求很容易超出此限制,导致 OOM Killer 强制杀死进程。配置 Swap 分区是防止进程崩溃的必要手段。

操作步骤:

  1. 创建交换文件 :使用 dd 命令创建一个 2GB 的文件。if=/dev/zero 表示输入源为零设备,bs=1M 表示块大小为 1MB,count=2048 表示块数量。

    bash 复制代码
    dd if=/dev/zero of=/swapfile bs=1M count=2048
  2. 权限设置:出于安全考虑,Swap 文件权限必须设置为 600(仅 root 读写)。

    bash 复制代码
    chmod 600 /swapfile
  3. 格式化与挂载

    bash 复制代码
    mkswap /swapfile
    swapon /swapfile
  4. 持久化配置 :修改 /etc/fstab 文件,确保服务器重启后 Swap 依然有效。

    bash 复制代码
    echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

2.3 Swappiness 参数调优

Linux 内核的 vm.swappiness 参数控制系统使用 Swap 的积极程度,取值范围 0-100。默认值 60 表示内存使用率达到 40% 时开始使用 Swap。在小内存机器上,建议将其调整为 100,即系统会极其积极地将非活跃内存页换出到磁盘,从而为活跃的 JVM 堆内存腾出物理空间。

bash 复制代码
# 修改内核参数
sysctl vm.swappiness=100

# 写入配置文件永久生效
echo 'vm.swappiness=100' >> /etc/sysctl.conf

注:虽然 Swap 速度远慢于内存,但对于非高并发的个人项目,保证服务"不挂"比"响应极快"更重要。

3.3 "白嫖"云端中间件

如果在 2G 的服务器上再部署 Redis、MongoDB 和 RabbitMQ,服务器必死无疑。因此,我采用了**云端托管数据库(SaaS)**的方案。这不仅节省了资源,还免去了运维数据库的烦恼。

配置提示

确保 Nacos 配置文件中,所有数据库连接地址均已指向这些云服务的公网地址,并启用了 SSL(如果服务商要求)。


3. 目录结构与数据持久化规划

规范的目录结构有助于后续的运维管理和日志排查。本项目采用 /opt/kiwihub 作为根目录,实现配置、日志、数据的分离。

text 复制代码
/opt
└── kiwihub/                  <-- 项目总目录
    ├── docker-compose.yml    <-- 核心编排文件
    ├── .env                  <-- (可选) 存放密码等环境变量
    ├── logs                  <-- 日志文件
    ├── services/             <-- 存放微服务 JAR 包和 Dockerfile
    │   ├── user/
    │   │   ├── Dockerfile
    │   │   └── kiwi-user.jar
    │   ├── content/
    │   │   ├── Dockerfile
    │   │   └── kiwi-content.jar
    │   └── link/
    │       ├── Dockerfile
    │       └── kiwi-link.jar
    ├── nginx/                <-- 存放 Nginx 配置
    │   ├── conf/
    │   │   └── nginx.conf    <-- 反向代理配置
    │   ├── logs/             <-- Nginx 日志映射
    │   └── html/             <-- Knife4j 导航页 index.html 放这
    └── data/                 <-- (重要) 数据挂载目录
        ├── redis/            <-- Redis 数据持久化 (使用的云端数据库,实际没有)
        ├── mongo/            <-- MongoDB 数据持久化 (使用的云端数据库,实际没有)
        └── nacos/            <-- Nacos 数据

4. 微服务镜像构建策略

为了加快传输速度并减少磁盘占用,我们放弃传统的 java -jar 方式,利用 Spring Boot 的**分层构建(Layered Jar)**特性和 Alpine 镜像。

4.1 通用 Dockerfile 模版

以下 Dockerfile 适用于所有三个微服务,通过多阶段构建(Multi-stage Build)实现环境分离。只需在构建时传入 JAR_FILE 参数即可。

dockerfile 复制代码
# 第一阶段:构建/提取层 (Builder Stage)
# 使用 JRE 而不是 JDK,且使用 Alpine 版本,体积最小 (约 50MB-60MB)
FROM eclipse-temurin:17-jre-alpine AS builder

# 设置工作目录
WORKDIR /application

# 接收构建参数,指向你的 jar 包位置
ARG JAR_FILE=*.jar

# 将 jar 包复制进去并重命名
COPY ${JAR_FILE} application.jar

# 利用 Spring Boot 的 layertools 提取分层
# 这会将 jar 包拆解为依赖、Spring加载器、快照依赖、业务代码四部分
RUN java -Djarmode=layertools -jar application.jar extract

# ----------------------------------------------------------------

# 第二阶段:运行层 (Runtime Stage)
# 再次使用最小的基础镜像,丢弃第一阶段的构建工具,只保留提取后的文件
FROM eclipse-temurin:17-jre-alpine

WORKDIR /application

# 按照分层顺序复制文件 (顺序很重要!变动最少的放在最前面)
# dependencies: 第三方依赖 (最不常变,Docker 会缓存这层)
COPY --from=builder /application/dependencies/ ./
# spring-boot-loader: Spring 启动加载器
COPY --from=builder /application/spring-boot-loader/ ./
# snapshot-dependencies: 内部快照依赖 (比如 common 模块)
COPY --from=builder /application/snapshot-dependencies/ ./
# application: 业务代码 (最常变,只有这层会重新构建)
COPY --from=builder /application/application/ ./

# 设置时区为上海 (解决日志时间不对的问题)
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' > /etc/timezone

# 暴露端口
EXPOSE 8070

# 核心启动命令
# 使用 Spring 的 JarLauncher 启动,而不是 java -jar
# 这样能自动加载分层文件
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]

4.2 构建优势分析

  1. 构建速度优化 :当只修改业务代码时,Docker 仅需重新构建最上层的 application 层,底层的依赖层直接利用缓存,大幅缩短构建时间。
  2. 存储效率:多个微服务往往共享大部分第三方依赖(如 Spring Boot Starter),这些相同的层在磁盘上只会存储一份。
  3. 安全性与体积:Alpine 镜像剔除了大量非必要的系统工具,减小了攻击面,且最终镜像大小控制在 150MB 左右。(三个镜像实际构建后不足140MB)

5. Nginx 反向代理配置详解

5.1 配置文件 (kiwi.conf)

Nginx 在此架构中承担两个关键角色

  1. 业务路由:将请求转发到对应的微服务。
  2. 文档聚合:让前端能访问到后端 Knife4j/Swagger 的静态资源。

文件路径:/opt/kiwihub/nginx/conf/conf.d/kiwi.conf

nginx 复制代码
server {
    listen 80;
    server_name kiwihub.com 120.**.***.5; # 绑定域名与公网IP

    # =========================================================
    # 前端导航页路由
    # =========================================================
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        # 前端路由支持,防止刷新 404
        try_files $uri $uri/ /index.html;
    }

    # =========================================================
    # Swagger/Knife4j 静态资源聚合逻辑
    # =========================================================
    # Knife4j 请求静态资源(CSS/JS/JSON)的通用路径
    location ~ ^/(webjars|v3|swagger-resources) {
        # 如果来源页面包含 /users/,转发到用户服务
        if ($http_referer ~* "/users/") { 
            proxy_pass http://kiwi-user:8070; 
        }
        # 如果来源页面包含 /links/,转发到短链服务
        if ($http_referer ~* "/links/") { 
            proxy_pass http://kiwi-link:8030; 
        }
        # 默认转发到内容服务(或根据需要添加更多 if 逻辑)
        proxy_pass http://kiwi-content:8010;
    }

    # =========================================================
    # 微服务反向代理配置
    # =========================================================
    
    # 用户服务代理
    location /users/ {
        # 使用 rewrite 去除 URL 前缀,根据后端 ContextPath 配置决定是否需要
        rewrite ^/users/(.*)$ /$1 break;
        proxy_pass http://kiwi-user:8070;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # 短链服务代理
    location /links/ {
        rewrite ^/links/(.*)$ /$1 break;
        proxy_pass http://kiwi-link:8030;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    # 内容服务代理
    # 注意:/content/ 必须放在具体路径匹配之后,防止优先级冲突
    location /content/ {
        rewrite ^/content/(.*)$ /$1 break;
        proxy_pass http://kiwi-content:8010;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

5.2 静态文档聚合页面 (index.html)

为了方便查看接口文档,在 /opt/kiwihub/nginx/html/ 下放置一个简单的 HTML 页面,通过 iframe 或超链接指向各服务的 Knife4j 地址,配合 Nginx 配合完成页面的路由。

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>KiwiHub API 导航</title>
    <style>
        body { font-family: 'Segoe UI', sans-serif; text-align: center; padding-top: 50px; background-color: #f4f4f4; }
        .container { max-width: 800px; margin: 0 auto; background: white; padding: 40px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
        h1 { color: #333; }
        .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-top: 30px; }
        .card { padding: 20px; border: 1px solid #eee; border-radius: 8px; transition: 0.3s; text-decoration: none; color: inherit; display: block; }
        .card:hover { transform: translateY(-5px); box-shadow: 0 5px 15px rgba(0,0,0,0.1); border-color: #409EFF; }
        .icon { font-size: 40px; margin-bottom: 10px; display: block; }
        .title { font-weight: bold; font-size: 18px; color: #409EFF; }
    </style>
</head>
<body>
    <div class="container">
        <h1>KiwiHub 微服务接口文档</h1>
        <p>基于 Nginx 反向代理与 Knife4j 实现</p>
        
        <div class="grid">
            <a href="/users/doc.html" class="card" target="_blank">
                <span class="title">用户服务</span>
            </a>
            
            <a href="/content/doc.html" class="card" target="_blank">
                <span class="title">内容服务</span>
            </a>
            
            <a href="/links/doc.html" class="card" target="_blank">
                <span class="title">短链服务</span>
            </a>
        </div>
    </div>
</body>
</html>

6. Docker Compose 编排与资源限制

docker-compose.yml 是整个部署的核心,定义了服务间的网络依赖、环境变量注入以及关键的资源限制(Resources Limits)。

yaml 复制代码
version: '3.8'

networks:
  kiwi-net:
    driver: bridge

services:
  # ----------------------------------------------------
  # Nacos 服务 (注册中心 & 配置中心)
  # ----------------------------------------------------
  nacos:
    image: nacos/nacos-server:v2.4.1
    container_name: nacos-standalone
    restart: always
    environment:
      # 开启单机模式
      - MODE=standalone
      # 默认用户名/密码: nacos / nacos
      - NACOS_AUTH_ENABLE=true
      # 内部身份识别 Key (随意填)
      - NACOS_CORE_AUTH_SERVER_IDENTITY_KEY=kiwihub_server_id
      # 内部身份识别 Value (随意填)
      - NACOS_CORE_AUTH_SERVER_IDENTITY_VALUE=kiwihub_server_value
      # Token 密钥 (必须是 Base64 编码且足够长,否则会报错)
      - NACOS_CORE_AUTH_PLUGIN_NACOS_TOKEN_SECRET_KEY=VGhpc0lzQVNlY3JldEtleUZvck5hY29zVG9rZW5WYWxpZGF0aW9u
      # 极致 JVM 内存压缩
      # 默认是 2G/2G,这里压到 200M/200M
      - JVM_XMS=200m
      - JVM_XMX=200m
      - JVM_MaxDirectMemorySize=64m # 限制堆外内存
      - JVM_XMN=128m
      - JVM_MS=128m
      - JVM_MMS=256m
    ports:
      # Nacos 控制台和 API 端口
      - "8848:8848"
      # Nacos 2.x gRPC 端口 (必须暴露,否则服务注册不上)
      # 9848 = 8848 + 1000
      - "9848:9848"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8848/nacos/v1/console/health/readiness"]
      interval: 10s
      timeout: 10s
      retries: 10
      start_period: 15s # 给它 15秒 的缓冲期再开始检查
      # - "9849:9849" # 这个端口通常用于集群同步,单机模式可不映射到宿主机,但在容器间网络要通
    volumes:
      # 挂载日志,方便排错
      - ./nacos/logs:/home/nacos/logs
      - /opt/kiwihub/data/nacos:/home/nacos/data
    deploy:
      resources:
        limits:
          # Docker 硬限制:如果 Nacos 尝试使用超过 512M 内存,直接杀掉重启
          # 保护你的 2G 服务器不被它拖垮
          memory: 512M
    networks:
      - kiwi-net

  # ----------------------------------------------------
  # Nginx (作为唯一入口)
  # ----------------------------------------------------
  nginx:
    image: nginx:alpine
    container_name: kiwi-nginx
    restart: always
    ports:
      - "80:80" # 只暴露 80 端口给公网
    volumes:
      # 挂载配置文件
      - ./nginx/conf.d:/etc/nginx/conf.d
      # 挂载静态资源(文档导航页)
      - ./nginx/html:/usr/share/nginx/html
      # 挂载日志
      - ./nginx/logs:/var/log/nginx
    environment:
      - TZ=Asia/Shanghai
    deploy:
      resources:
        limits:
          memory: 64M # Nginx 非常省内存,64M 绰绰有余
    networks:
      - kiwi-net

  # 3 个微服务
  kiwi-user:
    image: kiwi-user:v1
    container_name: kiwi-user
    restart: always
    environment:
      # 这里对应上面 YAML 里的变量名
      - NACOS_SERVER_ADDR=nacos:8848
      - SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR=nacos:8848
      - SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR=nacos:8848
      # 限制内存
      - JVM_TOOL_OPTIONS=-Xms128m -Xmx256m
      # ========== 对应spring.data.redis核心连接配置 ==========
      - SPRING_DATA_REDIS_URL=rediss://default:**********@******.upstash.io:6379
      - SPRING_DATA_REDIS_DATABASE=0
      # ========== 对应spring.data.redis.ssl.enabled ==========
      - SPRING_DATA_REDIS_SSL_ENABLED=true
      # ========== 对应spring.data.redis.lettuce.pool连接池配置 ==========
      - SPRING_DATA_REDIS_LETTUCE_POOL_MAX_ACTIVE=8
      - SPRING_DATA_REDIS_LETTUCE_POOL_MAX_IDLE=8
      - SPRING_DATA_REDIS_LETTUCE_POOL_MIN_IDLE=0
      - SPRING_DATA_REDIS_LETTUCE_POOL_MAX_WAIT=-1ms
      # 修正时区
      - TZ=Asia/Shanghai
    deploy:
      resources:
        limits:
          # 限制容器最大使用 312M 内存
          memory: 312M
    networks:
      - kiwi-net
    depends_on:
      nacos:
        condition: service_healthy
  kiwi-content:
    image: kiwi-content:v1
    container_name: kiwi-content
    restart: always
    environment:
      # 这里对应上面 YAML 里的变量名
      - NACOS_SERVER_ADDR=nacos:8848
      - SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR=nacos:8848
      - SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR=nacos:8848
      # 限制内存
      - JVM_TOOL_OPTIONS=-Xms128m -Xmx256m
      # ========== 对应spring.data.redis核心连接配置 ==========
      - SPRING_DATA_REDIS_URL=rediss://default:**********@******.upstash.io:6379
      - SPRING_DATA_REDIS_DATABASE=0
      # ========== 对应spring.data.redis.ssl.enabled ==========
      - SPRING_DATA_REDIS_SSL_ENABLED=true
      # ========== 对应spring.data.redis.lettuce.pool连接池配置 ==========
      - SPRING_DATA_REDIS_LETTUCE_POOL_MAX_ACTIVE=8
      - SPRING_DATA_REDIS_LETTUCE_POOL_MAX_IDLE=8
      - SPRING_DATA_REDIS_LETTUCE_POOL_MIN_IDLE=0
      - SPRING_DATA_REDIS_LETTUCE_POOL_MAX_WAIT=-1ms
      # 修正时区
      - TZ=Asia/Shanghai
    deploy:
      resources:
        limits:
          # 限制容器最大使用 312M 内存
          memory: 312M
    networks:
      - kiwi-net
    depends_on:
      nacos:
        condition: service_healthy
  kiwi-link:
    image: kiwi-link:v1
    container_name: kiwi-link
    restart: always
    environment:
      # 这里对应上面 YAML 里的变量名
      - NACOS_SERVER_ADDR=nacos:8848
      - SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR=nacos:8848
      - SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR=nacos:8848
      # 限制内存
      - JVM_TOOL_OPTIONS=-Xms128m -Xmx256m
      # 修正时区
      - TZ=Asia/Shanghai
    deploy:
      resources:
        limits:
          # 限制容器最大使用 312M 内存
          memory: 312M
    networks:
      - kiwi-net
    depends_on:
      nacos:
        condition: service_healthy

7. 部署与运维流程

7.1 Nacos 配置管理

在启动微服务之前,必须先配置 Nacos 中的 Config Service。

  1. 启动 Nacos

    bash 复制代码
    docker-compose up -d nacos
  2. 访问控制台 :浏览器访问 http://<服务器IP>:8848/nacos,使用默认账号密码 nacos/nacos 登录。

  3. 配置录入

  4. 外部数据源配置

    在 Yaml 配置文件中,修改数据源连接地址为云端 SaaS 服务提供的公网地址:

    • MongoDB : spring.data.mongodb.uri=mongodb+srv://user:pass@cluster.mongodb.net/kiwi
    • RabbitMQ : spring.rabbitmq.host=cloud-amqp-url
    • Redis: 虽然 Docker Compose 环境变量已注入,但建议在 Nacos 中也保留一份作为兜底。

7.2 镜像构建与服务启动

  1. 上传 JAR 包到对应的 /opt/kiwihub/services/xxx/ 目录。

  2. 执行构建命令:

    bash 复制代码
    cd /opt/kiwihub/services/user
    docker build -t kiwi-user:v1 .
    
    cd ../content
    docker build -t kiwi-content:v1 .
    
    cd ../link
    docker build -t kiwi-link:v1 .
  3. 全量启动:

    bash 复制代码
    cd /opt/kiwihub
    docker-compose up -d

7.3 验证与排查

  • 查看容器状态

    bash 复制代码
    docker-compose ps

    所有状态应为 Up。如果出现 Exit **,说明触发了内存限制(OOM),需检查 Swap 是否生效或进一步调低 JVM 参数。

  • 查看服务日志

    bash 复制代码
    docker-compose logs -f kiwi-user

    确认日志中出现 Started UserApplication in x.xxx seconds 且成功注册到 Nacos。

  • 防火墙配置

    在阿里云安全组中开放以下端口:

    • 80 (Web访问)
    • 8848 (Nacos控制台)

8. 成果展示与验证

部署完成后,通过浏览器访问服务器 IP。

  1. 首页导航:你将看到自定义的 index.html 页面,列出了三个服务的文档入口。

  2. 接口调试:点击任意一个服务,Nginx 会根据 Referer 自动转发,展示 Knife4j 文档。你可以直接在页面上进行接口测试(例如登录、发帖)。

  3. **内存占用:**实际内存占用情况


9. 总结与心得

  1. 因地制宜:Spring Cloud Gateway 虽然强大,但在小内存场景下,Nginx 才是王者。
  2. 云端借力:不要死磕本地数据库,合理利用云厂商的 Free Tier 可以极大减轻服务器压力。
  3. 容器限制:在 Docker Compose 中配置 deploy.resources.limits 和 JVM 参数 (-Xmx) 是防止服务器宕机的必要手段。
  4. 虚拟内存:Swap 是低配服务器运行 Java 全家桶的底气。

希望这篇笔记能给同样想用低配服务器跑微服务的朋友一些参考!

相关推荐
indexsunny3 小时前
互联网大厂Java面试实战:从Spring Boot到微服务架构的技术问答解析
java·spring boot·redis·微服务·kafka·jwt·flyway
tb_first3 小时前
万字超详细苍穹外卖学习笔记2
java·jvm·数据库·spring·tomcat·maven
1104.北光c°4 小时前
【从零开始学Redis | 第一篇】Redis常用数据结构与基础
java·开发语言·spring boot·redis·笔记·spring·nosql
Hui Baby4 小时前
Java SPI 与 Spring SPI
java·python·spring
岁岁种桃花儿6 小时前
SpringCloud超高质量面试高频题300道题
spring·spring cloud·面试
only-qi7 小时前
微服务场景下,如何实现分布式事务来保证一致性?
分布式·微服务·架构
czlczl200209257 小时前
Spring Data Redis
java·redis·spring
闻哥7 小时前
深入理解 Spring @Conditional 注解:原理与实战
java·jvm·后端·python·spring