3. 接口响应时间优化策略归纳总结
接口响应时间,说白了就是你的 API 从收到请求到返回结果花了多少毫秒。在金融系统里,这玩意儿直接关系到用户的交易体验,甚至影响合规风控的时效。我遇到过好几家券商,对外接口平均耗时 800ms 以上,一查全是慢查询和序列化开销堆出来的。所以这一章我把多年踩坑换来的优化策略做个系统的总结。
核心思路:先测量,再优化,最后验证。没有监控数据,一切优化都是瞎猜。
下面这张图是我自己画的一张优化路线图,建议你硬记下来,每次做接口性能优化时就按这个框架过一遍。
3.1 先搞定指标体系:没有数据你优化个啥
我个人的习惯是,接手一个接口优化之前,先盯一周的监控。不看 TP99 就动手,很容易把 20ms 的优化做到一个调用量不到 0.1% 的尾巴上。
金融接口必须关注这几个指标:
- TP50(中位数):反映大部分用户的真实体验
- TP95 / TP99:长尾延迟,往往是慢查询或 GC 导致的
- 超时率:金融合规里 5xx 率超过 0.1% 就可能触发警告
- 每秒请求数(QPS):同时评估资源消耗
避坑指南:我曾经在一个量化交易项目里,只关注了平均耗时(80ms),结果上线后实盘频繁断连。一查 TP99 高达 3 秒——原来某只大单行情消息的序列化出了严重阻塞。从此我定了个规矩:优化前必须拉 TP99 和超时率曲线。
3.2 数据库层:金融系统 70% 的瓶颈都在这里
大部分金融接口背后是数据库查询,尤其账户余额、持仓、交易流水这些。你想想看,一次跨库 JOIN 加上几百行的结果集,响应时间能不崩吗?
3.2.1 索引优化
不要相信「索引越多越好」。我曾经接手过一个账户查询接口,一张表挂了 12 个索引,查询反而更慢——因为 MySQL 选错了索引。解决方案就是干掉冗余索引,建联合索引覆盖最频繁的查询。
-- 常见慢查询:按客户号和产品号查持仓
-- 正确联合索引
CREATE INDEX idx_cust_prod ON position (cust_id, product_id);
-- 避免 SELECT *,只取必要字段
SELECT position_id, qty, cost_price FROM position WHERE cust_id = ? AND product_id = ?;
3.2.2 连接池调优
金融系统并发高,数据库连接池太小会排队等待,太大又挤爆数据库。我一般用 HikariCP,参数这样配:
# HikariCP 典型配置(压测后微调)
spring.datasource.hikari.maximum-pool-size=50
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.connection-timeout=5000 # 等 5 秒还拿不到就放弃
3.2.3 读写分离 & 降级
查询类接口尽量走只读库,极端场景可以降级到缓存。有个小技巧:在 DAO 层用 @Transactional(readOnly = true) 强制路由到从库。
3.3 缓存策略:把压舱石搬进内存
缓存是降低响应时间最立竿见影的手段。但别全扔给 Redis,本地缓存的优势是零网络开销。
| 缓存类型 | 典型场景 | 注意点 |
|---|---|---|
| 本地缓存 | 配置信息、行情快照(微秒级) | 内存上限、TTL、缓存穿透 |
| 分布式缓存 | 用户会话、交易中间结果 | 序列化开销、大 key 问题 |
我建议金融系统把热点数据做两层缓存:本地存最新热点,Redis 存全量。这样就算 Redis 短暂抖动,本地缓存还能抗住 90% 的请求。
重要警告:资金相关的数据(如余额)慎用缓存!强一致性场景建议直接查库或使用分布式锁更新缓存。
3.4 并发与异步:别让线程等你
金融接口常常需要调用多个下游服务:核价、风控、清算……如果串行调用,时间就是累加的。我习惯使用 CompletableFuture 做并发调用,并且在网关层用消息队列做异步写。
3.4.1 线程池参数
线程池大小不是越大越好。一个经验公式:线程数 = CPU 核心数 * (1 + 等待时间/计算时间)。金融接口多为 IO 密集型,等待时间比计算时间长,所以适当调大。别用无界队列,容易把线程池撑成内存泄漏。
// 线程池配置示例(可动态调整)
ThreadPoolExecutor executor = new ThreadPoolExecutor(
20, // corePoolSize
100, // maxPoolSize
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200), // 有界队列
new NamedThreadFactory("biz-pool"),
new CallerRunsPolicy() // 拒绝策略:让调用方自己运行
);
3.4.2 异步写 vs 同步写
对账、流水记录这类不要求实时返回的操作,用 MQ 削峰填谷。我曾优化过一个清算接口,把本地写库改成发送到 Kafka 后异步落库,接口响应时间从 1200ms 降到 85ms。
3.5 网络与序列化:最后一根稻草
很多时候业务逻辑已经很快了,响应时间还是下不来。为什么?序列化太重,连接反复创建。
- 替换 JSON 为 Protobuf 或 Kryo:我测试过,同样 500 个字段的行情数据,JSON 序列化耗时 3.2ms,Protobuf 只需要 0.6ms。
- 启用 HTTP/2 或 gRPC:连接复用,减少 TCP 握手开销。内部微服务通信我优先选 gRPC。
- 开启数据压缩:对网关出口流量大时,gzip 压缩能节省带宽但增加 CPU 消耗。要权衡。
// gRPC 与 HTTP/1.1 耗时对比(内部测试,单位 ms)
// 场景:传输 2KB 的订单结构
// HTTP/1.1 + JSON → 平均 12ms
// gRPC + Protobuf → 平均 3.1ms
3.6 代码级其他优化清单
还有一些零碎但重要的点,我列成清单方便你自查:
- 去掉无用的日志打印:尤其是循环里的。
- 对象复用:用对象池减少 GC 压力。
- 批量接口合并:比如余额查询一次查 100 个账户比查 100 次快得多。
- 热部署/类加载问题:金融系统重启成本高,注意不要用 DevTools 影响性能。
- 限流与熔断:保护接口不被突发流量打垮,间接稳定响应时间。
记住一个原则:优化响应时间不是削足适履,而是在满足业务一致性和合规的前提下,剔除每一点多余的等待。
我个人这些年优化过的接口加起来有两三百个,发现 80% 的收益都来自数据库查询和序列化这两个环节。所以你先从这里动手,大概率能见到立竿见影的效果。剩下的 20% 再慢慢扣线程池、网络和代码细节。