先问一个问题:如果面试官问你“接口性能优化有哪些手段”,你第一反应是什么?估计大多数人是“加缓存、加索引、分库分表”这么三板斧。这样说没错,但放在京东这种体量的业务里,面试官大概率会追问一句“还有呢?”或者“具体什么场景下用哪种方案,能讲细一点吗?”。
背八股和真正做过性能排查的人,差别就在这里。这篇文章我按自己的真实经验,把接口性能优化从发现问题、定位瓶颈,到分层优化的完整思路捋一遍。不空谈理论,尽量把每一类手段背后的适用场景、实现细节和容易踩的坑都讲清楚。内容按“先发现问题、再定位根因、再逐层优化、最后加保护”的顺序组织,既适合准备面试时系统梳理,也适合实际项目里照着排查。
1. 性能优化的第一步不是优化,而是先定义“慢”
我见过不少团队做性能优化,上来就闷头改代码,改了半天发现用户根本不买账。原因很简单:他们压根没搞清楚“慢”的标准是什么。你连目标都没有,怎么知道优化到位没有?
1.1 用哪些指标衡量接口性能
衡量一个接口的性能,不能只看一个数字。通常我会同时盯住下面几个维度:
- 响应时间(RT):从请求发出到收到完整响应的时间。
- 吞吐量(QPS/TPS):系统单位时间内能处理的请求数。
- 错误率:5xx、超时、业务异常的比例。
- 资源利用率:CPU、内存、磁盘IO、网络带宽,以及数据库连接池、线程池的占用情况。
- 慢查询数量:数据库层面执行时间超过阈值的SQL数量。
这里特别想说一下TP99这个指标。很多人只看平均响应时间,其实平均值的参考意义很有限——几个超慢请求就能把平均值拉高,但大多数用户实际体验可能还行。TP99才是真正反映“绝大多数用户感受”的指标,意思是99%的请求耗时都小于这个值。我在压测和线上监控里几乎都以TP99为准。
1.2 不同业务有完全不同的性能基线
“接口响应时间小于200ms”不是所有业务的硬性标准。比如:
- 用户点击一个按钮查余额,这种场景TP99就应该控制在500ms以内。
- 上传一个高清视频,文件几GB,接口耗时几十秒也能接受,关键是进度反馈要准。
- 批量导出报表,小时级甚至异步通知都行,但不能让用户一直白屏等着。
我见过有人拿同一套“慢查询阈值”去卡所有接口,结果业务方被打扰得不行。性能基线的制定要结合业务形态:同步写操作、同步读操作、异步任务、批处理任务、流式接口,标准完全不同。如果你在面试里能讲出“我会按业务场景区分性能基线”,就已经比单纯背几个指标强很多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 一次接口变慢的真实定位过程,比直接背方案重要十倍
性能优化最关键的能力其实是排查。很多人知道各种优化方案,但是面对一个“突然变慢”的线上接口,不知道从哪里下手。我分享一个典型的定位链路。
2.1 先看监控,确定“慢”的范围和趋势
收到报警或者业务反馈之后,第一件事不是翻代码,而是打开监控看三个问题:
- 慢是整个系统都慢,还是某一个接口慢? 如果全站都慢,大概率是基础设施问题,比如数据库CPU被打满、网络抖动、依赖的公共组件故障。
- 是从什么时间节点开始慢的? 配合发布记录,经常能发现是某次上线引入的问题。
- 是持续慢,还是有规律地慢? 比如每到整点慢,那很可能跟定时任务抢资源有关;流量高峰慢,那就是容量不够。
有链路追踪系统(比如SkyWalking、Zipkin、CAT)就直接看链路上的耗时分布,哪个环节耗时最长一眼就能定位。没有的话,就得靠日志时间戳和数据库慢查询日志硬推。
2.2 分层排查法:网络、系统、应用、存储逐层拆解
我习惯把一次请求的完整路径拆成四层,逐层排查:
| 层次 | 排查重点 | 常用手段 |
|---|---|---|
| 网络层 | DNS解析、网络延迟、丢包、带宽 | ping、traceroute、抓包 |
| 系统层 | CPU、内存、磁盘IO、文件句柄 | top、vmstat、iostat、dmesg |
| 应用层 | 线程池、连接池、GC、代码逻辑 | jstack、jmap、arthas、日志分析 |
| 存储层 | 慢SQL、锁等待、连接数、缓存命中率 | 慢查询日志、show processlist、explain |
一个实用的习惯是从存储层开始往上查。根据我自己的经验,接口变慢的根因里有超过一半都出在数据库:慢查询、锁竞争、连接池被占满。先用show processlist看一眼数据库当前在跑什么,往往比在应用代码里猜半天更高效。
2.3 一个真实案例:外部依赖超时是如何拖垮整个应用的
我说一个自己踩过的坑。某次线上接口突然大面积超时,监控看应用CPU、内存都正常,数据库也没有慢查询。最后用jstack抓线程栈,发现大量线程都阻塞在一个地方——调用外部支付网关的HTTP请求上。
原因很简单:没有设置合理的超时时间。某个第三方接口响应突然变慢,最慢的能拖到几十秒,而Tomcat的线程池是有限的,线程全被这些等外部响应的请求占住了,新的请求排不上队,整个应用的接口就全堵死了。
这个案例告诉我们两件事:
- 调用外部依赖必须设置连接超时和读取超时,而且要结合业务容忍度设得尽量小,宁可失败快速返回,也不要无限等待。
- 涉及外部调用的接口,必须做线程池隔离或者信号量隔离,不能把所有接口的线程都放在一个池子里互相影响。
后来我用线程池隔离把外部调用单独隔离出去,再配合熔断器,这个问题就再没出现过。这种经历在面试里讲出来,比背一百个优化方案都更有说服力。
3. 数据库才是大多数接口慢的根因,索引、SQL和连接池都要查
把问题定位到数据库之后,接下来就是具体的优化动作。数据库层面的优化是所有接口性能优化里见效最快、同时也是最容易出问题的一环。
3.1 索引失效的常见场景,对照自查
很多接口慢,一查就是SQL走了全表扫描。但很多人建了索引还是慢,原因多半是 SQL 写法导致索引没生效。我整理了一个高频失效场景表,建议收藏:
| 场景 | 原因 | 正确做法 |
|---|---|---|
| 对索引列用了函数 | WHERE DATE(create_time) = '2024-01-01' |
改成 create_time >= '2024-01-01' AND create_time < '2024-01-02' |
| 隐式类型转换 | 索引列是varchar,查询条件传了数字 | 保证查询参数类型与列类型一致 |
| 前导模糊查询 | LIKE '%keyword' |
改全文索引,或ES,或改后缀匹配 |
| 索引列参与运算 | WHERE price * 2 > 100 |
把运算移到等号右侧 |
| OR连接非索引列 | WHERE a = 1 OR b = 2,b无索引 |
拆成两个查询UNION,或者给b也建索引 |
| 复合索引未满足最左前缀 | 复合索引(a,b,c),条件只用了b | 调整索引顺序或查询条件 |
| 范围查询右侧的列 | 复合索引(a,b),a用了范围查询 | 把范围查询列放在复合索引的最后 |
这个表里的每一条,我都踩过,也帮别人排查过。最典型的坑是隐式类型转换:表中的order_no是varchar类型,代码里传的是Long类型,MySQL会自动把varchar转成数字再比较,结果索引直接失效。这种问题光看SQL根本发现不了,必须用EXPLAIN看执行计划才能看到端倪。
3.2 用EXPLAIN分析一个慢查询的完整过程
说到EXPLAIN,我建议每个人都掌握至少这几个字段的含义:
- type:访问类型,从好到坏依次是
const、eq_ref、ref、range、index、ALL。看到ALL基本就是全表扫描,要警惕。 - key:实际用到的索引,如果为NULL说明没用索引。
- rows:预估扫描的行数,这个数字越大,SQL通常越慢。
- Extra:如果出现
Using filesort或Using temporary,说明排序和分组用了临时文件,是很明显的性能隐患。
实战中我遇到过这样一个SQL:
sql复制SELECT * FROM orders
WHERE user_id = 10086
ORDER BY create_time DESC
LIMIT 10;
这个SQL看起来没什么问题,user_id也建了索引。但EXPLAIN发现Extra显示Using filesort,因为user_id索引只帮我们过滤了记录,排序还是要另外做。数据量小时无所谓,数据量达到千万级之后,每次查询都要额外排序,就会明显变慢。
优化方案是建立一个联合索引(user_id, create_time),这样索引本身就已经按用户和创建时间排好序了,查询的时候可以直接按索引顺序取数,排序操作就省了。就这么一个索引的调整,接口耗时从800ms降到50ms。索引不是建得越多越好,而是要贴近你的查询模式和排序需求。
3.3 深分页慢,其实不是LIMIT的锅
分页查询到了后期会越来越慢,比如:
sql复制SELECT * FROM orders
WHERE user_id = 10086
ORDER BY id
LIMIT 100000, 20;
这个查询慢的原因是:MySQL虽然通过user_id索引定位了数据,但要拿到第100000条之后的20条,它得先把前面的100000条都扫描一遍再丢弃,扫描的成本非常高。
比较通用的优化方式有两种:
- 延迟关联:先用覆盖索引查出目标主键,再回表取完整数据。
sql复制SELECT o.* FROM orders o
INNER JOIN (SELECT id FROM orders WHERE user_id = 10086 ORDER BY id LIMIT 100000, 20) t
ON o.id = t.id;
- 基于游标的分页:把LIMIT OFFSET改成“WHERE id > 上一页最后一条的id LIMIT 20”。
实际业务里,我倾向于用游标分页,尤其适合资讯流、订单列表这种场景。它不需要扫描前面所有的记录,而且数据量大之后性能稳定,不会随着翻页越来越慢。
3.4 连接池被打满,接口也会“假慢”
还有一个容易被忽视的数据库层面问题:连接池耗尽。当数据库连接池的所有连接都被占满,新的数据库操作就要排队等待获取连接,接口的表现就是“特别慢”,但SQL本身可能根本不慢。
排查方法是看应用日志里有没有获取连接超时的异常,以及show processlist里的连接数量是否接近max_connections。解决思路有几个方向:
- 调大连接池上限,但注意不要超过数据库允许的最大连接数。
- 排查是否有连接泄漏——获取了连接但没有释放。
- 看是否有慢SQL长时间占用连接不归还,先把慢SQL解决掉,连接池压力自然就缓解了。
- 对读写分离的架构,确保读操作走从库,减轻主库压力。
4. 缓存设计:重点不是“加缓存”,而是“怎么加才不出事”
缓存确实是性能优化里最立竿见影的手段,但也是出问题最频繁的手段。很多线上事故,恰恰是加了缓存之后出现的。我分几个层面说清楚。
4.1 缓存到底解决了什么问题,又解决不了什么
缓存的核心作用是把重复的热点读请求从数据库转移到更快的存储上。比如一个商品详情接口,数据库查询要100ms,Redis查询只要1ms,加上缓存后接口耗时直接下降一个量级。
但缓存解决不了所有问题,我总结过三个“不适合”:
- 数据更新极其频繁且一致性要求极高的场景,比如实时库存。这种情况缓存的意义不大,反而一致性包袱很重。
- 每次请求的数据都不同,几乎没有重复读,比如个性化推荐流。缓存命中率极低,形同虚设。
- 写多读少的场景,缓存带来的收益有限,反而可能在缓存与数据库之间产生大量不一致问题。
面试时如果能说出“我会先评估数据的读写比和热点集中度,再决定要不要上缓存”,比张口就“加缓存”高级很多。
4.2 缓存穿透、击穿、雪崩的应对细节
这三个概念几乎是必考内容,但很多人只能说出名字,具体怎么应对就含糊了。我把自己实际用过的方案列一下。
缓存穿透:查询一个不存在的key,缓存和数据库都没有,请求直接打到数据库。如果攻击者知道了接口规律,大量用不存在的ID请求,数据库压力会瞬间飙升。
应对方案:
- 缓存空值:即使查询结果不存在,也在缓存里放一个空值,并设置较短的过期时间(比如1-5分钟)。
- 布隆过滤器:启动时把数据的主键全部加载到布隆过滤器里,查询前先判断key是否存在,不存在直接返回,不查数据库。这个方案比较省内存,适合数据量大的场景。
缓存击穿:某一个热点key过期,恰好此时大量请求同时访问这个key,请求全部落到数据库上。
应对方案:
- 互斥锁:当缓存不存在时,只让一个线程去数据库查询并重建缓存,其他线程等待。实现可以用Redis的SETNX命令,但要注意设置锁超时时间,防止持锁线程崩溃导致死锁。
- 热点key永不过期:不给这个key设置过期时间,后台异步更新。这个方案适合最终一致性可以接受的场景,而且实现逻辑要仔细设计,不能导致数据长期不更新。
缓存雪崩:大量key在同一时间段集中过期,或者缓存服务整体不可用,导致大量请求同时打到数据库,数据库直接被压垮。
应对方案:
- 过期时间加随机值:比如基础过期时间5分钟,加上0-60秒的随机偏移,避免同一批key同时过期。
- 多级缓存:本地缓存(如Caffeine)加上分布式缓存(如Redis),即使Redis挂了,本地缓存还能扛住一部分流量。
- 缓存服务高可用:Redis集群、哨兵模式,避免单点。
- 服务降级:缓存不可用时,对非核心数据返回默认值或降级数据,保护数据库不被打挂。
4.3 缓存和数据库的一致性,几种常用策略对比
缓存和数据库的数据一致性,是面试里很容易问深的地方。用我自己的理解来总结一下:
| 策略 | 做法 | 风险与适用场景 |
|---|---|---|
| Cache Aside(旁路缓存) | 读的时候先读缓存,没有则读数据库回填;写的时候先更新数据库,再删除缓存 | 最简单常用,有短时间不一致窗口。适合一致性要求不极端的场景 |
| Read Through / Write Through | 由缓存组件负责读写数据库 | 实现复杂度高,一致性较好,但整套组件要封装好 |
| Write Behind(异步写回) | 先更新缓存,异步批量写数据库 | 性能极高,但宕机可能丢数据,适合对数据丢失不敏感的场景 |
| 延迟双删 | 先删缓存,更新数据库,再延迟删除一次缓存 | 解决并发读写导致的不一致,但实现要处理延迟时间 |
| 订阅binlog同步 | 通过监听数据库binlog异步刷新缓存 | 业务侵入小,一致性好,依赖中间件(如Canal) |
我在实际项目中最常用的是Cache Aside,加一个延迟双删来降低极端并发下的不一致概率。要注意的是,没有任何方案能在保证性能和强一致性的同时做到零成本,架构决策本质上就是取舍。把这个取舍逻辑讲清楚,面试官会觉得你是有真实思考的。
5. 从单个请求提速到整体吞吐提升,并发出招才是进阶玩法
很多接口慢,不是某一个环节慢,而是整个请求的处理过程是串行的:先查用户信息,再查订单列表,再查优惠信息,一个接一个地查,总耗时自然特别长。这种场景下,需要从“并发”、“批量”、“异步”三个维度去做优化。
5.1 串行改并行:CompletableFuture的正确用法
一个接口需要同时获取多个相互独立的数据时,完全可以用并行调用,而不是串行等待。Java 8的CompletableFuture就是干这个的。下面是我在实际项目中用过的例子:
java复制public OrderDetailVO getOrderDetail(Long orderId) {
// 三个互不依赖的查询,并行执行
CompletableFuture<OrderVO> orderFuture = CompletableFuture
.supplyAsync(() -> orderService.getOrder(orderId), orderThreadPool);
CompletableFuture<UserVO> userFuture = CompletableFuture
.supplyAsync(() -> userService.getUser(orderId), orderThreadPool);
CompletableFuture<List<ItemVO>> itemsFuture = CompletableFuture
.supplyAsync(() -> itemService.getItems(orderId), orderThreadPool);
// 等待所有任务完成,然后组装结果
CompletableFuture.allOf(orderFuture, userFuture, itemsFuture).join();
return OrderDetailVO.builder()
.order(orderFuture.getNow(null))
.user(userFuture.getNow(null))
.items(itemsFuture.getNow(null))
.build();
}
这里有两个细节要特别注意:
- 必须传入自定义线程池,不能使用默认的
ForkJoinPool.commonPool()。默认线程池的线程数太少(CPU核数-1),而且多个接口共用,容易互相干扰。 - 合理设置线程池大小。如果是IO密集型任务(查数据库、调外部接口),可以设置
CPU核数 * 2到CPU核数 * 4左右;如果是CPU密集型任务,建议与CPU核数接近就好,开太多线程反而增加上下文切换开销。
我实际测过,一个原本串行需要300ms的接口,改成并行之后变成120ms,性能提升非常明显且代价很低。在动手加缓存之前,先看自己是不是把能并行的操作串行做了,这个优化经常被忽略。
5.2 批量查询好过循环单查,一个N+1问题的优化实例
有些同学在循环里逐条查数据库,这是非常典型的性能杀手。比如统计一个订单列表里每个订单的商品名称:
java复制// 不推荐:循环里单查数据库
for (Order order : orderList) {
Item item = itemMapper.selectById(order.getItemId());
// ...
}
如果orderList有100条,就会产生100次数据库查询,也就是传说中的N+1问题。正确的做法是查一次,把数据一次性捞出来,再在内存里做关联:
java复制// 推荐:先收集ID,一次批量查询
List<Long> itemIds = orderList.stream()
.map(Order::getItemId)
.distinct()
.collect(Collectors.toList());
List<Item> items = itemMapper.selectBatchIds(itemIds);
Map<Long, Item> itemMap = items.stream()
.collect(Collectors.toMap(Item::getId, Function.identity()));
这里再提醒一个细节:批量查询的IN条件一次性不要塞太多值。MySQL的IN列表虽然理论上可以放很多,但实际执行计划可能会退化,而且网络传输也累。我一般控制在500-1000个ID一批,数据量更大就分批查再合并。
同样的逻辑也适用于外部接口调用。需要批量获取信息时,优先看对方有没有提供批量接口;没有的话,再用线程池并行调用,把耗时从“数量 * 单次耗时”降成“单次耗时 + 少量的排队时间”。
5.3 异步化:把不核心的操作挪出请求链路
有些操作在用户的请求链路上其实不是必须同步完成的,比如:
- 发送短信通知
- 推送站内信
- 记录操作日志
- 更新统计指标
- 生成对账文件
这些操作如果同步执行,会白白增加接口耗时。把它们放到消息队列里异步处理,接口只处理核心业务,返回速度会快很多。
但是这里必须泼一盆冷水:异步化不是万能的。如果用户需要立即看到结果(比如支付成功后立即显示支付状态),就不能简单地把核心状态更新丢到MQ里。我在实际项目里见过把用户下单的核心流程异步化,结果用户下单后刷新页面订单还是“待支付”,投诉一大堆。异步化之前,一定要想清楚业务对“实时性”的真实要求。
6. 光会加速还不够,限流、熔断与降级才能保证“关键时刻不掉链子”
性能和稳定性是一体两面。一个接口优化得再快,如果遇到突发流量直接被打挂,那之前的优化也等于白费。所以真正的接口性能优化,一定包含对系统进行保护的内容。
6.1 限流的四种算法,各自的适用场景
限流的本质是控制单位时间内的请求量,保证系统在能力范围内运行。具体实现上有四种常见算法:
| 算法 | 原理 | 优点 | 缺点 | 典型场景 |
|---|---|---|---|---|
| 固定窗口计数器 | 单位时间内计数,超限直接拒绝 | 实现简单 | 窗口切换瞬间可能产生双倍流量 | 简单的接口限流 |
| 滑动窗口 | 把时间窗口细分成多个小格,滑动统计 | 解决临界突变问题 | 内存开销稍大 | 对突发流量敏感的场景 |
| 漏桶 | 请求先进入桶,以固定速率流出 | 流量平滑,保护下游恒定 | 无法应对突发流量 | 保护数据库、下游依赖 |
| 令牌桶 | 以固定速率生成令牌,请求要取到令牌才能执行 | 允许一定突发 | 当桶被占满时仍然会丢弃新请求 | 电商大促、允许突发流量的场景 |
我个人在业务接口上用令牌桶比较多,因为它能同时兼顾“平滑”和“允许一定突发”。比如商品详情页遇到热点活动时,每秒可能突然涌进几千个请求,如果采用漏桶算法,请求都被强行平摊,用户体验反而不好。令牌桶允许这一瞬间的突发,只要令牌桶里有足够的令牌,就能直接放行。
限流的粒度也要注意:可以按接口维度限流,也可以按用户维度限流,甚至按IP限流。不同的业务选择合适的维度,避免“一个接口限流拖累所有用户”。
6.2 熔断的三个核心参数,调参要结合业务
熔断是当依赖的某个外部服务或资源出现故障时,不再继续发起请求,而是直接快速失败,避免整个系统因为一个依赖故障而雪崩。常见的实现框架有Sentinel、Hystrix、Resilience4j。
使用熔断器时,最核心的参数是这三个:
- 失败率阈值:例如滑动窗口内请求失败率达到50%,就触发熔断。
- 滑动窗口大小:统计失败率的时间窗口,比如10秒。
- 熔断持续时间:熔断持续多久,比如5秒后进入半开状态,尝试放行少量请求验证服务是否恢复。
参数的设置不能拍脑袋。如果失败率阈值设得太低(比如10%),一次小范围的网络抖动就会导致熔断触发,反而加重了系统的不可用;设得太高(比如90%),熔断机制又起不到保护作用。我一般会结合线上历史数据来设定,先看过去一周该接口的正常失败率波动范围,再在这个基础上设置阈值。
6.3 降级:有损服务也是方案
降级是指在系统资源紧张或者依赖不可用时,主动放弃一些非核心功能,保证核心功能的可用性。我在项目中经常用到的降级思路有:
- 返回兜底数据:比如推荐系统挂了,返回热门榜数据而不是直接报错。
- 简化业务流程:比如大促期间暂时关闭“猜你喜欢”等重计算模块。
- 屏蔽非核心依赖:比如评论功能挂了,订单流程不能跟着失败,要允许“无评论的订单照样提交”。
在面试里,如果你能把“降级”和“用户体验”的关系讲明白——比如“我们在大促时把非核心功能降级,虽然页面信息少了一点,但核心下单链路始终是稳定的”,面试官会认为你真的理解线上系统是怎么运转的。
7. 容易忽略的三块细节:序列化、幂等与压测闭环
最后补几个在索引、缓存、并发之外,经常被忽略但影响深远的细节点。
7.1 大字段与序列化,其实挺影响性能
接口返回的数据体积越大,网络传输时间越长,前端解析也越慢。有些接口为了“图省事”,把表格所有字段全部返给前端,一次返回几MB甚至十几MB的JSON,接口性能想好都难。
优化手段有几个:
- 精简字段:只返回前端需要的字段。可以用专门的VO类做字段裁剪,别直接把数据库Entity返回给前端。
- 压缩传输:HTTP层启用Gzip压缩,对JSON体积有明显的缩减效果。
- 二进制序列化替代JSON:如果是对性能要求极高的内部接口,可以考虑Protobuf、MessagePack等二进制格式,体积和解析速度都比JSON有优势。
- 分页/懒加载:大数据量列表不要一次返回全部,用分页或者滚动加载的方式,减小单次传输压力。
顺便提一下,前端也要处理大对象。有些性能问题不在后端,而在前端对返回的巨型JSON做了JSON.stringify后再存进缓存,这个操作本身就很耗时。前后端配合才能把接口性能真正优化到位。
7.2 幂等设计,对写接口性能同样有影响
幂等性听起来跟性能没什么关系,但实际上,如果没有幂等设计,用户重复提交订单、支付回调重复通知,系统就需要做各种兜底处理,反而会拖慢正常接口的性能。而且更重要的是,面试中讲到写接口优化时提到幂等,会显得你的知识体系很完整。
常用的幂等方案:
- 数据库唯一索引:比如订单号唯一约束,重复插入直接报错。
- Token机制:前端提交前先获取一个token,提交时带上,后端校验token是否已消费,首次提交成功消费token,重复提交直接拒绝。
- 状态机校验:比如支付回调只允许“待支付”状态流转到“支付成功”,如果状态已经是“支付成功”,直接返回成功,不重复处理。
幂等设计到位,很多重复请求可以直接返回结果,其实也是在保护接口性能。
7.3 性能优化一定要用压测数据说话
我认为性能优化最重要的一条经验是:所有改动都要用数据验证,不能靠感觉。一个接口从800ms优化到200ms,不是说改完代码上线就完事了,必须做压测验证。
具体的做法是:
- 优化前先压测,记录基线数据(比如QPS、RT、TP99、错误率)。
- 优化后再压测,对比同一压力模型下的数据变化。
- 关注优化是否引入了新的瓶颈。比如你加了缓存,数据库压力降了,但Redis的CPU是不是上来了?网络带宽够不够?
- 回归测试,确保优化的同时没有破坏业务逻辑。
压测工具有很多,简单场景用JMeter或者wrk,复杂场景可以用GoReplay做线上流量回放。压测的流量模型要尽量贴近真实业务,不然数据没有参考价值。
我见过很多新人做了优化之后不压测就上线,结果上线后发现某个隐藏的并发问题,反而把系统搞垮了,这就是典型的“优化了个寂寞”。性能优化的本质是一个闭环:发现问题、定位原因、执行优化、压测验证、持续监控。每一步都必须有数据支撑。
最后再分享一点个人体会:接口性能优化这件事,没有银弹。面试的时候不用把上面所有手段全讲一遍,而是应该根据面试官的具体问题,讲清楚你选择某项手段的理由,以及它适用的场景和边界。而真正做项目的时候,先定位瓶颈再动手,永远比拿到方案就改代码更重要。希望这篇整理能帮你在面试里讲出层次,也能在实际项目中少走一些弯路。
