第六篇:架构篇 — 微服务、部署、高并发与专家级能力

目标 :具备企业级系统设计、部署、优化和架构能力
学习时长 :4 周以上,持续深化
前置要求:完成前五篇,有实际项目经验


目录

  1. 单体架构与微服务架构
  2. [Spring Cloud 基础](#Spring Cloud 基础)
  3. 服务注册与发现
  4. 配置中心
  5. [Gateway 网关](#Gateway 网关)
  6. [OpenFeign 服务调用](#OpenFeign 服务调用)
  7. 熔断、限流、降级
  8. 分布式事务
  9. 分布式锁与幂等性
  10. [Docker 部署](#Docker 部署)
  11. [Nginx 反向代理](#Nginx 反向代理)
  12. [JVM 调优](#JVM 调优)
  13. 高并发系统设计
  14. 高可用系统设计
  15. 面试高频题

1. 单体架构与微服务架构

对比

维度 单体架构 微服务架构
部署 整体部署 独立部署
技术栈 统一技术栈 可多语言
扩展 整体扩展(浪费资源) 按需扩展
故障影响 一处故障可能拖垮全局 故障隔离
开发协作 代码冲突多 团队边界清晰
复杂度 高(网络、分布式、运维)
适用场景 小团队、早期项目 大团队、高并发、复杂业务

微服务拆分原则

复制代码
1. 按业务领域拆分(DDD 限界上下文)
   用户服务、订单服务、商品服务、支付服务

2. 单一职责:每个服务只做一件事

3. 服务不能直接操作其他服务的数据库

4. 拆分粒度:太粗 → 失去微服务意义;太细 → 运维复杂度爆炸

2. Spring Cloud 基础

核心组件全景

复制代码
┌──────────────────────────────────────────────────────────────────┐
│                     Spring Cloud 生态                              │
├──────────────┬─────────────────┬──────────────┬──────────────────┤
│ 服务注册发现  │    配置中心     │   服务通信    │    流量控制       │
│  Nacos/Eureka│ Nacos/Apollo/  │  OpenFeign   │  Sentinel/       │
│              │ Spring Cloud   │  RestTemplate│  Resilience4j    │
│              │ Config         │              │                  │
├──────────────┼─────────────────┼──────────────┼──────────────────┤
│   API 网关    │  链路追踪       │  消息总线    │    容器化         │
│   Gateway    │  SkyWalking/   │  RabbitMQ/   │  Docker/K8s      │
│              │  Zipkin        │  Kafka       │                  │
└──────────────┴─────────────────┴──────────────┴──────────────────┘

Spring Cloud Alibaba(国内主流)

xml 复制代码
<dependencyManagement>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>2023.0.1.0</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>
</dependencyManagement>

<!-- 注册中心 + 配置中心 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 熔断限流 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

3. 服务注册与发现

Nacos 配置

yaml 复制代码
spring:
  application:
    name: user-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: dev               # 命名空间(环境隔离)
        group: DEFAULT_GROUP

工作原理

复制代码
1. 服务启动 → 向 Nacos 注册(服务名、IP、端口、健康状态)
2. Nacos 维护服务列表,定期心跳检测(默认5秒)
3. 服务消费方 → 从 Nacos 拉取服务列表 → 本地缓存
4. 调用时从缓存中选择实例(负载均衡)
5. Nacos 推送服务变更 → 消费方实时更新缓存

4. 配置中心

Nacos 配置中心

yaml 复制代码
spring:
  config:
    import: nacos:user-service-dev.yml?group=DEFAULT_GROUP
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
        namespace: dev
        file-extension: yml

配置热更新

java 复制代码
@RefreshScope  // 标注此注解的 Bean,配置变更时自动刷新
@RestController
public class ConfigController {
    @Value("${app.feature.switch:false}")
    private boolean featureSwitch;

    @GetMapping("/feature")
    public boolean getFeature() {
        return featureSwitch;  // Nacos 控制台修改配置后,此处自动更新
    }
}

5. Gateway 网关

核心职责

复制代码
外部请求
    ↓
API Gateway(路由 + 鉴权 + 限流 + 日志 + 协议转换)
    ↓           ↓            ↓
用户服务    订单服务      商品服务

配置示例

yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service          # lb:// 表示负载均衡
          predicates:
            - Path=/api/users/**          # 路径匹配
          filters:
            - StripPrefix=1              # 去掉前缀 /api
            - name: RequestRateLimiter   # 限流过滤器
              args:
                redis-rate-limiter.replenishRate: 10    # 令牌桶填充速率
                redis-rate-limiter.burstCapacity: 20

        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
            - Header=X-Api-Version, v2   # Header 匹配

自定义全局过滤器

java 复制代码
@Component
@Order(-1)  // 优先级(越小越先执行)
public class AuthGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");

        // 白名单路径直接放行
        String path = exchange.getRequest().getPath().toString();
        if (path.startsWith("/api/auth/")) {
            return chain.filter(exchange);
        }

        if (token == null || !JwtUtil.isValid(token.replace("Bearer ", ""))) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }
}

6. OpenFeign 服务调用

java 复制代码
// 1. 定义 Feign 客户端接口
@FeignClient(
    name = "user-service",           // 服务名(Nacos 中的名称)
    fallback = UserClientFallback.class  // 熔断降级实现
)
public interface UserClient {

    @GetMapping("/api/users/{id}")
    Result<UserVO> getUserById(@PathVariable Long id);

    @PostMapping("/api/users")
    Result<UserVO> createUser(@RequestBody CreateUserDTO dto);
}

// 2. 降级实现
@Component
public class UserClientFallback implements UserClient {
    @Override
    public Result<UserVO> getUserById(Long id) {
        return Result.fail("用户服务暂不可用,请稍后重试");
    }

    @Override
    public Result<UserVO> createUser(CreateUserDTO dto) {
        return Result.fail("用户服务暂不可用");
    }
}

// 3. 注入使用
@Service
@RequiredArgsConstructor
public class OrderService {
    private final UserClient userClient;

    public OrderVO createOrder(CreateOrderDTO dto) {
        Result<UserVO> userResult = userClient.getUserById(dto.getUserId());
        // 像调本地方法一样调用远程服务
    }
}

7. 熔断、限流、降级

Sentinel 熔断降级

java 复制代码
// 注解方式
@SentinelResource(
    value = "getUserById",
    blockHandler = "handleBlock",      // 触发限流/熔断时的处理
    fallback = "handleFallback"        // 业务异常时的处理
)
public UserVO getUserById(Long id) {
    return userClient.getUserById(id).getData();
}

public UserVO handleBlock(Long id, BlockException ex) {
    log.warn("触发限流/熔断: id={}", id);
    return new UserVO();  // 返回默认值
}

public UserVO handleFallback(Long id, Throwable t) {
    log.error("业务异常降级: id={}, error={}", id, t.getMessage());
    return new UserVO();
}

熔断状态机

复制代码
CLOSED(正常)─────────────────→ OPEN(熔断,快速失败)
(错误率超阈值)                    ↓
      ↑                    (休眠时间后)
      │                            ↓
      └────────────── HALF-OPEN(半开,放少量请求探测)
          (探测成功)                   (探测失败,重新OPEN)

8. 分布式事务

问题场景

复制代码
用户下单(order-service)→ 扣减库存(stock-service)→ 扣减余额(account-service)
三个操作在不同服务(不同数据库),如何保证原子性?

解决方案

Seata AT 模式(推荐,业务无侵入)
java 复制代码
@GlobalTransactional  // 开启全局事务
public void createOrder(OrderDTO dto) {
    // 1. 创建订单(order-service 本地事务)
    orderRepository.save(new Order(dto));

    // 2. 扣减库存(远程调用)
    stockClient.deductStock(dto.getProductId(), dto.getQuantity());

    // 3. 扣减余额(远程调用)
    accountClient.deductBalance(dto.getUserId(), dto.getAmount());

    // 任意步骤失败 → Seata 协调所有参与者回滚
}
Saga 模式(长事务)
复制代码
步骤1成功 → 步骤2成功 → 步骤3成功 → 全局提交
步骤3失败 → 补偿步骤2 → 补偿步骤1 → 全局回滚

适合:长事务、异构系统、对最终一致性可接受

9. 分布式锁与幂等性

Redis 分布式锁(参见 chapter06)

java 复制代码
// 核心命令:SET key value NX PX expire
// NX:不存在才设置(互斥)
// PX:毫秒级过期(防死锁)
// value:UUID(释放时验证,防误删)

// 释放锁:Lua 脚本保证原子性
if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end

// 生产推荐:Redisson(自带看门狗、可重入、红锁)
RLock lock = redisson.getLock("myLock");
lock.lock(10, TimeUnit.SECONDS);
try {
    // 业务代码
} finally {
    lock.unlock();
}

幂等性设计

复制代码
接口幂等 = 同一请求多次调用,结果一致

实现方案:
1. Token 机制(防重复提交):见 chapter06 IdempotentAspect
2. 数据库唯一索引:INSERT 时触发唯一键冲突
3. 乐观锁:版本号机制
4. 状态机:只有特定状态才能流转
5. 请求去重:Redis 记录已处理的请求 ID

10. Docker 部署

Dockerfile

dockerfile 复制代码
# 多阶段构建(减小最终镜像体积)
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:resolve     # 缓存依赖层
COPY src ./src
RUN mvn clean package -DskipTests

FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080

# JVM 调优参数
ENV JAVA_OPTS="-Xms512m -Xmx1g -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

docker-compose.yml

yaml 复制代码
version: '3.8'
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/mydb
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_started

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: mydb
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

volumes:
  mysql_data:
  redis_data:

11. Nginx 反向代理

nginx 复制代码
# /etc/nginx/conf.d/myapp.conf

upstream backend {
    # 负载均衡(轮询)
    server app1:8080 weight=3;
    server app2:8080 weight=1;
    server app3:8080 backup;      # 备用节点
    keepalive 32;                  # 保持连接池
}

server {
    listen 80;
    server_name api.example.com;
    return 301 https://$host$request_uri;  # HTTP → HTTPS
}

server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate     /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;

    # 限流(每个 IP 每秒最多10个请求)
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

    location /api/ {
        limit_req zone=api burst=20 nodelay;
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_connect_timeout 10s;
        proxy_read_timeout 60s;
    }

    location /static/ {
        root /var/www;
        expires 30d;           # 静态资源缓存30天
        add_header Cache-Control "public, immutable";
    }
}

12. JVM 调优

常用 JVM 参数

bash 复制代码
java -jar app.jar \
  # 堆内存
  -Xms2g                          # 初始堆大小
  -Xmx2g                          # 最大堆大小(与初始相同,避免动态扩缩)
  -XX:NewRatio=2                  # 老年代:新生代 = 2:1

  # GC 选择
  -XX:+UseG1GC                    # G1(JDK 9+ 默认,推荐)
  -XX:MaxGCPauseMillis=200        # 最大GC停顿时间目标

  # JDK 21+ 使用 ZGC(低延迟)
  -XX:+UseZGC

  # 元空间
  -XX:MetaspaceSize=256m
  -XX:MaxMetaspaceSize=512m

  # OOM 时 dump 堆
  -XX:+HeapDumpOnOutOfMemoryError
  -XX:HeapDumpPath=/logs/heapdump.hprof

  # GC 日志
  -Xlog:gc*:file=/logs/gc.log:time,uptime:filecount=5,filesize=20m

GC 日志分析

bash 复制代码
# 使用 GCEasy(在线)或 JVM 自带工具分析
jstat -gc <pid> 1000   # 每秒打印一次GC统计
jmap -heap <pid>       # 查看堆使用情况
jstack <pid>           # 线程快照(排查死锁/CPU飙高)

13. 高并发系统设计

核心技术手段

复制代码
提升并发的三板斧:
1. 缓存:减少数据库压力(Redis、本地缓存)
2. 异步:解耦、削峰(消息队列、@Async)
3. 分库分表:水平扩展(ShardingSphere)

高并发常用方案:
┌─────────────────────────────────────────┐
│  客户端                                  │
├─────────────────────────────────────────┤
│  CDN(静态资源)                           │
├─────────────────────────────────────────┤
│  Nginx(负载均衡 + 限流)                  │
├─────────────────────────────────────────┤
│  Gateway(认证 + 路由 + 限流)             │
├─────────────────────────────────────────┤
│  应用层(多实例水平扩展)                   │
├──────────────────┬──────────────────────┤
│  Redis 缓存层    │  消息队列(削峰)        │
├──────────────────┼──────────────────────┤
│  数据库(读写分离 + 分库分表)              │
└─────────────────────────────────────────┘

秒杀系统设计要点

java 复制代码
// 1. 前端:限流(按钮置灰、验证码)
// 2. 网关:IP 限流、黑名单
// 3. 应用层:Redis 预减库存(原子操作)
//    DECR stock:product:1  → 若结果 < 0,直接返回"售罄"
// 4. 消息队列:异步创建订单(解耦削峰)
// 5. 数据库:最终一致库存扣减(WITH 乐观锁)

// Redis 原子扣减库存
String luaScript = """
    local stock = redis.call('get', KEYS[1])
    if not stock or tonumber(stock) <= 0 then
        return -1
    end
    return redis.call('decr', KEYS[1])
    """;
Long result = redisTemplate.execute(
    RedisScript.of(luaScript, Long.class),
    List.of("stock:product:" + productId));

14. 高可用系统设计

高可用指标

复制代码
可用性 = 正常运行时间 / 总时间

99%    → 年停机 3.65 天(不可接受)
99.9%  → 年停机 8.76 小时(一般水平)
99.99% → 年停机 52.6 分钟(高可用)
99.999%→ 年停机 5.26 分钟(极高可用)

高可用设计要点

复制代码
1. 消除单点故障(SPOF)
   - 应用层:多实例部署 + 负载均衡
   - Redis:主从复制 + 哨兵 / Cluster
   - MySQL:主从复制 + MHA/MGR
   - Nacos:集群部署(3节点以上)

2. 熔断降级
   - 上游服务故障 → 快速失败,不让故障扩散
   - 降级:返回缓存数据、默认值或友好提示

3. 限流保护
   - 超出系统承载能力的请求直接拒绝
   - 保护核心业务,宁可部分用户失败

4. 数据多副本
   - 数据库主从、异地多活
   - Redis Cluster 数据分片 + 副本

5. 故障恢复
   - 健康检查(Spring Actuator + K8s Probe)
   - 自动重启(K8s RestartPolicy)
   - 灰度发布(Blue-Green / Canary)

15. 面试高频题

Q1:微服务和单体架构如何选型?

团队 < 10人、业务简单、初期产品用单体;团队大、业务复杂、高并发、独立部署诉求强时用微服务。微服务解决了扩展性问题,但引入了分布式复杂性(网络、一致性、运维)。

Q2:如何保证 Redis 分布式锁的安全性?

①SET NX PX(原子操作获取锁);②value 用 UUID(释放时验证防误删);③Lua 脚本保证释放的原子性;④生产使用 Redisson(看门狗续期、可重入)。

Q3:CAP 理论是什么?

分布式系统中,一致性©、可用性(A)、分区容错性§ 三者只能同时满足两个。网络分区§必须容忍,所以只能选 CP 或 AP。Nacos 支持 CP+AP 切换,Redis 是 AP,ZooKeeper 是 CP。

Q4:如何解决分布式事务?

①Seata AT(无侵入,适合简单场景);②TCC(Try-Confirm-Cancel,强一致);③Saga(最终一致,适合长事务);④本地消息表+消息队列(可靠消息,最终一致)。

Q5:服务注册中心挂了会怎样?

服务消费方有本地缓存,短期内仍可调用;但新注册的服务无法被发现,已下线的服务不能及时感知。Nacos 客户端有 failover 机制,会优先读本地快照。

Q6:JVM 什么时候 Full GC?如何排查?

老年代空间不足、元空间耗尽、主动调用 System.gc()。排查:jstat -gcutil <pid> 观察老年代使用率;jmap -histo <pid> 查看对象分布;开启 GC 日志分析。

Q7:高并发下如何防止库存超卖?

①数据库乐观锁(version);②Redis 原子 DECR 预减库存;③数据库 UPDATE stock = stock - ? WHERE id = ? AND stock >= ?;④分布式锁(性能差)。

Q8:什么是 DDD 领域驱动设计?

以业务领域为核心进行建模,核心概念:限界上下文(服务边界)、聚合根(事务边界)、领域事件(跨聚合通信)、值对象(无身份的不变对象)。适合复杂业务系统的微服务拆分。


上一篇:05_原理篇


附录:六篇学习路径与项目实战

复制代码
阶段一(2周):入门篇 → 实战:博客系统基础 CRUD
阶段二(2周):核心篇 → 实战:添加校验/异常处理/Swagger
阶段三(2周):数据篇 → 实战:引入 MyBatis-Plus、分页、事务
阶段四(3周):进阶篇 → 实战:Redis缓存、JWT、权限控制
阶段五(2周):原理篇 → 深读源码:自动配置、AOP、Bean生命周期
阶段六(4周+):架构篇 → 实战:拆分微服务、K8s部署、高并发压测
篇章 实战项目 核心技术
入门篇 用户管理系统 REST API + JPA + H2
核心篇 接口规范化 统一返回 + 全局异常 + Swagger
数据篇 商品管理系统 MyBatis-Plus + 事务 + 乐观锁
进阶篇 秒杀系统 Redis + JWT + 限流 + 消息队列
原理篇 自定义框架 自定义 Starter + AOP + 条件装配
架构篇 电商微服务 Spring Cloud + Docker + 分布式锁

16. 链路追踪:SkyWalking 接入(专家必知)

知识点 1:为什么需要链路追踪

微服务架构中,一次用户请求可能经过:

text 复制代码
用户 → Gateway → 订单服务 → 库存服务 → 支付服务 → 消息队列 → 通知服务

问题:某个接口耗时3秒,哪个服务慢?哪段调用出了问题?
没有链路追踪:逐个服务查日志,可能花1小时才能定位
有链路追踪:打开 SkyWalking 控制台,一眼看出瓶颈在哪里

核心概念

概念 说明
Trace 一次完整请求的全链路(贯穿所有服务)
Span 链路中的一个操作节点(如一次RPC、一次DB查询)
TraceId 全链路唯一标识(通过请求头传递)
SpanId 当前 Span 的标识

知识点 2:SkyWalking Agent 接入(无代码侵入)

SkyWalking 通过 Java Agent 字节码增强,无需修改业务代码:

bash 复制代码
# 下载 SkyWalking Agent
# https://skywalking.apache.org/downloads/

# 启动时添加 Agent 参数
java -javaagent:/path/to/skywalking-agent/skywalking-agent.jar \
     -Dskywalking.agent.service_name=order-service \
     -Dskywalking.collector.backend_service=127.0.0.1:11800 \
     -jar order-service.jar

Docker 部署配置

dockerfile 复制代码
FROM eclipse-temurin:17-jre-alpine
# 复制 SkyWalking Agent
COPY skywalking-agent/ /skywalking-agent/
COPY target/*.jar app.jar

ENV SW_AGENT_NAME=order-service
ENV SW_AGENT_COLLECTOR_BACKEND_SERVICES=skywalking-oap:11800
ENV JAVA_OPTS="-javaagent:/skywalking-agent/skywalking-agent.jar"

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

知识点 3:手动传递 TraceId 到日志

xml 复制代码
<!-- logback-spring.xml:在日志中打印 TraceId -->
<pattern>%d{HH:mm:ss} [%thread] %-5level %logger - [TraceId:%X{tid}] %msg%n</pattern>

SkyWalking Agent 会自动将 TraceId 注入 MDC(tid 键),日志中即可打印,方便日志与链路关联查询。


17. 优雅停机:零中断部署(生产必备)

知识点 1:不优雅停机会发生什么

text 复制代码
kill -9 直接杀进程:
  - 正在处理中的 HTTP 请求 → 客户端收到 502/连接断开
  - 正在消费的 MQ 消息 → 消息未 ACK,重新入队(可能重复消费)
  - 正在写数据库 → 事务未提交,数据不一致
  - 线程池中的任务 → 直接丢弃

知识点 2:Spring Boot 优雅停机配置

yaml 复制代码
server:
  shutdown: graceful          # 开启优雅停机(Spring Boot 2.3+)

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s  # 等待进行中请求完成的最长时间

优雅停机流程

text 复制代码
接收到 SIGTERM 信号
  → Spring Boot 停止接收新请求(返回 503)
  → 等待进行中的请求完成(最多30秒)
  → 执行 @PreDestroy 方法(释放连接池、关闭消费者等)
  → JVM 正常退出

配合 Kubernetes 的配置

yaml 复制代码
# Kubernetes Deployment 配置
spec:
  template:
    spec:
      terminationGracePeriodSeconds: 60  # K8s 给容器的最长关闭时间(要大于30s)
      containers:
        - lifecycle:
            preStop:
              exec:
                command: ["sleep", "5"]  # 给 LB 5秒时间摘除实例,避免新请求进来

知识点 3:MessageQueue 消费者的优雅停机

java 复制代码
@Component
public class OrderConsumer implements DisposableBean {

    private volatile boolean running = true;
    private final RabbitListenerEndpointRegistry registry;

    // 停机时暂停消费者,等待当前消息处理完
    @Override
    public void destroy() {
        running = false;
        log.info("收到停机信号,暂停 MQ 消费者...");
        registry.getListenerContainer("orderQueueContainer").stop();
        // 等待正在处理的消息完成(配合 shutdown timeout)
        log.info("MQ 消费者已停止");
    }
}

18. 生产监控:Prometheus + Grafana 体系

知识点:Spring Boot 接入 Prometheus 监控

yaml 复制代码
# application.yml
management:
  endpoints:
    web:
      exposure:
        include: "health,prometheus,metrics"
  metrics:
    tags:
      app: ${spring.application.name}
      env: ${spring.profiles.active:dev}
    distribution:
      percentiles-histogram:
        http.server.requests: true  # 开启 P50/P95/P99 直方图
      slo:
        http.server.requests: 50ms, 200ms, 500ms  # SLO 阈值

Prometheus 采集配置

yaml 复制代码
# prometheus.yml
scrape_configs:
  - job_name: 'spring-boot-apps'
    metrics_path: '/actuator/prometheus'
    scrape_interval: 15s
    static_configs:
      - targets:
          - 'order-service:8080'
          - 'user-service:8080'
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance

关键告警规则(Prometheus Alert Rules)

yaml 复制代码
# alert-rules.yml
groups:
  - name: spring-boot-alerts
    rules:
      # 接口 P99 超过 1 秒
      - alert: HighResponseTime
        expr: histogram_quantile(0.99, rate(http_server_requests_seconds_bucket[5m])) > 1
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "{{ $labels.app }} 接口响应时间过高"
          description: "P99={{ $value | humanizeDuration }}"

      # JVM 堆内存使用超过 80%
      - alert: HighJvmHeapUsage
        expr: jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} > 0.8
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "{{ $labels.app }} JVM 堆内存告警"

      # 错误率超过 5%
      - alert: HighErrorRate
        expr: rate(http_server_requests_seconds_count{status=~"5.."}[5m])
              / rate(http_server_requests_seconds_count[5m]) > 0.05
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "{{ $labels.app }} 错误率超过 5%"

推荐 Grafana Dashboard :导入 ID 12900(Spring Boot 2.1 Statistics)或 19004(Spring Boot 3.x)。


19. 分布式 ID 生成方案

知识点 1:为什么不用数据库自增 ID

text 复制代码
数据库自增 ID 的问题(分布式环境):

1. 单点:ID 生成依赖单个数据库,有性能瓶颈
2. 分库分表:多个数据库各自自增,ID 重复
3. 信息泄露:连续 ID 暴露了业务量(竞争对手可推断订单量)
4. 导入困难:数据迁移时 ID 可能冲突

分布式 ID 要求:
  全局唯一、趋势递增(索引友好)、高性能、不依赖中心节点

知识点 2:雪花算法原理与实现

text 复制代码
Twitter Snowflake 64位 ID 结构:

| 1位符号位 | 41位时间戳(毫秒)| 10位机器ID | 12位序列号 |
      0         ←毫秒时间戳→       ←机器→    ←并发序列→

- 41位时间戳:可用约 69 年
- 10位机器ID:最多 1024 台机器(5位数据中心 + 5位机器)
- 12位序列号:同一毫秒内最多生成 4096 个 ID
- 总计:每毫秒可生成 4096 × 1024 = 4,194,304 个全局唯一 ID
java 复制代码
/**
 * 雪花算法实现
 */
@Component
public class SnowflakeIdGenerator {

    private final long workerId;
    private final long datacenterId;

    private long lastTimestamp = -1L;
    private long sequence = 0L;

    // 位数定义
    private static final long SEQUENCE_BITS = 12L;
    private static final long WORKER_ID_BITS = 5L;
    private static final long DATACENTER_ID_BITS = 5L;

    // 最大值
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);       // 31
    private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS); // 31
    private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);           // 4095

    // 左移位数
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;                         // 12
    private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;    // 17
    private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS; // 22

    // 起始时间戳(2024-01-01 00:00:00)减少 ID 长度
    private static final long EPOCH = 1704038400000L;

    public SnowflakeIdGenerator(
            @Value("${snowflake.worker-id:1}") long workerId,
            @Value("${snowflake.datacenter-id:1}") long datacenterId) {
        if (workerId > MAX_WORKER_ID || workerId < 0)
            throw new IllegalArgumentException("workerId 超出范围 [0, " + MAX_WORKER_ID + "]");
        if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0)
            throw new IllegalArgumentException("datacenterId 超出范围");
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    public synchronized long nextId() {
        long currentTimestamp = System.currentTimeMillis();

        // 时钟回拨检测(NTP 同步可能导致时钟回拨)
        if (currentTimestamp < lastTimestamp) {
            throw new RuntimeException("时钟回拨,拒绝生成 ID,回拨时间: "
                + (lastTimestamp - currentTimestamp) + "ms");
        }

        if (currentTimestamp == lastTimestamp) {
            // 同一毫秒内,序列号 +1
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if (sequence == 0) {
                // 序列号溢出,等待下一毫秒
                currentTimestamp = waitNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;  // 新的毫秒,序列号重置为 0
        }

        lastTimestamp = currentTimestamp;

        return ((currentTimestamp - EPOCH) << TIMESTAMP_SHIFT)
            | (datacenterId << DATACENTER_ID_SHIFT)
            | (workerId << WORKER_ID_SHIFT)
            | sequence;
    }

    private long waitNextMillis(long lastTs) {
        long ts = System.currentTimeMillis();
        while (ts <= lastTs) ts = System.currentTimeMillis();
        return ts;
    }
}

// 使用
@Service
@RequiredArgsConstructor
public class OrderService {
    private final SnowflakeIdGenerator idGenerator;

    public Order createOrder(CreateOrderDTO dto) {
        long orderId = idGenerator.nextId(); // 生成唯一订单ID
        // orderId 是 long 型,注意返回给前端时转 String(JS 精度问题)
    }
}

20. 接口限流:令牌桶与漏桶算法

知识点 1:两种限流算法对比

text 复制代码
漏桶算法(Leaky Bucket):
  请求进入漏桶,以固定速率流出(处理)
  优点:输出速率恒定(保护下游)
  缺点:无法处理突发流量(即使瞬间有空闲容量)
  适合:需要严格控制输出速率的场景(如接口调用计费)

令牌桶算法(Token Bucket):
  令牌以固定速率填入桶,请求消耗令牌
  优点:允许突发(桶满时积累的令牌可以被快速消耗)
  缺点:需要额外存储桶状态
  适合:允许适度突发的限流(API 限流,大多数场景)

Sentinel 和 Gateway 都用令牌桶算法(排队等待模式)

知识点 2:Guava RateLimiter 单机限流

java 复制代码
@RestController
@RequestMapping("/api/sms")
public class SmsController {

    // 令牌桶:每秒最多5个请求(应用级别,不适合分布式)
    private final RateLimiter rateLimiter = RateLimiter.create(5.0);

    @PostMapping("/send")
    public Result<Void> sendSms(@RequestParam String phone) {
        // 尝试获取令牌(立即返回,不等待)
        if (!rateLimiter.tryAcquire()) {
            return Result.fail(429, "请求过于频繁,请稍后再试");
        }
        // 获取令牌(等待直到有令牌,适合削峰场景)
        // rateLimiter.acquire();

        smsService.send(phone);
        return Result.success();
    }
}

知识点 3:基于 Redis 的分布式限流(滑动窗口)

java 复制代码
@Component
@RequiredArgsConstructor
public class RateLimiter {

    private final StringRedisTemplate redis;

    /**
     * 滑动窗口限流
     * @param key    限流 key(如 ip:192.168.1.1 或 user:123)
     * @param limit  时间窗口内最大请求数
     * @param windowSeconds 时间窗口(秒)
     * @return true=允许,false=拒绝
     */
    public boolean isAllowed(String key, int limit, int windowSeconds) {
        long now = System.currentTimeMillis();
        long windowStart = now - windowSeconds * 1000L;
        String redisKey = "rate:limit:" + key;

        // Lua 脚本保证原子性
        String script = """
            local key = KEYS[1]
            local now = tonumber(ARGV[1])
            local windowStart = tonumber(ARGV[2])
            local limit = tonumber(ARGV[3])
            local expire = tonumber(ARGV[4])
            
            -- 删除窗口外的旧请求记录
            redis.call('ZREMRANGEBYSCORE', key, '-inf', windowStart)
            -- 统计当前窗口内的请求数
            local count = redis.call('ZCARD', key)
            
            if count < limit then
                -- 未超限:记录本次请求
                redis.call('ZADD', key, now, now)
                redis.call('EXPIRE', key, expire)
                return 1  -- 允许
            else
                return 0  -- 拒绝
            end
            """;

        Long result = redis.execute(
            new DefaultRedisScript<>(script, Long.class),
            List.of(redisKey),
            String.valueOf(now),
            String.valueOf(windowStart),
            String.valueOf(limit),
            String.valueOf(windowSeconds * 2)
        );

        return Long.valueOf(1L).equals(result);
    }
}

// 配合注解使用
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    int limit() default 100;       // 时间窗口内最大请求数
    int window() default 60;       // 时间窗口(秒)
    String keyPrefix() default ""; // 限流key前缀
}

@Aspect
@Component
@RequiredArgsConstructor
public class RateLimitAspect {

    private final RateLimiter rateLimiter;

    @Around("@annotation(rateLimit)")
    public Object doRateLimit(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {
        // 默认按用户ID限流(也可以按IP)
        String userId = String.valueOf(UserContext.get().getId());
        String key = rateLimit.keyPrefix() + ":" + userId;

        if (!rateLimiter.isAllowed(key, rateLimit.limit(), rateLimit.window())) {
            throw new BusinessException(429, "操作过于频繁,请" + rateLimit.window() + "秒后重试");
        }
        return point.proceed();
    }
}

// 使用示例
@RestController
public class SmsController {

    @PostMapping("/api/sms/send")
    @RateLimit(limit = 5, window = 60, keyPrefix = "sms:send")  // 每用户每分钟最多5次
    public Result<Void> sendSms(@RequestParam String phone) {
        smsService.send(phone);
        return Result.success();
    }
}

21. 高并发设计:异步解耦与削峰填谷

知识点:秒杀场景的技术方案

text 复制代码
秒杀核心挑战:
  10万并发请求 → 扣减库存 → 创建订单
  若不处理:数据库被打垮,超卖,系统崩溃

分层防护方案:

层1 - 前端限流:
  点击后按钮置灰(防止重复提交)
  倒计时,秒杀未开始时接口不可用

层2 - Gateway 限流:
  Redis 令牌桶,超过 QPS 阈值直接拒绝(快速失败)

层3 - 库存预减(Redis):
  秒杀开始前,将库存加载到 Redis
  收到请求时,先 Redis 原子扣减
  Redis 库存 ≤ 0,直接返回"已售罄"(拒绝90%+的无效请求)

层4 - 异步下单(MQ):
  Redis 扣减成功的请求,发送消息到 MQ(返回"排队中")
  后台消费者从 MQ 消费,创建真正的数据库订单

层5 - 幂等保障:
  Redis SETNX 防止用户重复下单
  数据库唯一索引(user_id + product_id)兜底
java 复制代码
@RestController
@RequiredArgsConstructor
public class SeckillController {

    private final StringRedisTemplate redis;
    private final RabbitTemplate rabbitTemplate;

    @PostMapping("/api/seckill/{productId}")
    public Result<String> seckill(@PathVariable Long productId,
                                   @AuthenticationPrincipal UserDetails user) {
        String userId = user.getUsername();

        // 1. 幂等检查(每人只能下单一次)
        String orderKey = "seckill:order:" + productId + ":" + userId;
        Boolean isFirst = redis.opsForValue().setIfAbsent(orderKey, "1", 1, TimeUnit.HOURS);
        if (!Boolean.TRUE.equals(isFirst)) {
            return Result.fail(409, "您已参与过本次秒杀");
        }

        // 2. Redis 原子扣减库存
        Long stock = redis.opsForValue().decrement("seckill:stock:" + productId);
        if (stock == null || stock < 0) {
            // 恢复幂等标记(允许用户重试,此时是真的没库存了)
            redis.delete(orderKey);
            return Result.fail(200, "库存不足,活动结束");
        }

        // 3. 异步下单(发 MQ,快速返回)
        SeckillMessage msg = new SeckillMessage(productId, Long.parseLong(userId));
        rabbitTemplate.convertAndSend("seckill.exchange", "seckill.order", msg);

        return Result.success("下单成功,请等待订单确认");
    }
}

// 异步订单消费者
@Component
@RabbitListener(queues = "seckill.order.queue")
@RequiredArgsConstructor
@Slf4j
public class SeckillOrderConsumer {

    private final OrderService orderService;

    @RabbitHandler
    public void handleSeckillOrder(SeckillMessage msg, Channel channel,
                                    @Header(AmqpHeaders.DELIVERY_TAG) long tag) {
        try {
            orderService.createSeckillOrder(msg.getProductId(), msg.getUserId());
            channel.basicAck(tag, false);
        } catch (Exception e) {
            log.error("秒杀订单处理失败: {}", msg, e);
            // 入死信队列等待人工处理
            channel.basicNack(tag, false, false);
        }
    }
}
相关推荐
Wave8451 小时前
基于 STM32 + ESP8266 + W25Q64 的双核 OTA 底层架构总结
stm32·嵌入式硬件·架构
yongyoudayee2 小时前
CRM架构演进:从记录系统到执行引擎的技术解析
架构
源码宝3 小时前
基于 SpringBoot + Vue 的医院随访系统:技术架构与功能实现
java·vue.js·spring boot·架构·源码·随访系统·随访管理
有马贵将3 小时前
【5】微前端知识点总结
前端·架构
ting94520004 小时前
深入解析 Social Fetch 机制:原理、架构、应用场景、实战落地与性能优化全攻略
人工智能·性能优化·架构
ZOOOOOOU4 小时前
云边端协同架构下,门禁权限引擎的离线决策与策略续存实现
大数据·人工智能·架构
Java后端的Ai之路5 小时前
Kubernetes是什么?(小白入门版)
云原生·容器·kubernetes·教程
heimeiyingwang5 小时前
【架构实战】编排vs协同:微服务通信架构选型
微服务·云原生·架构
空中海5 小时前
第二篇:注册中心篇 — Nacos 与 Eureka 服务注册发现
spring boot·云原生·eureka