1 API 设计原则
RESTful 核心:资源 (名词) + HTTP 动词 + 标准状态码

1.1 RESTful 核心:资源 + HTTP 动词 + 状态码

REST (Representational State Transfer) 是一种架构风格,核心思想:

CRUD → HTTP Methods 映射图
CRUD 操作 HTTP 方法 路径示例 是否幂等 是否安全 语义
Create POST POST /machines 创建新资源
Read GET GET /machines/42 获取资源
Update (全量) PUT PUT /machines/42 替换整个资源
Update (部分) PATCH PATCH /machines/42 修改部分字段
Delete DELETE DELETE /machines/42 删除资源

幂等性:同一请求执行一次和多次效果相同  |  安全性:请求不会修改服务器资源

1.2 URL 设计规则

URL 设计六条铁律
规则
用名词不用动词GET /ordersGET /getOrders
用复数名词/users/user
嵌套表示关系/users/1/orders/getUserOrders?uid=1
用 query 过滤/orders?status=active/activeOrders
用 query 分页/orders?page=2&size=20/orders/page/2
用 query 排序/orders?sort=-created_at/ordersByDateDesc
工厂类比:URL 就是仓库地址

/workshops/3/machines 就像「3号车间的设备清单」——先定位车间,再看设备。
过滤 ?status=running 就是「只看正在运行的设备」。
排序 ?sort=-temperature 就是「按温度从高到低排」。
分页 ?page=1&size=50 就是「每次只看50台」。

1.3 版本控制

三种常见版本控制方式
方式示例优点缺点
URL 路径/api/v1/users直观,易调试URL 变长
请求头Accept: application/vnd.api.v2+jsonURL 干净不易调试
查询参数/api/users?version=2简单不够 RESTful

推荐:URL 路径方式,团队最容易理解和实施。

Production API 设计不规范导致前端对接困难

场景:某项目后端 API 混用 RESTful 和 RPC 风格:

// 不一致的设计 — 前端工程师崩溃
POST /api/getUserInfo        // 用了动词
POST /api/user/create        // RESTful 但路径不对
GET  /api/user_list          // 下划线风格
GET  /api/order-detail/123   // 连字符风格
POST /api/orders/delete/456  // 用 POST 删除?

问题:前端每次都要查文档才能确定用哪个方法、哪个路径、返回什么格式。

修复:统一 RESTful 规范 + 自动生成 API 文档(Swagger/OpenAPI)。

// 统一后的设计
GET    /api/v1/users           // 列表
POST   /api/v1/users           // 创建
GET    /api/v1/users/:id       // 详情
PUT    /api/v1/users/:id       // 全量更新
DELETE /api/v1/users/:id       // 删除
GET    /api/v1/users/:id/orders // 子资源

1.4 实战案例:制造 OEE 仪表盘 API 设计

OEE (Overall Equipment Effectiveness) = 可用率 × 性能率 × 质量率。以下是一个完整的生产级 API 设计:

OEE Dashboard API 端点设计
基础路径: /api/v1

// ===== 工厂 & 产线 =====
GET    /factories                     // 工厂列表
GET    /factories/{fid}/lines         // 产线列表
GET    /factories/{fid}/lines/{lid}   // 产线详情

// ===== 设备 =====
GET    /lines/{lid}/machines          // 设备列表
GET    /machines/{mid}                // 设备详情
PATCH  /machines/{mid}/status         // 更新设备状态

// ===== OEE 数据 =====
GET    /oee/overview?factory=1&date=2026-06-10
       // 工厂级 OEE 总览

GET    /oee/lines/{lid}?period=daily&date=2026-06-10
       // 产线 OEE(可用率/性能率/质量率)

GET    /oee/machines/{mid}?period=hourly&start=2026-06-10T00:00&end=2026-06-10T23:59
       // 设备级 OEE 趋势

GET    /oee/leaderboard?sort=-oee&limit=10
       // OEE 排行榜

// ===== 告警 =====
GET    /alerts?severity=critical&status=open&page=1&size=20
PATCH  /alerts/{aid}                  // 处理告警

// ===== 报表导出 =====
POST   /reports/generate              // 生成报表任务
GET    /reports/{rid}/download        // 下载报表

设计要点:资源嵌套不超过2层,复杂查询用 query 参数,导出等异步操作返回任务 ID。

2 HTTP 状态码
用正确的状态码告诉客户端「到底发生了什么」
HTTP 状态码仪表盘
2xx
成功 — 请求被正确处理
3xx
重定向 — 资源已搬家
4xx
客户端错误 — 你的问题
5xx
服务器错误 — 我的锅
工厂类比:状态码 = 生产工单回执

2xx = 「加工完成,请取货」  |  3xx = 「此工序已搬到3号车间」  |  4xx = 「图纸画错了,请修改」  |  5xx = 「机器故障了,我们在修」

2.2 常用状态码详解

含义使用场景工厂类比
200OKGET 成功、PUT/PATCH 更新成功质检合格,准予出厂
201CreatedPOST 创建资源成功,返回新资源新设备入库登记完成
204No ContentDELETE 成功,无需返回 body报废设备已移除
301Moved Permanently资源永久迁移到新 URL产线永久搬到新厂房
400Bad Request请求格式错误、参数校验失败工单填写不完整
401Unauthorized未认证(token 缺失/过期)没刷工牌,门禁不放行
403Forbidden已认证但无权限普通工人进不了总经理办公室
404Not Found资源不存在设备编号不存在
409Conflict资源冲突(重复创建)设备编号已存在
422Unprocessable Entity格式正确但语义错误温度值填了负数
429Too Many Requests请求频率超限同一产线1分钟报工100次
500Internal Server Error服务器内部错误控制系统崩溃
502Bad Gateway网关收到无效上游响应中控系统收到车间乱码
503Service Unavailable服务暂时不可用(过载/维护)车间正在检修,暂停接单
状态码选择的黄金法则
  • 永远不要所有接口都返回 200,然后用 body 里的 code 区分 — 这违背了 HTTP 语义
  • 4xx vs 5xx:客户端能修复的错误用 4xx,服务端自己的问题用 5xx
  • 401 vs 403:不知道你是谁 → 401;知道你是谁但没权限 → 403
  • 400 vs 422:格式语法错误 → 400;格式对但业务逻辑不通 → 422
3 认证与授权
你是谁(认证)?你能做什么(授权)?

3.1 认证方式概览

工厂类比:三种通行方式

API Key = 厂牌(一张卡进出所有门,简单但不灵活)
JWT = 电子通行证(含照片+权限+有效期,自助验证不需查系统)
OAuth 2.0 = 临时授权书(你授权快递员只能进收发室,不能进车间)

API Key — 最简单的方式

工作原理
// 请求时带上 Key
GET /api/v1/machines
Header: X-API-Key: sk_live_abc123def456

// 或 query 参数(不推荐,会出现在日志里)
GET /api/v1/machines?api_key=sk_live_abc123def456

优点:实现极简  |  缺点:无细粒度权限、无法过期、泄露风险高

适用:服务间调用、开放 API(如天气接口)

3.2 JWT (JSON Web Token) 详解

JWT 结构:Header.Payload.Signature
HEADER
{ "alg": "HS256", "typ": "JWT" }
.
PAYLOAD
{ "sub": "user_42", "role": "admin", "exp": 1718000000 }
.
SIGNATURE
HMAC(header.payload, secret)
算法信息
用什么加密
用户数据 + 元信息
谁、什么角色、何时过期
防篡改签名
确保内容没被改过
JWT = 电子工牌

Header(卡片材质)+ Payload(你的照片+部门+有效期)+ Signature(防伪标记,HR盖章)。
任何人都能读取内容(Base64),但只有持有密钥的人能验证真伪。

JWT 认证流程
用户登录
POST /auth/login
服务器验证
查数据库
签发 JWT
含用户信息+过期时间
客户端存储
localStorage / Cookie
请求时携带
Authorization: Bearer xxx
服务器验证签名
无状态,不查 DB

3.3 OAuth 2.0 授权码流程

OAuth 2.0 授权码模式流程图
① 用户 点击「使用微信登录」,跳转到授权服务器
↓ 用户在授权服务器上确认授权
② 授权服务器 重定向回应用,附带 authorization_code
↓ 后端用 code 换 token
③ 应用后端 POST /token(code + client_secret)→ 获取 access_token + refresh_token
↓ 用 access_token 访问资源
④ 资源服务器 验证 access_token → 返回用户资源
OAuth = 借用他人身份

你(用户)授权快递员(第三方应用)进入小区(资源服务器)放快递, 物业(授权服务器)给快递员一张临时通行证(access_token), 有效期1小时,到期可以续签(refresh_token)。

3.4 Session vs Token 对比

维度Session (有状态)JWT Token (无状态)
存储位置服务端内存/Redis客户端(localStorage/Cookie)
扩展性需共享 Session(粘性会话/Redis)天然支持分布式
性能每次查存储验签即可,无 I/O
安全撤销直接删除 Session需黑名单机制(又变成有状态)
payload 大小只需 Session IDToken 自带数据,体积较大
适用场景传统 Web 应用API、移动端、微服务

3.5 Refresh Token Rotation

Token 刷新与轮换策略
Access Token
15 分钟过期
过期!
用 Refresh Token
换新 Access Token
新 Access Token
+ 新 Refresh Token

Rotation:每次刷新时同时颁发新 Refresh Token,旧的一次性作废。 若旧 Refresh Token 被重复使用,说明可能被盗,立即撤销整个 Token 家族。

Production JWT 被盗用怎么办?

问题:JWT 无状态,签发后无法主动撤销。如果 Token 泄露,攻击者在过期前可一直使用。

解决方案(多层防御)

  1. 短有效期:Access Token 15 分钟,即使泄露窗口也小
  2. Refresh Token Rotation:刷新时轮换,检测重放攻击
  3. Token 黑名单:登出/改密码时将 Token 的 jti 加入 Redis 黑名单
  4. IP 绑定:签发时记录 IP,使用时校验(可选)
  5. HTTPS:传输层加密,防止中间人窃取
// 黑名单检查中间件 (伪代码)
async function jwtGuard(req, res, next) {
  const token = extractToken(req);
  const payload = jwt.verify(token, SECRET);
  const isBlacklisted = await redis.get(`blacklist:${payload.jti}`);
  if (isBlacklisted) return res.status(401).json({ error: 'Token revoked' });
  req.user = payload;
  next();
}
4 API 安全
保护 API 免受滥用、注入和未授权访问

4.1 限流 (Rate Limiting)

三种限流算法对比
固定窗口
|---- 1 min ----|
简单但有临界突发问题
Redis INCR + EXPIRE
滑动窗口
|-- 滑动窗口 --|
平滑计数,无临界问题
Redis Sorted Set + 时间戳
令牌桶
令牌 80/100
↑ 恒定速率补充
允许突发流量
Google Guava RateLimiter
限流实践要点
  • 限流维度:按 IP、按用户 ID、按 API Key、按接口
  • 返回信息429 Too Many Requests + X-RateLimit-Remaining + Retry-After
  • 分布式:用 Redis 存计数,确保多实例一致
  • 推荐算法:令牌桶(允许突发)+ 滑动窗口(精确统计)

4.2 输入校验

永远不要信任客户端输入
// 常见输入校验清单
const validationRules = {
  // 1. 类型校验
  age: { type: 'integer', min: 0, max: 150 },
  // 2. 格式校验
  email: { type: 'string', format: 'email' },
  // 3. 长度限制
  name: { type: 'string', minLength: 1, maxLength: 100 },
  // 4. 枚举值
  status: { type: 'string', enum: ['active', 'inactive', 'maintenance'] },
  // 5. SQL 注入防护 — 永远用参数化查询
  // BAD:  `SELECT * FROM users WHERE id = ${req.params.id}`
  // GOOD: `SELECT * FROM users WHERE id = $1`, [req.params.id]
  // 6. XSS 防护 — 输出时转义
  // BAD:  res.send(`
${user.name}
`) // GOOD: 使用模板引擎自动转义 };

4.3 CORS 配置

跨域资源共享 (CORS)
// Express CORS 配置示例
app.use(cors({
  origin: ['https://app.example.com'],  // 不要用 *
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,     // 允许携带 Cookie
  maxAge: 86400          // 预检缓存 24 小时
}));

// 生产环境注意:
// - origin 不要用 '*',要精确指定域名
// - credentials: true 时 origin 不能是 '*'
// - 预检请求 (OPTIONS) 会消耗资源,设置 maxAge

4.4 HTTPS 强制

Production API 被恶意调用 10 万次/秒

场景:某开放 API 被爬虫/攻击者高频率调用,导致后端数据库连接池耗尽,正常用户无法使用。

应急处理

  1. 紧急止损:WAF 层面按 IP 黑名单封禁攻击源
  2. 启用限流:按 IP 限 100 次/分钟,按用户限 1000 次/分钟
  3. 分析日志:定位异常调用模式(User-Agent、路径、时间分布)
  4. 长期方案:API Gateway 统一限流 + 验证码 + API Key 白名单
// Nginx 限流配置
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/m;
limit_req zone=api burst=50 nodelay;

// IP 黑名单
deny 203.0.113.0/24;
deny 198.51.100.0/24;
5 GraphQL 入门
按需获取数据,一个端点搞定一切
REST vs GraphQL 数据获取对比
REST
获取用户 + 订单 + 地址:
GET /users/1
GET /users/1/orders
GET /users/1/address
3 次请求 · 可能 over-fetching
VS
GraphQL
一次搞定:
query {
  user(id: 1) {
    name
    orders { id total }
    address { city }
  }
}
1 次请求 · 精确获取所需字段

5.1 什么时候用 GraphQL

场景适合 GraphQL适合 REST
多端(Web/iOS/Android)不同数据需求✓ 每端按需查询
复杂嵌套数据(用户→订单→商品→评价)✓ 一次查询搞定多次请求或 over-fetch
简单 CRUD 后台管理✓ REST 更直观
缓存至关重要缓存较复杂✓ HTTP 缓存天然支持
团队 REST 经验丰富,GraphQL 经验少学习成本高✓ 快速上手

5.2 GraphQL 三种操作

Query / Mutation / Subscription
# Query — 查询数据(类比 GET)
query GetMachineStatus($id: ID!) {
  machine(id: $id) {
    name
    status
    oee { availability performance quality }
  }
}

# Mutation — 修改数据(类比 POST/PUT/DELETE)
mutation UpdateMachineStatus($id: ID!, $status: MachineStatus!) {
  updateMachine(id: $id, status: $status) {
    id
    status
    updatedAt
  }
}

# Subscription — 实时订阅(WebSocket)
subscription OnAlertAdded($lineId: ID!) {
  alertAdded(lineId: $lineId) {
    id
    severity
    message
    createdAt
  }
}
6 微服务架构
从「大工厂全在一个厂房」到「多个专业车间」

6.1 单体 vs 微服务

单体架构 vs 微服务架构
Monolith 单体
用户模块
订单模块
支付模块
库存模块
通知模块
报表模块
共享一个数据库
单一 DB
简单但牵一发动全身
一个模块挂了全部挂
VS
Microservices 微服务
用户服务
User DB
订单服务
Order DB
支付服务
Pay DB
库存服务
Inv DB
通知服务
Msg Queue
报表服务
ClickHouse
独立部署、独立扩展
但引入分布式复杂性
工厂类比

单体 = 大工厂全在一个厂房:冲压、焊接、喷漆、总装全在一起。一台设备坏了可能影响整条线。
微服务 = 多个专业车间:冲压车间、焊接车间各自独立运作,通过传送带(消息队列)或物流车(REST/gRPC)协作。

6.2 服务拆分原则

怎么拆?按什么拆?
  • 按业务领域拆(DDD 限界上下文):用户域、订单域、支付域
  • 高内聚低耦合:经常一起变化的放一起,很少一起变化的分开
  • 独立数据存储:每个服务有自己的数据库,不共享
  • 一个服务一个团队:康威定律 — 系统架构反映组织沟通结构
  • 避免过细拆分:20 个服务可能太多了,从 5-8 个开始

6.3 服务通信方式

方式协议适用场景优点缺点
REST HTTP/JSON 外部 API、简单调用 通用、易调试 开销大、无强类型
gRPC HTTP/2 + Protobuf 服务间高频调用 高性能、强类型 调试不便、浏览器不支持
消息队列 AMQP/Kafka 异步解耦、事件驱动 削峰填谷、松耦合 复杂度增加、最终一致性

6.4 服务发现 & API 网关

基础设施组件
组件工具作用
服务发现Consul / Eureka / Nacos服务注册与发现,动态感知上下线
API 网关Nginx / Kong / Spring Cloud Gateway统一入口:路由、限流、认证、日志
配置中心Apollo / Nacos / Consul集中管理各服务配置,动态刷新
链路追踪Jaeger / Zipkin / SkyWalking追踪请求在服务间的完整调用链
微服务架构总览图
Web 前端
Mobile App
第三方系统
API Gateway
认证 · 限流 · 路由 · 日志
用户服务
订单服务
支付服务
库存服务
通知服务
报表服务
MySQL
Redis
Kafka
ClickHouse
Production 微服务拆了 20 个但调试困难

场景:某团队将系统拆成 20+ 微服务,一个用户请求涉及 6 个服务调用。出 Bug 时完全无法定位是哪个服务的问题。

根因:没有分布式追踪、没有统一日志、没有服务间调用可视化。

解决方案

  1. 引入分布式追踪:每个请求生成 Trace ID,在各服务间传递
  2. 统一日志:ELK (Elasticsearch + Logstash + Kibana) 集中收集
  3. 服务网格:Istio/Linkerd 自动注入追踪信息
  4. 复盘:20 个服务是否合理?考虑合并一些过于碎片化的服务
// 在请求头中传递 Trace ID
app.use((req, res, next) => {
  const traceId = req.headers['x-trace-id'] || uuid();
  req.traceId = traceId;
  res.setHeader('X-Trace-Id', traceId);
  // 所有日志带上 traceId
  logger.defaultMeta = { traceId };
  next();
});
7 分布式系统挑战
网络不可靠、时钟不同步、节点会故障 — 分布式的本质问题

7.1 CAP 定理

CAP 定理:三者不可兼得
C
Consistency
一致性
A
Availability
可用性
P
Partition
分区容忍
C 一致性
所有节点看到的数据一致
A 可用性
每个请求都能得到响应(不保证最新)
P 分区容忍
网络分区时系统仍能运作
现实:网络分区不可避免(P 必选),所以只能在 CP 和 AP 之间选。
选择策略典型系统场景
CP分区时宁可不可用也不返回旧数据Zookeeper, etcd, HBase金融交易、配置管理
AP分区时保证可用但可能返回旧数据Cassandra, DynamoDB, Eureka社交、购物车、缓存

7.2 分布式事务

三种分布式事务方案
方案原理优点缺点适用
2PC
两阶段提交
协调者问所有参与者能不能提交 → 全部能 → 提交 强一致 同步阻塞、单点故障 数据库层面跨库事务
TCC
Try-Confirm-Cancel
业务层面:预留资源(Try) → 确认(Confirm) 或 回滚(Cancel) 柔性事务、高性能 每个操作要写3个方法、侵入性强 支付、金融核心
Saga 长事务拆成多个本地事务,每步失败执行补偿操作 无锁、可扩展 最终一致、补偿复杂 电商下单(库存+支付+物流)
工厂类比:Saga = 多工序协作

订单处理 = 下单 → 扣库存 → 支付 → 发物流 → 通知用户。
每步都是独立的本地事务。如果「支付」失败,就补偿:释放库存 → 取消订单。
不需要所有工序同时锁住(2PC),而是「做一步看一步,失败了往回退」。

7.3 熔断器模式 (Circuit Breaker)

熔断器三态模型
CLOSED
关闭 (正常)
请求正常通过
监控失败率
失败率 > 阈值
OPEN
打开 (熔断)
直接拒绝请求
返回降级响应
超时后试探
HALF-OPEN
半开 (试探)
放少量请求测试
成功→关闭 失败→打开
← 成功恢复
实现:Hystrix(已停更)| Resilience4j(推荐)| Sentinel(阿里开源)

7.4 服务降级策略

降级 ≠ 熔断,但经常配合使用
策略做法示例
返回默认值用兜底数据响应推荐系统挂了 → 返回热门商品
返回缓存用上次成功的结果价格服务挂了 → 返回缓存价格
功能关闭关闭非核心功能评价系统挂了 → 隐藏评价区域
排队等待异步处理,稍后通知高峰期下单 → 排队,短信通知结果
Production 依赖的支付服务挂了怎么办?

场景:电商系统的第三方支付网关宕机,所有支付请求超时,线程池耗尽导致整个系统雪崩。

解决方案:熔断 + 降级

// Resilience4j 熔断配置
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
    .failureRateThreshold(50)           // 失败率 50% 触发熔断
    .waitDurationInOpenState(Duration.ofSeconds(30))  // 熔断 30 秒
    .slidingWindowSize(10)              // 滑动窗口 10 次请求
    .permittedNumberOfCallsInHalfOpenState(3)  // 半开时放 3 个试探
    .build();

// 降级逻辑
@CircuitBreaker(name = "payment", fallbackMethod = "payFallback")
public PaymentResult pay(Order order) {
    return paymentClient.charge(order);
}

public PaymentResult payFallback(Order order, Exception e) {
    // 降级方案 1:记录到数据库,稍后重试
    pendingPaymentRepository.save(order);
    // 降级方案 2:通知用户
    notificationService.notify(order.getUserId(),
        "支付系统繁忙,我们稍后自动重试");
    return PaymentResult.queued(order.getId());
}
8 面试题精选 (25 道)
覆盖 API 设计、认证、微服务权衡、CAP、分布式事务
Q1 核心 什么是 RESTful API?它的核心原则是什么?
REST (Representational State Transfer) 是一种架构风格。核心原则:
资源导向:URL 表示资源(名词),HTTP 方法表示操作(动词)
无状态:每次请求包含所有必要信息,服务端不保存客户端状态
统一接口:GET/POST/PUT/DELETE 语义明确
标准状态码:用 HTTP 状态码表达结果
HATEOAS(可选):响应中包含相关链接,实现自发现
Q2 中等 PUT 和 PATCH 的区别是什么?
PUT:全量替换。必须提供资源的所有字段。如果缺少某字段,该字段会被设为 null。
PATCH:部分更新。只修改提供的字段,未提供的字段保持不变。

示例:更新用户 { name: "Alice", age: 25, email: "a@b.com" }
PUT { name: "Bob" } → 结果 { name: "Bob" }(age 和 email 丢失)
PATCH { name: "Bob" } → 结果 { name: "Bob", age: 25, email: "a@b.com" }
Q3 中等 API 版本控制有哪几种方式?各有什么优缺点?
URL 路径/api/v1/users):最直观,但 URL 变长
请求头Accept: application/vnd.api.v2+json):URL 干净,但调试不方便
查询参数?version=2):最简单,但不 RESTful

推荐:大多数团队用 URL 路径,简单且被广泛理解。
Q4 困难 如何设计一个支持分页、过滤和排序的列表 API?
GET /api/v1/orders?
  status=active              // 过滤:只看活跃订单
  &created_after=2026-01-01  // 范围过滤
  &sort=-created_at,priority // 排序:- 表示降序
  &page=2&size=20            // 分页:第2页,每页20条
响应体包含分页元信息:
{
  "data": [...],
  "pagination": {
    "page": 2, "size": 20,
    "total": 158, "total_pages": 8
  }
}
Q5 中等 401 和 403 的区别是什么?
401 Unauthorized:未认证 — 「你是谁?」Token 缺失、过期、无效。
403 Forbidden:已认证但无权限 — 「我知道你是谁,但你没权限。」

类比:401 = 没刷工牌进不去大楼;403 = 刷了工牌但你进不了机房(需要更高权限)。
Q6 困难 JWT 的原理是什么?有什么优缺点?
JWT = Header(算法信息)+ Payload(用户数据+过期时间)+ Signature(防篡改签名),三部分用 Base64 编码后用 . 连接。

优点:无状态(不查 DB)、天然支持分布式、移动端友好
缺点:无法主动撤销(需黑名单)、Payload 明文可读(不要放敏感信息)、Token 体积比 Session ID 大
追问:JWT 被盗怎么办?→ 短有效期 + Refresh Token Rotation + 黑名单
Q7 困难 Session 和 JWT Token 认证各自的优缺点?怎么选?
Session:服务端存储,易撤销,但需要共享存储(Redis),有状态不利于扩展。
JWT:客户端存储,无状态,利于扩展,但难以主动撤销。

选择:传统 Web(服务端渲染)→ Session;API/移动端/微服务 → JWT。
Q8 中等 OAuth 2.0 的授权码模式流程是怎样的?为什么比隐式模式安全?
授权码模式:用户在授权服务器登录 → 授权服务器返回 code → 后端用 code + client_secret 换 token。

为什么更安全:access_token 只在后端和服务端之间传递,不经过浏览器,JavaScript 无法获取。隐式模式直接在浏览器返回 token,容易被 XSS 窃取。
Q9 困难 如何防止 API 被恶意刷接口?
多层防御:
限流 (Rate Limiting):按 IP/用户/接口维度限制频率(令牌桶/滑动窗口)
IP 黑名单:WAF/网关层封禁恶意 IP
认证 + 授权:接口必须认证,避免匿名滥用
验证码:高频操作前加入人机验证
监控告警:异常流量模式检测,自动触发防护
API Key 白名单:只允许已注册的客户端调用
Q10 基础 什么是 CORS?如何解决跨域问题?
CORS (Cross-Origin Resource Sharing) 是浏览器安全机制,限制网页向不同域名发起请求。

解决方式:服务端在响应头中添加 Access-Control-Allow-Origin 等头信息。复杂请求(PUT/DELETE/自定义头)会先发 OPTIONS 预检请求。

生产环境:精确指定 origin,不要用 *
Q11 中等 REST 和 GraphQL 的区别?什么时候选哪个?
REST:资源导向,多个端点,每个端点返回固定结构。适合简单 CRUD、缓存优先场景。
GraphQL:查询导向,单一端点,客户端按需获取字段。适合多端差异化需求、复杂嵌套数据。

选择:后台管理/简单 API → REST;BFF (Backend for Frontend)/多端复杂需求 → GraphQL。
Q12 核心 单体架构和微服务架构各有什么优缺点?
单体:开发简单、部署简单、调试方便。但随着项目增大,代码耦合、部署风险高、扩展不灵活。
微服务:独立部署、独立扩展、技术栈灵活。但引入分布式复杂性(网络延迟、数据一致性、调试困难)。

关键:不要为了微服务而微服务。小项目/团队小 → 单体更合适;大团队/大项目 → 微服务值得投入。
Q13 困难 微服务之间怎么通信?各有什么优缺点?
REST (HTTP/JSON):简单通用,但开销大(JSON 序列化 + HTTP 协议开销),适合外部 API
gRPC (HTTP/2 + Protobuf):高性能二进制协议,强类型,适合服务间高频调用
消息队列 (Kafka/RabbitMQ):异步解耦,削峰填谷,适合事件驱动场景,但增加复杂度

实践:同步调用用 gRPC,异步通信用消息队列,对外暴露用 REST。
Q14 中等 什么是 API Gateway?它解决了什么问题?
API Gateway 是微服务的统一入口,所有外部请求先经过网关。

解决的问题
统一认证:在网关层做认证,各服务不用重复实现
路由转发:根据路径/头部将请求路由到对应服务
限流熔断:在入口层统一保护后端服务
协议转换:外部 REST → 内部 gRPC
日志监控:统一记录请求日志和指标

常见方案:Nginx、Kong、Spring Cloud Gateway、Traefik。
Q15 困难 什么是 CAP 定理?在实际系统中怎么取舍?
CAP 定理:分布式系统中,C(一致性)、A(可用性)、P(分区容忍)三者不可兼得。

网络分区(P)是不可避免的,所以实际是在 CPAP 之间选择:
- CP(如 Zookeeper、etcd):分区时宁可不可用也要保证数据一致。适合配置管理、分布式锁。
- AP(如 Cassandra、Eureka):分区时保证可用但可能返回旧数据。适合社交、购物车。

BASE 理论:大多数互联网系统选择 AP + 最终一致性。
Q16 困难 分布式事务有哪几种解决方案?
2PC(两阶段提交):协调者通知所有参与者准备 → 全部 OK → 提交。强一致但同步阻塞。
TCC(Try-Confirm-Cancel):业务层面实现 Try(预留)/Confirm(确认)/Cancel(回滚)。高性能但侵入性强。
Saga:将长事务拆成多个本地短事务,每步有补偿操作。最终一致,适合电商、物流等业务流程。
本地消息表:将消息持久化到本地 DB,定时扫描发送。简单可靠。

实践:尽量用最终一致性,避免强一致分布式事务。
Q17 困难 什么是熔断器模式?它有哪些状态?
熔断器模式:当下游服务异常率过高时,自动切断请求,防止故障蔓延(雪崩效应)。

三态:
CLOSED(关闭):正常放行请求,同时统计失败率
OPEN(打开):失败率超阈值,直接拒绝请求(快速失败),返回降级响应
HALF-OPEN(半开):超时后放少量请求试探,成功则关闭,失败则继续打开

实现框架:Resilience4j(推荐)、Sentinel、Hystrix(已停更)。
Q18 中等 服务降级有哪些策略?
返回默认值:推荐服务挂了 → 返回热门商品列表
返回缓存:价格服务挂了 → 返回上次缓存的价格
功能关闭:评价服务挂了 → 隐藏评价区域
写队列:下单高峰 → 写入消息队列异步处理
限流降级:超量请求直接返回「系统繁忙」
Q19 中等 什么是服务发现?为什么微服务需要它?
微服务实例 IP 和端口是动态的(容器化部署、弹性伸缩),不能硬编码服务地址。

服务发现:服务启动时注册自己的地址到注册中心,调用方从注册中心查询目标服务地址。
- Consul:支持健康检查 + KV 存储,Go 生态
- Eureka:Netflix 开源,Spring Cloud 生态
- Nacos:阿里开源,支持 AP/CP 切换,国内主流

调用流程:Service A → 查注册中心 → 获取 Service B 地址 → 发起调用
Q20 困难 微服务如何实现分布式链路追踪?
核心思想:每个请求分配唯一 Trace ID,在各服务间传递,每个服务记录自己的 Span 信息。

实现
① 入口网关生成 Trace ID,放入请求头 X-Trace-Id
② 每个服务从请求头取出 Trace ID,记录日志并传递给下游
③ 所有服务日志集中收集(ELK/ClickHouse),按 Trace ID 检索完整链路

工具:Jaeger、Zipkin、SkyWalking。云原生环境可用 Istio 自动注入。
Q21 中等 什么是幂等性?为什么 API 设计要考虑幂等?
幂等性:同一操作执行一次和多次效果相同。

GET、PUT、DELETE 天然幂等。POST 不是幂等的(重复提交会创建多条记录)。

如何保证 POST 幂等
① 客户端生成幂等 Key(Idempotency-Key 请求头),服务端记录已处理的 Key
② 数据库唯一约束,重复插入会报错
③ Token 机制:先获取 token,提交时消费 token
Q22 困难 如何保证微服务的数据一致性?
微服务每个服务有独立数据库,不能使用本地事务。方案:
避免分布式事务:合理拆分服务边界,让事务在单个服务内完成
事件驱动 + 最终一致性:服务 A 完成操作后发布事件,服务 B 监听并处理
Saga 模式:编排多个本地事务,每步有补偿操作
本地消息表:将事件持久化到本地 DB,确保消息不丢失

黄金法则:能不用分布式事务就不用。最终一致性 > 强一致性。
Q23 中等 限流算法有哪些?令牌桶和漏桶的区别?
固定窗口:按时间段计数,简单但有临界突发问题
滑动窗口:按时间滑动统计,解决临界问题
漏桶:请求以恒定速率处理(匀速出水),削峰填谷,但无法应对合理突发
令牌桶:以恒定速率生成令牌,请求消耗令牌。允许突发(桶里有累积的令牌),是生产环境首选

区别:漏桶 = 请求匀速处理;令牌桶 = 允许一定突发后匀速恢复。
Q24 困难 什么是「雪崩效应」?如何防止?
雪崩效应:一个服务故障 → 调用方线程阻塞 → 线程池耗尽 → 调用方也故障 → 连锁反应导致整个系统崩溃。

防止策略
熔断器:下游异常率过高时快速失败,不阻塞线程
超时设置:设置合理的连接超时和读超时
限流:限制并发请求数,保护线程池
舱壁模式:不同服务使用隔离的线程池/连接池,故障不蔓延
降级:故障时返回兜底响应,保证核心功能可用
Q25 困难 你会如何设计一个高可用的 API 系统?
接入层
- DNS 轮询 / CDN 加速
- 负载均衡(Nginx / 云 LB)多实例部署
- API Gateway 统一认证、限流、路由

应用层
- 无状态设计(JWT),支持水平扩展
- 熔断 + 降级 + 超时(Resilience4j)
- 健康检查 + 自动重启

数据层
- 主从复制 / 读写分离
- Redis 缓存 + 热点数据本地缓存
- 消息队列削峰填谷

运维层
- 全链路监控 + 告警(Prometheus + Grafana)
- 日志集中收集(ELK)
- 自动扩缩容(K8s HPA)
这道题考察的是系统设计全局思维,面试官会根据你的回答追问细节。
RESTful API 设计与微服务 · 面试备战库
共 8 个章节 · 10+ CSS 图表 · 25 道面试题 · 6 个生产案例