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

目标 :具备企业级系统设计、部署、优化和架构能力
学习时长 :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);
        }
    }
}
相关推荐
泡干脆面就番茄5 小时前
Hive入门:架构解析与安装部署实录
大数据·数据仓库·hive·hadoop·架构
yyk333245 小时前
Hive架构
hive·hadoop·架构
霸道流氓气质5 小时前
Open Code Review 入门与实战:三阶段确定性流水线、LLM Agent 混合架构与 CI/CD 代码审查集成
ci/cd·架构·代码复审
阿里云云原生11 小时前
AI 语音一直“正在连接”?用 RUM 排查 WebSocket 建连问题
云原生
loulanyue_15 小时前
从个人提效到团队可验证交付:中宏保险AI Coding流水线重塑与Qoder实战——读中宏保险AI架构总监王志宇2026云栖专场演讲
人工智能·架构
wewowilling17 小时前
认识微服务
java·运维·微服务·springcloud
Erishen17 小时前
Lume C11编译器内部架构:从树形解释器到双LLVM后端的设计之旅
架构·开源·编译器
沪上企服通17 小时前
历史账套科目重映射与规则版本重算:旧账梳理的“口径复位”工程
笔记·架构
亥时无人机管理系统18 小时前
无人机管理系统源码 | 私有化部署平台 航线规划飞行审批实时监控功能介绍
架构·私有化部署·无人机·低空经济·无人机巡检·无人机管理系统·一网统飞