如何设计对外API接口
如何设计对外API接口
面试官您好,关于 "如何设计对外 API 接口" 这个问题,我会从7 个核心维度来分享我的设计思路 🎯
先明确 API 设计的 "黄金三角" 原则
这是所有设计的基础,偏离了这些原则,API 就会变成 "没人愿意用的鸡肋" 👇
接口命名与规范 ✅
坚决使用 RESTful 风格,这是业界事实上的标准,能极大降低沟通成本:
- 资源用名词复数:
/users、/orders、/products - HTTP 动词表示操作:
- GET:查询资源
- POST:创建资源
- PUT:全量更新资源
- PATCH:部分更新资源
- DELETE:删除资源
- 禁止使用动词:❌
/getUser、✅/users/{id} - 路径分隔用短横线:❌
/userInfo、✅/user-info - 统一小写,避免大小写敏感问题
请求与响应设计 📦
这是最容易踩坑的地方,统一的格式比什么都重要!
1. 统一响应体结构
{
"code": 200, // 业务状态码,≠HTTP状态码
"message": "success", // 提示信息
"data": {}, // 业务数据
"timestamp": 1717888888888 // 服务器时间戳
}2. 分页查询标准设计
所有列表接口必须支持分页,防止大数据量拖垮服务:
GET /users?pageNum=1&pageSize=20&sort=createTime,desc响应示例:
{
"code": 200,
"message": "success",
"data": {
"total": 100,
"list": [...],
"pageNum": 1,
"pageSize": 20,
"pages": 5
}
}3. 关键注意事项 ⚠️
- 参数校验:所有入参必须在 Controller 层做校验(使用 JSR-380 规范:
@NotNull、@Size等) - 敏感字段脱敏:手机号、身份证、银行卡号等必须返回脱敏后的数据
- 禁止返回内部异常栈:生产环境异常信息会泄露系统架构,给黑客可乘之机
安全设计 🛡️
对外 API 的安全是生命线,没有安全的 API 就是在裸奔!
核心安全措施:
- 强制 HTTPS:所有对外接口必须使用 HTTPS,禁止 HTTP 明文传输
- 签名机制:请求参数 + 时间戳 + 随机数 + 密钥生成签名,防止参数被篡改
- 防重放攻击:签名有效期一般设为 5-15 分钟,配合 nonce 随机数去重
- 身份认证:优先使用 JWT 无状态认证,涉及高敏感操作时增加二次验证
- 权限控制:基于 RBAC 模型,细粒度控制每个接口的访问权限
性能与可用性设计 🚀
好的 API 不仅能用,还要好用、耐用:
| 问题 | 解决方案 | 实现要点 |
|---|---|---|
| 高并发 | 限流 | 令牌桶算法,基于 Redis 实现分布式限流 |
| 服务雪崩 | 熔断降级 | 使用 Sentinel/Hystrix,非核心接口直接降级 |
| 重复提交 | 幂等性 | 唯一请求 ID + 防重表,写操作必须保证幂等 |
| 慢查询 | 缓存 | 热点数据放入 Redis,设置合理的过期时间 |
| 大数据量 | 分批处理 | 导出、批量查询接口必须分批,避免 OOM |
幂等性设计是重中之重:所有写操作(POST/PUT/DELETE)都必须保证幂等,防止重复下单、重复扣款等严重业务问题。
版本管理 🔄
API 一旦发布就不能随意修改,版本控制是保证兼容性的关键:
- 推荐 URL 路径版本:
/api/v1/users、/api/v2/users - 不推荐:参数版本(
?version=1)、请求头版本 - 旧版本至少保留3-6 个月,给调用方足够的迁移时间
- 废弃的接口在响应头中添加
Deprecation和Sunset字段提示
文档与监控 📊
接口文档:强制使用 Swagger/OpenAPI 3.0,文档必须包含:
- 接口功能说明
- 请求参数、响应字段说明
- 成功 / 失败示例
- 错误码对照表
监控告警:监控以下核心指标:
- QPS、响应时间、错误率
- 慢接口 TOP10
- 异常调用次数
- 限流熔断触发次数
最后总结 ✨
好的对外 API 接口应该像一个 "优雅的服务员":
- 说话清晰(命名规范)
- 态度友好(响应统一)
- 安全可靠(防护到位)
- 反应迅速(性能优秀)
- 与时俱进(版本迭代)
它不仅是系统之间的桥梁,更是技术团队的 "脸面"。一个设计糟糕的 API 会让调用方痛苦不堪,而一个设计优秀的 API 则会大大提升开发效率和系统稳定性。
核心代码实现与技术亮点 ✨
我会展示对外 API 设计中最核心、最有技术含量的 5 个模块代码,全部是生产环境可用的标准实现。
1. 统一响应体 + 全局异常处理(基础中的基础)
技术亮点:泛型封装 + 全局异常拦截,彻底消灭零散的响应构造代码,统一所有异常的返回格式
// 1. 通用响应体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
private int code;
private String message;
private T data;
private long timestamp;
// 静态工厂方法,业务代码只需调用这几个方法
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "success", data, System.currentTimeMillis());
}
public static <T> ApiResponse<T> fail(int code, String message) {
return new ApiResponse<>(code, message, null, System.currentTimeMillis());
}
}
// 2. 全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
// 业务异常
@ExceptionHandler(BusinessException.class)
public ApiResponse<Void> handleBusinessException(BusinessException e) {
return ApiResponse.fail(e.getCode(), e.getMessage());
}
// 参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResponse<Void> handleValidationException(MethodArgumentNotValidException e) {
String errorMsg = e.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
return ApiResponse.fail(400, "参数错误:" + errorMsg);
}
// 兜底异常(禁止返回异常栈)
@ExceptionHandler(Exception.class)
public ApiResponse<Void> handleException(Exception e) {
log.error("系统异常", e); // 内部记录完整日志
return ApiResponse.fail(500, "系统繁忙,请稍后重试");
}
}2. 签名验签工具类(安全核心)
技术亮点:支持防篡改 + 防重放攻击,生产环境标准实现
@Component
public class SignUtils {
private static final String SIGN_SECRET = "your-secret-key"; // 从配置中心读取
private static final long SIGN_EXPIRE_TIME = 5 * 60 * 1000; // 签名有效期5分钟
// 生成签名
public String generateSign(Map<String, String> params, String timestamp, String nonce) {
// 1. 参数按字典序排序
String sortedParams = params.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
// 2. 拼接签名串:sortedParams + timestamp + nonce + secret
String signStr = sortedParams + timestamp + nonce + SIGN_SECRET;
// 3. MD5加密并转大写
return DigestUtils.md5DigestAsHex(signStr.getBytes()).toUpperCase();
}
// 验证签名+防重放
public boolean verifySign(Map<String, String> params, String timestamp, String nonce, String sign) {
// 1. 检查时间戳是否过期
long requestTime = Long.parseLong(timestamp);
if (System.currentTimeMillis() - requestTime > SIGN_EXPIRE_TIME) {
return false;
}
// 2. 检查nonce是否已存在(防重放)
String redisKey = "nonce:" + nonce;
if (redisTemplate.hasKey(redisKey)) {
return false;
}
// 3. 验证签名
String serverSign = generateSign(params, timestamp, nonce);
if (!serverSign.equals(sign)) {
return false;
}
// 4. 存入Redis,有效期同签名有效期
redisTemplate.opsForValue().set(redisKey, "1", SIGN_EXPIRE_TIME, TimeUnit.MILLISECONDS);
return true;
}
}3. 幂等性注解 + AOP 实现(无侵入设计)
技术亮点:注解驱动 + AOP,业务代码零侵入,支持自定义幂等有效期
// 1. 幂等性注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
long expireTime() default 10 * 60 * 1000; // 默认10分钟
}
// 2. AOP切面实现
@Aspect
@Component
public class IdempotentAspect {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Around("@annotation(idempotent)")
public Object around(ProceedingJoinPoint point, Idempotent idempotent) throws Throwable {
// 1. 获取唯一请求ID(建议从请求头传递,前端生成)
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String requestId = request.getHeader("X-Request-Id");
if (StringUtils.isEmpty(requestId)) {
throw new BusinessException(400, "缺少请求唯一标识");
}
// 2. Redis原子操作判断是否已执行
String redisKey = "idempotent:" + requestId;
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(redisKey, "1", idempotent.expireTime(), TimeUnit.MILLISECONDS);
if (Boolean.FALSE.equals(success)) {
throw new BusinessException(409, "请勿重复提交");
}
// 3. 执行原方法
try {
return point.proceed();
} catch (Exception e) {
// 异常时删除key,允许重试
redisTemplate.delete(redisKey);
throw e;
}
}
}
// 3. 业务使用示例
@PostMapping("/orders")
@Idempotent(expireTime = 30 * 60 * 1000) // 订单30分钟内不允许重复提交
public ApiResponse<OrderVO> createOrder(@RequestBody @Valid CreateOrderRequest request) {
Order order = orderService.createOrder(request);
return ApiResponse.success(convert(order));
}4. 分布式限流注解 + AOP 实现
技术亮点:基于 Redis 令牌桶算法,支持自定义限流规则,集群环境有效
// 1. 限流注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
int limit() default 100; // 每秒允许的请求数
int burst() default 200; // 突发流量上限
}
// 2. AOP切面实现
@Aspect
@Component
public class RateLimitAspect {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String LIMIT_SCRIPT = "local key = KEYS[1] " +
"local limit = tonumber(ARGV[1]) " +
"local burst = tonumber(ARGV[2]) " +
"local now = tonumber(ARGV[3]) " +
"local rate = limit / 1000 " +
"local last_time = tonumber(redis.call('hget', key, 'last_time') or now) " +
"local tokens = tonumber(redis.call('hget', key, 'tokens') or burst) " +
"tokens = math.min(burst, tokens + (now - last_time) * rate) " +
"if tokens >= 1 then " +
" redis.call('hset', key, 'tokens', tokens - 1) " +
" redis.call('hset', key, 'last_time', now) " +
" return 1 " +
"else " +
" return 0 " +
"end";
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {
String key = "rate_limit:" + point.getSignature().toShortString();
List<String> keys = Collections.singletonList(key);
Object[] args = {rateLimit.limit(), rateLimit.burst(), System.currentTimeMillis()};
Long result = (Long) redisTemplate.execute(new DefaultRedisScript<>(LIMIT_SCRIPT, Long.class), keys, args);
if (result == 0) {
throw new BusinessException(429, "请求过于频繁,请稍后重试");
}
return point.proceed();
}
}技术难点与解决方案 🧩
我整理了对外 API 设计中最容易被面试官追问的 5 个技术难点,以及对应的生产级解决方案:
| 技术难点 | 具体挑战 | 最优解决方案 | 注意事项 |
|---|---|---|---|
| 分布式幂等性保证 ⚠️ | 1. 并发场景下重复请求 2. 网络超时导致的重试 3. 业务回滚后允许重试 | Redis 防重表 + 唯一请求 ID (推荐方案) 备选: - 数据库唯一索引 - 状态机控制 | 1. 所有写操作必须加幂等 2. 请求 ID 必须由前端生成 3. 异常时要删除 Redis Key |
| 防重放攻击 🛡️ | 1. 中间人截取请求重复发送 2. 签名被破解后批量攻击 | 签名 + 时间戳 + nonce 随机数 1. 签名有效期 5-15 分钟 2. nonce 存入 Redis 去重 3. 关键接口增加 IP 白名单 | 1. 禁止使用固定密钥 2. 密钥定期轮换 3. 监控异常签名请求 |
| 大流量限流熔断 🚀 | 1. 突发流量打垮服务 2. 下游服务故障导致雪崩 | Redis 分布式限流 + Sentinel 熔断 1. 限流:令牌桶算法 2. 熔断:慢调用比例 + 异常比例 3. 降级:返回默认值或缓存数据 | 1. 限流阈值要压测得出 2. 熔断后要有告警 3. 核心接口不能降级 |
| 敏感数据脱敏 🔒 | 1. 不同字段脱敏规则不同 2. 业务代码零散处理容易遗漏 | Jackson 自定义序列化器 + 注解java<br>@Sensitive(SensitiveType.PHONE)<br>private String phone;<br> | 1. 日志也要做脱敏 2. 内部接口同样需要脱敏 3. 禁止返回完整身份证号 |
| API 版本兼容 🔄 | 1. 旧版本无法立即下线 2. 多个版本并行维护成本高 | URL 路径版本 + 灰度发布 1. 版本号使用 v1、v2 整数 2. 旧版本保留 3-6 个月 3. 废弃接口添加告警 | 1. 版本内保证向下兼容 2. 禁止在旧版本上新增功能 3. 监控旧版本调用量 |
补充:面试加分项 🌟
如果面试官继续追问,可以补充这两点:
- 灰度发布:通过网关根据用户 ID 或流量比例切流到新版本 API,降低发布风险
- API 网关:将认证、限流、日志、监控等通用逻辑下沉到网关层,业务服务只关注核心逻辑
真实面试模拟
真实面试模拟
面试官 😊:
欢迎,咱们直入正题。今天这道场景设计题很常见:“如果让你从零设计一套对外的API接口,你会怎么考虑?” 就从你踩过的坑,想到哪说到哪。
候选人 🧑💻:
好的面试官,对外API就是一份合同,我会把它当成“法务条文”来设计。核心抓住七个点:接口契约、统一响应、安全三板斧、版本管理、流量控制与幂等、监控文档、异步处理。我按一条请求的生命周期展开说吧。
面试官 👂:
行,先从最直观的开始。你怎么定接口规范,让调用方一眼就能看懂?
候选人 🧑💻:
我严格遵循 RESTful 风格,做到“望文生义”:
- URL 只用名词复数,层级不超过两层,如
/api/v1/orders/{orderId}/items,一看就知道在操作订单下的商品。 - HTTP 方法语义固定:
GET查、POST创、PUT全量改、PATCH部分改、DELETE删。 - 状态码精打细磨:
200/201成功,400参数错,401没认证,403没权限,404不存在,409冲突,429限流,500系统异常。保证调用方只抓状态码就能判断大类。
面试官 💬:
不错,那返回体结构你怎么统一? 我不希望每个接口格式五花八门。
候选人 🧑💻:
我强制所有接口都套一层统一响应信封,长这样:
{
"code": 200,
"message": "success",
"data": { ... },
"traceId": "a1b2c3d4-xxxx"
}code是业务错误码,能唯一定位问题。traceId必须全链路透传,调用方查日志时直接甩这个ID过来,省得扯皮。- 错误时
data为空,message说人话,但绝不暴露数据库或堆栈信息。
面试官 🛡️:
统一格式是基本功。接下来是重头戏:对外接口的安全你怎么落地? 真怕被人一晚上打穿。
候选人 🧑💻:
这块必须上安全三板斧,我画个时序图更清晰:
具体落地:
- 身份+防篡改:给每个合作方发
AppKey+AppSecret,要求请求头带上 HMAC-SHA256 签名。把timestamp、nonce(随机数)、请求体拼起来算签,服务端重算对比。 - 防重放:用
timestamp和nonce联合约束。timestamp偏离服务端时间超过5分钟直接拒掉;nonce存入 Redis,有效期5分钟,出现重复直接视为重放攻击。 - 传输:全站HTTPS,HSTS强制。
面试官 🤔:
挺严密。那我再挑个刺儿:如果调用方坚持自己生成幂等key,不信任你的,怎么防止key冲突?
候选人 🧑💻:
这是个常见的信任问题。我会在调用方传来的key前面强制加上AppKey前缀,变成 app_123:order_submit:20260609。这样既隔离了不同租户,也避免恶意伪造撞库。同时限制key总长度,防止撑爆Redis。
面试官 🔄:
安全过关。接下来,你怎么做版本管理,避免升级时被调用方骂?
候选人 🧑💻:
很简单,永不原地修改。URL路径显式带版本号,如 /api/v1/。一旦有破坏性变更,直接发 /api/v2/,老版本 v1 保持可用至少一个季度,并提前发邮件定好日落时间。最忌讳的就是偷偷改字段类型,那是在制造线上事故💥。
面试官 ⚡:
线上突发流量怎么扛?比如双十一,下游慢怎么办?
候选人 🧑💻:
这要靠限流+降级+幂等组合拳。
- 限流:网关层按
AppKey用令牌桶,每个key 100QPS。超了直接回429,并带上Retry-After头。达到80%阈值就告警,准备扩容。 - 熔断降级:内部服务慢了,熔断器打开,直接返回缓存降级数据,不死扛导致雪崩。
- 写操作幂等:所有创建、扣款接口,必须要求调用方传
Idempotent-Key。我用 Redis 的setnx锁住这个key,处理成功就把结果缓存起来,重复请求直接返回缓存结果。这样就算网超重试,也只成功一次。
客户端重试
│
▼
┌──────────────────────┐
│ 带 Idempotent-Key 请求│
└──────────┬───────────┘
▼
┌──────────────┐
│ Redis 存在key?│
└──┬───────┬───┘
存在 │ │ 不存在
▼ ▼
返回缓存结果 执行业务
│
▼
结果缓存到Redis(锁定15天)
│
▼
返回结果面试官 👨🏫:
嗯,这个图把幂等逻辑讲得很透。
面试官 📊:
接口上线后就是运营了。你怎么让调用方用得爽,自己排查快?
候选人 🧑💻:
两手抓:监控与文档。
- 可观测性:QPS、延迟p99、错误率全上Grafana大盘,错误率突增即时钉钉/飞书告警。
- 在线文档:用OpenAPI 3.0自动生成Swagger页面,调用方能在网页上直接试参数。文档里必须写清错误码、限流规则、幂等key用法,最好提供 Java/Python SDK,帮他们封装好签名、重试、幂等注入,十行代码搞定接入,对接方自然粘性高 🤝。
面试官 ⏳:
最后一个场景,如果接口需要导出大报表,耗时很长,怎么设计交互?
候选人 🧑💻:
绝不同步阻塞。接口立即返回 202 Accepted,body里给一个 taskId。调用方用 /api/v1/tasks/{taskId} 轮询进度。如果甲方有回调能力,我们支持提前注册 webhook,任务一完成主动通知。体验拉满,还不占用连接数。
面试官 🧐:
整体设计没问题,看得出工程经验。不过我好奇具体落地,能不能挑几个核心代码片段,展示下你的技术亮点?另外,总结一下这种对外API设计的几个硬骨头和你的解法。
候选人 🧑💻:
没问题,我挑三段最能体现亮点的代码,然后梳理一张难点攻克表。
💻 核心代码 & 技术亮点
① 统一响应信封 + 全局异常翻译
不写死,用枚举管理错误码,并借助 @RestControllerAdvice 把异常自动翻译成标准信封,避免 try-catch 满天飞。
// 统一响应体
@Data
@AllArgsConstructor
public class ApiResponse<T> {
private int code;
private String message;
private T data;
private String traceId;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "success", data, MDC.get("traceId"));
}
public static ApiResponse<Void> error(ErrorCode errorCode) {
return new ApiResponse<>(errorCode.getCode(), errorCode.getMessage(), null, MDC.get("traceId"));
}
}
// 全局异常处理,任何未捕获异常都会变成标准格式
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BizException.class)
public ApiResponse<Void> handleBizException(BizException e) {
return ApiResponse.error(e.getErrorCode());
}
@ExceptionHandler(Exception.class)
public ApiResponse<Void> handleException(Exception e) {
log.error("系统异常", e);
return ApiResponse.error(ErrorCode.SYSTEM_ERROR);
}
}🌟 亮点:利用 MDC 透传 traceId,响应里自动带出,配合日志系统,一条链路不漏。
② 签名验证拦截器 (HMAC-SHA256 + 防重放)
这是安全核心,抽成独立拦截器,不侵入业务代码。
@Component
public class SignatureInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String appKey = request.getHeader("X-AppKey");
String timestamp = request.getHeader("X-Timestamp");
String nonce = request.getHeader("X-Nonce");
String clientSign = request.getHeader("X-Sign");
// 1. 时间戳偏移校验 (5分钟窗口)
if (Math.abs(System.currentTimeMillis() - Long.parseLong(timestamp)) > 300_000) {
throw new BizException(ErrorCode.TIMESTAMP_EXPIRED);
}
// 2. nonce防重放 Redis setnx 原子排他
String nonceKey = "nonce:" + appKey + ":" + nonce;
Boolean locked = redisTemplate.opsForValue().setIfAbsent(nonceKey, "1", 5, TimeUnit.MINUTES);
if (Boolean.FALSE.equals(locked)) {
throw new BizException(ErrorCode.REPLAY_ATTACK);
}
// 3. 重算签名 (参数+body)
String body = getRequestBody(request); // 缓存body的wrapper
String sign = HmacUtils.hmacSha256Hex(appSecretMap.get(appKey),
appKey + timestamp + nonce + body);
if (!sign.equals(clientSign)) {
throw new BizException(ErrorCode.SIGN_INVALID);
}
return true;
}
}🌟 亮点:setIfAbsent 天然原子性,既防重放又避免锁竞争;签名材料包含 body,防请求体篡改。
③ 幂等注解 + AOP(切面自动处理)
不想每个方法都写重复逻辑,用声明式注解让业务方一个 @Idempotent 搞定。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
String keyPrefix() default ""; // 业务前缀
long lockTime() default 15; // 结果缓存天数
}
@Aspect
@Component
public class IdempotentAspect {
@Around("@annotation(idempotent)")
public Object around(ProceedingJoinPoint point, Idempotent idempotent) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(
RequestContextHolder.getRequestAttributes())).getRequest();
String idempotentKey = request.getHeader("Idempotent-Key");
if (StringUtils.isBlank(idempotentKey)) {
throw new BizException(ErrorCode.MISSING_IDEMPOTENT_KEY);
}
String finalKey = request.getHeader("X-AppKey") + ":" + idempotent.keyPrefix() + ":" + idempotentKey;
String result = redisTemplate.opsForValue().get(finalKey);
if (result != null) {
return JSON.parseObject(result, ApiResponse.class); // 直接返回缓存结果
}
Object proceed = point.proceed();
redisTemplate.opsForValue().set(finalKey, JSON.toJSONString(proceed),
idempotent.lockTime(), TimeUnit.DAYS);
return proceed;
}
}🌟 亮点:AOP 零侵入,缓存完整响应对象,真正“第一次干活,后续全部秒返回”;key 自动注入租户前缀,隔离安全。
🧗 技术难点 & 解决方案
对外 API 不是“能用就行”,这些硬骨头啃不掉就会天天救火 🚒。
| 技术难点 | 核心风险 | 解决方案 | 落地关键点 |
|---|---|---|---|
| 高并发幂等性 | 网络重试导致重复扣款/重复下单 | 基于Redis原子SETNX + 结果缓存 | 强制要求Idempotent-Key,键名包含租户前缀防冲突 |
| 防重放与防篡改 | 攻击者抓包重放,数据被改 | HMAC签名 + 时间窗口 + nonce一次性令牌 | 签名包含请求体、5分钟窗口、nonce Redis暂存 |
| 版本无缝升级 | 老接口废弃导致调用方瘫痪 | URL路径版本号 + 日落期通知 | v1/v2并存,至少保留一个季度,提前邮件周知 |
| 突发流量雪崩 | 双十一流量打垮下游,级联故障 | 网关令牌桶限流 + 业务熔断降级 | 超过阈值返回429,预警告警,降级返回兜底数据 |
| 大任务阻塞连接 | 导出报表等长耗时导致HTTP超时 | 异步化:202 Accepted + 轮询/Webhook | 立即返回taskId,后台处理,支持回调URL主动通知 |
| 调用方接入痛苦 | 签名、幂等、重试逻辑复杂导致弃用 | 提供多语言SDK封装 + 在线交互式文档 | OpenAPI生成文档,SDK仅10行代码完成完整调用 |
| 问题定位困难 | 跨系统调用链断裂,扯皮耗时 | 全链路traceId + 统一错误码字典 | 网关注入traceId,所有响应和日志携带,Grafana串联 |
最后再附一张防御体系全景图,方便记忆:
这个图把最核心的几道防线串起来了,每一层都在保护后面的系统 🤺。
面试官 🚀:
非常好,从代码到难点体系都讲得很实,看得出你是真刀真枪在线上扛过的。这一题咱们就完美收尾,准备进入下个领域。
