← 返回场景决策
1. Docker — 容器化决策
Docker = 集装箱
→
应用和所有依赖打包在一起,到哪都能跑
不用Docker = 散装运输
→
每台机器都要装环境,"在我电脑上能跑"
✅ 什么时候用 Docker
- 环境一致性:开发/测试/生产环境统一 — 消除"我电脑上能跑"
- 微服务部署:每个服务一个容器 — 独立部署/扩缩容
- CI/CD:构建产物就是 Docker 镜像 — 一致性保证
- 快速启动:秒级启动 — 比虚拟机快100倍
❌ 什么时候不用 Docker
- GUI 应用:桌面软件 — Docker 不适合跑GUI
- 高性能计算:需要直接操作硬件/GPU — 容器有隔离开销
- 数据存储:数据库容器化需谨慎 — 数据持久化复杂
- 极简项目:就一个 jar 包 — 直接 java -jar 更简单
💻 Spring Boot 多阶段构建
# Dockerfile — 多阶段构建,减小镜像体积
FROM eclipse-temurin:17-jdk-alpine AS build
WORKDIR /app
COPY . .
RUN ./mvnw package -DskipTests
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
# 非root用户运行
RUN addgroup -S app && adduser -S app -G app
USER app
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
# 构建和运行
# docker build -t myapp:1.0 .
# docker run -p 8080:8080 myapp:1.0
面试金句
多阶段构建(Multi-stage Build):第一阶段用 JDK 编译打包,第二阶段只 COPY jar 到 JRE 镜像中,最终镜像不含 Maven 缓存和源码,体积从 800MB+ 降至 ~200MB。非 root 用户运行是容器安全的基本要求,防止容器逃逸后获取宿主机 root 权限。
💻 Docker Compose 本地开发
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports: ["8080:8080"]
environment:
SPRING_PROFILES_ACTIVE: dev
DB_HOST: mysql
REDIS_HOST: redis
depends_on: [mysql, redis]
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: app_dev
ports: ["3306:3306"]
redis:
image: redis:7-alpine
ports: ["6379:6379"]
# 一键启动全套环境
# docker-compose up -d
面试金句
docker-compose 定义多容器应用的编排方式:depends_on 声明启动顺序但不等待就绪(MySQL 容器启动 ≠ MySQL 可连接),生产环境需配合 healthcheck 或重试机制。服务名即 DNS 名,app 容器内可用 mysql:3306 直接访问数据库,无需 IP。
2. Kubernetes — 容器编排决策
K8s = 自动化港口
→
集装箱(容器)来了,自动调度、扩缩容、自愈
✅ 什么时候用 K8s
- 微服务 > 5 个:服务多需要统一管理 — K8s 自动调度
- 弹性扩缩容:流量高峰自动扩容 — HPA (Horizontal Pod Autoscaler)
- 高可用:服务挂了自动重启 — 自愈能力
- 多环境:dev/staging/prod 统一管理 — Namespace 隔离
❌ 什么时候不用 K8s
- 小项目 < 3 个服务:K8s 运维成本远大于收益 — Docker Compose 就够
- 没有运维团队:K8s 学习曲线极陡 — 出问题没人会修
- 单机部署:一台服务器跑所有服务 — 没必要编排
💻 K8s 部署 YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 3 # 3个副本
selector:
matchLabels: { app: order-service }
template:
metadata:
labels: { app: order-service }
spec:
containers:
- name: order-service
image: registry.example.com/order-service:1.0.0
ports: [{ containerPort: 8080 }]
resources:
requests: { memory: "512Mi", cpu: "200m" }
limits: { memory: "1Gi", cpu: "500m" }
livenessProbe: # 存活检查
httpGet: { path: /actuator/health/liveness, port: 8080 }
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe: # 就绪检查
httpGet: { path: /actuator/health/readiness, port: 8080 }
initialDelaySeconds: 10
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector: { app: order-service }
ports: [{ port: 80, targetPort: 8080 }]
---
# HPA 自动扩缩容
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-service-hpa
spec:
scaleTargetRef: { apiVersion: apps/v1, kind: Deployment, name: order-service }
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource: { name: cpu, target: { type: Utilization, averageUtilization: 70 } }
面试金句
resources.requests 是调度依据(K8s 据此决定 Pod 放哪个 Node),limits 是硬上限(超过 memory limit 触发 OOMKilled)。HPA 根据 CPU 利用率(实际/request)自动调整副本数,阈值为 70% 意味着平均每个 Pod CPU 使用率达到 70% 就触发扩容。Liveness 失败→重启 Pod,Readiness 失败→摘除流量但不重启。
3. 部署策略决策
📊 部署策略决策表
| 策略 | 原理 | 停机时间 | 回滚速度 | 资源消耗 | 适合 |
| 滚动更新 | 逐步替换旧Pod | 零 | 中 | 低 | 默认策略 |
| 蓝绿部署 | 两套环境切换 | 零 | 秒级切换 | 2倍 | 关键服务 |
| 金丝雀发布 | 小流量先验证 | 零 | 快 | 中 | 高风险变更 |
| 重建 | 先停旧再启新 | 有 | 中 | 低 | 开发环境 |
💻 金丝雀发布 Nginx 配置
# Nginx 金丝雀:10%流量到新版本
upstream backend {
server old-service:8080 weight=9; # 90%流量
server new-service:8080 weight=1; # 10%流量
}
# K8s 金丝雀(更精细控制)
# kubectl apply -f canary-deployment.yaml
# 观察10分钟,无异常则逐步增加流量
# kubectl scale deployment order-service-canary --replicas=5
# 全部正常后,删除旧版本
面试金句
金丝雀发布(Canary Release):先让小比例流量(如 10%)打到新版本,观察错误率/延迟/业务指标无异常后再逐步放量。Nginx weight 实现简单加权轮询,无法精确控制流量比例;K8s 通过副本数比例(如 old:9 replicas + new:1 replica ≈ 10%)实现更精细的金丝雀。
4. 回滚决策
✅ 什么时候回滚
- 发布后立即报错:大量5xx错误 — 不回滚等什么?
- 性能严重劣化:响应时间翻倍 — 影响用户体验
- 数据错误:新版本有数据Bug — 回滚+修数据
- 核心功能不可用:登录/支付不能用 — 立即回滚
💻 K8s 回滚命令
# 查看部署历史
kubectl rollout history deployment/order-service
# 回滚到上一个版本
kubectl rollout undo deployment/order-service
# 回滚到指定版本
kubectl rollout undo deployment/order-service --to-revision=3
# 查看回滚状态
kubectl rollout status deployment/order-service
面试金句
K8s 滚动更新(Rolling Update)默认保留 10 个 revision 历史(revisionHistoryLimit),每次 kubectl apply 创建一个新 revision。undo 实际上是创建一个"反向"滚动更新,把 Pod 逐步替换回旧镜像。关键前提:镜像 tag 必须不同(不能用 latest),否则 K8s 无法区分版本。
💻 Docker 回滚
# 查看镜像版本
docker images myapp
# 回滚:重新运行旧版本镜像
docker stop myapp-container
docker rm myapp-container
docker run -d --name myapp-container -p 8080:8080 myapp:1.0.0 # 旧版本
面试金句
Docker 回滚 = 重新运行旧版本镜像。与 K8s 的渐进式滚动回滚不同,纯 Docker 环境的回滚会有短暂停机(stop→rm→run 期间服务不可用)。最佳实践:镜像 tag 使用语义化版本号(如 1.2.3),永远不要删旧版本镜像,至少保留最近 3 个可用版本。
5. 日志收集 — 4 大方案对比
📊 日志方案决策表
| 方案 | 组件 | 适合 | 复杂度 |
| ELK | Elasticsearch + Logstash + Kibana | 大中型项目 | 高 |
| EFK | Elasticsearch + Fluentd + Kibana | K8s环境 | 中 |
| Loki + Grafana | Loki + Promtail + Grafana | 轻量级首选 | 低 |
| 云厂商日志 | 阿里云SLS / 腾讯云CLS | 上云项目 | 最低 |
💡
怎么选:中小团队 →
Loki + Grafana(轻、便宜、一个面板看日志+监控);要全文搜索 → ELK;上云不想运维 → SLS/CLS。
→ 详见:日志收集方案详解
💻 Spring Boot 日志配置
// logback-spring.xml — JSON格式日志(方便ELK解析)
<configuration>
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdc>true</includeMdc> // MDC 中的 traceId
</encoder>
</appender>
// 代码中设置 traceId
MDC.put("traceId", traceId);
// 日志输出:
// {"@timestamp":"2024-01-15T10:30:00","level":"INFO",
// "traceId":"abc123","logger":"OrderService",
// "message":"订单创建成功"}
</configuration>
面试金句
JSON 结构化日志是 ELK/Loki 等日志平台高效检索的基础:传统文本日志需要 Grok 正则解析(脆弱且消耗 CPU),JSON 日志可直接被索引为字段。MDC(Mapped Diagnostic Context)是 ThreadLocal 级别的日志上下文,存放 traceId/userId 等链路标识,Logback 自动将 MDC 内容输出为 JSON 字段。
6. 监控告警决策
📊 监控方案决策表
| 维度 | 工具 | 监控什么 | 告警方式 |
| 基础设施 | Prometheus + Grafana | CPU/内存/磁盘/网络 | AlertManager → 钉钉/企微 |
| 应用性能 | SkyWalking/Pinpoint | 接口耗时/错误率/调用链 | 内置告警 |
| 日志监控 | ELK/Loki | ERROR日志量/关键字 | ElastAlert/Loki ruler |
| 业务监控 | 自定义Dashboard | 订单量/支付成功率 | 异常下降告警 |
| 容器监控 | cAdvisor + Prometheus | 容器CPU/内存/OOM | Pod重启告警 |
⚠️
告警设计原则
- 分级:P0(电话) / P1(短信) / P2(即时消息) / P3(邮件)
- 阈值:CPU > 90% 持续 5 分钟 → 告警(避免抖动)
- 去重:同一告警 5 分钟内不重复发送
- 升级:P0 告警 10 分钟未处理 → 升级通知主管
7. 优雅停机 — 零中断部署
✅ 优雅停机三步走
- 停止接收新请求:从注册中心摘除/健康检查设为不健康
- 等待现有请求完成:给一个优雅期(如30秒),等正在处理的请求结束
- 关闭资源:关闭数据库连接、消息消费者、定时任务
💻 Spring Boot 优雅停机配置
// application.yml
server:
shutdown: graceful // 开启优雅停机
spring:
lifecycle:
timeout-per-shutdown-phase: 30s // 最多等30秒
// K8s 配合优雅停机
// Pod 删除时:
// 1. kube-proxy 移除 Endpoints(新请求不再来)
// 2. 发送 SIGTERM 给容器
// 3. Spring Boot 收到 SIGTERM → 不接收新请求 → 等待现有请求完成
// 4. 30秒后如果还没结束 → SIGKILL 强制杀死
// K8s Pod 配置
spec:
terminationGracePeriodSeconds: 45 // K8s给45秒,Spring只要30秒
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"] // 额外等10秒让K8s摘除
面试金句
优雅停机的核心:terminationGracePeriodSeconds(K8s)必须 > timeout-per-shutdown-phase(Spring),否则 Spring 还没处理完请求就被 SIGKILL 强杀。preStop hook 的 sleep 10 是为了解决Endpoints 异步传播延迟——kube-proxy 移除 Endpoints 与 SIGTERM 发送是同时进行的,但上游的连接表更新有延迟,sleep 让旧连接有时间被摘除。
8. 健康检查 — 存活 vs 就绪
📊 存活 vs 就绪决策表
| 检查 | 失败后果 | 检查内容 | 典型场景 |
| Liveness(存活) | 重启容器 | 进程是否活着 | 死锁、线程耗尽 |
| Readiness(就绪) | 摘除流量 | 是否能处理请求 | 依赖不可用、初始化中 |
| Startup(启动) | 继续等待 | 是否启动完成 | 慢启动应用 |
💻 Spring Boot Actuator 健康检查
// application.yml
management:
endpoint:
health:
show-details: always
probes:
enabled: true // 开启 k8s 探针端点
endpoints:
web:
exposure:
include: health,info,prometheus
// 自动暴露:
// /actuator/health/liveness → K8s livenessProbe
// /actuator/health/readiness → K8s readinessProbe
// 自定义健康检查
@Component
public class RedisHealthIndicator implements HealthIndicator {
@Autowired
private RedisTemplate<String, String> redis;
@Override
public Health health() {
try {
redis.ping();
return Health.up().withDetail("redis", "connected").build();
} catch (Exception e) {
return Health.down().withException(e).build();
}
}
}
面试金句
Spring Boot Actuator HealthIndicator 聚合所有组件健康状态(Redis/DB/Disk…),任一 DOWN 则整体 /actuator/health 返回 DOWN。K8s Probes(probes.enabled=true)将 Liveness 和 Readiness 分离到不同端点,实现精确控制:Readiness DOWN 摘流不重启(依赖临时不可用),Liveness DOWN 重启 Pod(进程死锁/线程耗尽)。
9. 环境管理决策
📊 环境规划决策表
| 环境 | 用途 | 数据 | 部署频率 | 谁用 |
| DEV(开发) | 开发调试 | Mock/假数据 | 每次提交 | 开发 |
| TEST(测试) | 功能测试 | 脱敏数据 | 每天 | QA |
| UAT(预发) | 验收测试 | 脱敏生产数据 | 发版前 | 产品/客户 |
| PROD(生产) | 正式上线 | 真实数据 | 按计划发布 | 所有用户 |
⚠️
环境管理最佳实践
- 配置隔离:不同环境不同配置 — 绝对不能连错数据库
- 数据脱敏:预发环境不能用真实用户数据 — GDPR/隐私合规
- 权限控制:生产环境只有运维能操作 — 开发不能直连生产库
- 环境一致性:所有环境用同样的 Docker 镜像 — 只改配置不改代码