目标 :具备企业级系统设计、部署、优化和架构能力
学习时长 :4 周以上,持续深化
前置要求:完成前五篇,有实际项目经验
目录
- 单体架构与微服务架构
- [Spring Cloud 基础](#Spring Cloud 基础)
- 服务注册与发现
- 配置中心
- [Gateway 网关](#Gateway 网关)
- [OpenFeign 服务调用](#OpenFeign 服务调用)
- 熔断、限流、降级
- 分布式事务
- 分布式锁与幂等性
- [Docker 部署](#Docker 部署)
- [Nginx 反向代理](#Nginx 反向代理)
- [JVM 调优](#JVM 调优)
- 高并发系统设计
- 高可用系统设计
- 面试高频题
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);
}
}
}