目录
- [1. 必须设置超时](#1. 必须设置超时)
- [2. 重试策略,不能无脑重试](#2. 重试策略,不能无脑重试)
- [3. 熔断、降级、隔离](#3. 熔断、降级、隔离)
- [4. 限流](#4. 限流)
- [5. 异常处理,区分不同失败类型](#5. 异常处理,区分不同失败类型)
- [6. 请求参数与响应处理](#6. 请求参数与响应处理)
- [7. 线程池注意](#7. 线程池注意)
- [8. 超时时间的设计,链路整体考虑](#8. 超时时间的设计,链路整体考虑)
- [9. 资源与连接池(HTTP 客户端)](#9. 资源与连接池(HTTP 客户端))
- [10. 业务层面:幂等、数据一致性](#10. 业务层面:幂等、数据一致性)
- [11. 监控告警](#11. 监控告警)
- 面试口述版本
- 高频追问
1. 必须设置超时(重中之重,第一优先级)
问题
不设置超时,线程会无限阻塞等待下游返回,线程池线程被占住不释放,最终线程池打满、队列积压,整个服务雪崩。这是线上最常见故障!
实践
不管是 RestTemplate、OkHttp、HttpClient、Dubbo,必须配置连接超时、读取超时。
- connectTimeout:建立网络连接超时
- readTimeout:接口响应读取超时(业务接口 RT 上限)
示例:普通业务接口,connect 2s,read 3‑5s;大文件导出类接口 read 适当放大。
超时不是越大越好,下游慢,我们及时失败释放线程,保护自己。
面试一句话:调用下游一定要设置超时,防止线程无限阻塞,耗尽线程池。
2. 重试策略,不能无脑重试
什么场景可以重试:幂等接口;查询接口。
不能重试:非幂等写接口(下单、扣款),重试会重复创建订单、重复扣款。
重试注意点:
- 只对特定异常重试:网络异常、连接超时;业务返回业务异常不要重试。比如下游返回参数错误,再重试多少次都失败。
- 一定要加退避策略(间隔时间),不要立刻疯狂重试,避免把下游打垮。
- 设置最大重试次数,不能无限重试。
- 重试带来的负载要算到下游 QPS 评估。
Spring Retry / Resilience4j / Dubbo 自带重试都可以实现。金融系统写接口优先关闭自动重试。
3. 熔断、降级、隔离(Resilience4j/Sentinel)
下游故障,不能把故障传导到本服务。
熔断 CircuitBreaker:下游大量失败,打开熔断,直接本地快速失败,不去调用下游,保护自己,也保护下游。
舱壁隔离 Bulkhead:调用下游的请求单独隔离线程池,下游慢,只占隔离池,不会占用业务核心线程池,不会影响别的业务。
很多人忽略:如果不做舱壁,下游全部卡死,自己业务线程池全部占满,其他无关接口全部挂掉。
汇丰金融项目很看重故障隔离,不能一个第三方挂掉把整个服务拖垮。
4. 限流
对下游做限流,控制发给下游最大 QPS,防止我方流量打垮下游。
自己服务入口也要限流,防止外部流量进来,大量调用下游把下游打崩。
5. 异常处理,区分不同失败类型
调用下游会出现多种失败,分开处理,不要一把全部 catch (Exception) 笼统处理:
- 网络异常:连接失败、超时、socket 异常
- HTTP 状态码:4xx(客户端错误,参数错)、5xx(下游服务报错)
- 业务码:http200,但是 body 里面业务 code 代表业务失败
- 返回体解析异常:返回 JSON 格式不对,解析报错
实践:
- 打印完整日志:请求参数、响应体、耗时、traceId,方便排查;
- 不要吞异常,需要告警;
- 区分是我方参数错误,还是下游故障。
坑:只判断 http 状态码 200 就认为成功,忽略业务 code。
6. 请求参数与响应处理
- 请求体过大,注意序列化,避免大对象序列化耗 CPU;
- 做好响应解析保护,不要直接强转,防止返回格式变更导致解析异常;
- 大返回体,注意内存溢出风险;
- 传递链路 traceId,方便全链路追踪。
7. 线程池注意(和前面线程池知识点联动)
高频考点:调用下游 IO 操作,会阻塞当前线程。
两种情况:
- 在同步接口里面调用下游:占用 web 容器线程(Tomcat 线程),下游慢会耗尽 tomcat 线程,接口全部卡死。
- 在自定义异步线程池调用下游:占业务线程池线程,如果没有超时,线程池耗尽。
最佳实践:
- 调用下游必须设置超时;
- 如果大量调用第三方,建议用单独隔离的线程池(舱壁),第三方故障不会污染核心业务线程池。
8. 超时时间的设计,链路整体考虑
链路总耗时 = 多个下游调用时间总和。
例如网关整体超时 10s,内部服务调用下游每个接口不能设置 8s,多个调用叠加直接超时。
每个下游接口超时要小于整体链路超时。
9. 资源与连接池(HTTP 客户端)
OkHttp、HttpClient 一定要配置连接池,复用 TCP 连接,不要每次请求新建连接。
设置最大连接数,避免创建大量 TCP 连接,出现 too many open files。
10. 业务层面:幂等、数据一致性
- 调用写接口,下游要支持幂等,传入唯一业务 id,防止重试造成重复数据;
- 调用失败,要考虑:回滚、补偿、本地记录调用日志,后续定时任务重试补偿;
- 金融场景,不能简单调用失败就直接丢弃,要落地本地记录,异步补偿。
11. 监控告警
需要监控这些指标:
- 下游调用 QPS
- 调用耗时 p50/p95/p99
- 失败率(超时、5xx、业务异常)
- 熔断状态
失败率到达阈值要告警,不要等到业务报错才发现第三方挂了。
面试口述版本(B5 级别,可以直接复述)
调用下游接口,首先必须设置连接超时、读取超时,防止下游响应慢把我方线程无限阻塞,耗尽线程池或者 Tomcat 线程,造成服务雪崩。
然后是重试,查询类接口可以重试,写接口不能随便重试,需要下游支持幂等;重试要加退避,限制最大次数。
我们会使用 Sentinel 或者 Resilience4j 做熔断和舱壁隔离,把对下游的调用隔离在独立线程池,下游故障不会扩散到核心业务。
做好异常分类处理,区分网络异常、HTTP 状态码、业务错误码,打印完整请求响应日志,带上 traceId 方便排查。
HTTP 客户端要使用连接池复用 TCP 连接。同时评估整体链路超时,单个下游超时不能超过链路总时间。
金融写场景,调用失败不能直接丢弃,落库做补偿;对下游调用做监控,监控耗时、失败率,配置告警。
高频追问
Q:调用下游没设置超时,会发生什么?
线程会一直阻塞等待响应,线程不会释放。同步接口会耗尽 Tomcat 容器线程;异步任务会耗尽业务线程池。最终整个服务无法处理新请求,雪崩。
Q:熔断和舱壁的区别?
熔断:统计失败率,失败多了直接拒绝发起调用,保护下游和自己。
舱壁 Bulkhead:线程池隔离,限制调用该下游最多占用多少线程。哪怕下游全部卡死,最多耗尽隔离池的线程,不影响其他业务。两个组件配合使用。
Q:为什么写接口不能随便重试?
如果接口非幂等,重试会造成重复下单、重复扣款等脏数据;如果要重试,下游必须支持幂等,传入唯一业务标识。