ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java接口性能优化:15个实战技巧从瓶颈定位到链路改造

Java接口性能优化:15个实战技巧从瓶颈定位到链路改造 先讲一个我印象特别深的场景。线上有个订单详情接口平时P99在60ms左右某天突然涨到800ms页面转圈转得用户投诉不断。我上去一看代码逻辑没有任何改动数据库也没出明显故障最后用Arthas一查线程栈才发现Tomcat线程池被一个下游优惠券服务的慢请求占满了所有请求排着队等线程而那个慢请求根本没有设置超时。这种问题典型到不能再典型但它恰恰说明了一件事Java接口性能优化从来不是单纯改几行代码的事而是一个从链路设计到数据访问再到并发控制的系统工程。这篇内容适合正在做Java后端、被线上接口RT问题折磨过的新手也适合想系统化梳理优化思路的资深开发。我不会按教科书的方式给你讲概念而是按我自己在实战里的优化路径来写先讲怎么定位瓶颈再按链路设计、数据层、JVM与并发、异步化、监控压测六个维度拆开一共15个技巧每个都结合真实场景说清楚。你能直接拿去用的有代码、有参数、有避坑经验。1. 优化前先想清楚瓶颈到底在哪一层1.1 接口慢慢在哪个环节一个接口从客户端发起到拿到响应中间经过的环节远比你想的多DNS解析、网络传输、网关路由、应用层业务处理、线程池调度、数据库查询、下游RPC调用、序列化、GC……任何一环出问题最终表现出来都是“接口慢了”。很多人的第一个反应是看业务代码对着一个方法抠来抠去。但你想想如果问题是数据库索引没命中业务代码写得再漂亮也没用如果问题是下游服务超时你在本应用里优化一百次也无济于事。所以第一步永远是先搞清楚慢在哪一层。这里我有个习惯拿到一个变慢的接口先按“客户端→网关→应用→数据库/下游”这个链条把每个环节的耗时拆一遍。应用内的耗时可以用Arthas的trace命令去看方法级耗时数据库和下游则通过监控平台看响应时间。拆完之后再决定往哪个方向优化。用餐厅点餐来类比用户等餐时间长了可能是收银台慢、可能是后厨慢、可能是传菜慢你不能只盯着“菜炒得不好吃”这个问题。1.2 用数据说话别靠猜不要用“我感觉”、“我猜”来定位性能问题一切以数据为准。我最常用的是三个硬指标RT的P99、QPS、错误率。为什么用P99而不是平均RT因为平均值太容易被“大多数正常少数毛刺”给平滑掉。举个例子某接口平均RT是50ms听起来很健康但P99可能是1.5秒说明每100个请求里就有1个用户卡了整整1.5秒——这种体验问题平均值完全看不出来。P99之外还要看CPU使用率、内存占用、GC频率、数据库连接池活跃数、Redis耗时、网络带宽。我见过一个项目接口变慢的表现是CPU持续在90%以上数据拉出来才发现是反序列化占了60%的CPU问题根本不在数据库。所以我的建议是遇到线上接口变慢先截取一段典型时间窗口把上面这些数据全部拉出来对着看往往一眼就能锁定嫌疑对象。1.3 最容易踩的优化误区误区一的典型表现是接口慢了先改JVM参数把堆内存调大把GC改回收器。不是说JVM调优没用而是没有经过GC日志分析就乱调大概率是白忙一场甚至调得更差。误区二是没有压测就上线改完代码直接发布结果第二天线上数据更差了连回滚都不知道回滚到哪一步。误区三更隐蔽只盯着一个点优化。比如花了三天把某个方法的代码优化得很漂亮结果整个链路里这个方法只占5%的耗时收益微乎其微。还有个误区很少人提就是“为了优化而优化”。有一种优化叫过度设计把一个普通读接口改成多级缓存加异步加载复杂度上去了收益可能就几毫秒。优化的投入产出比一定要算清楚真正值得优化的永远是最慢的那一段。2. 链路设计从源头减少工作量2.1 技巧1响应体瘦身最容易被忽略的优化很多人优化接口盯着代码和SQL看半天却忽略了一个事实响应体本身就是性能的一部分。序列化耗时、网络传输耗时、客户端解析耗时全都和响应体的大小正相关。我经手过一个真事一个列表接口返回了35KB的数据前端页面只用了其中5个字段那35KB里有大量嵌套对象、空字段、冗余信息。后来按场景拆了一个精简版DTO只保留前端需要的字段再把Jackson的空值输出关掉响应体从35KB降到7KB接口P99从120ms直接降到80ms。实操上记住三件事。第一按使用场景拆分DTO列表页、详情页、移动端各一套不要一个万能大DTO走天下。第二配置序列化框架忽略null字段Jackson可以用JsonInclude.Include.NON_NULL这一点改动很小但效果明显。第三内部接口的字段命可以适当地精简但对外接口的契约要稳定删除字段一定要走版本迭代流程。这里有个度的问题瘦身别把语义删没了接口可读性也很重要。2.2 技巧2并行调用替代串行调用把等待时间重叠起来接口里最容易出现的隐性浪费是串行调用多个无依赖的服务。假设一个订单详情接口要查用户信息、订单信息、商品信息、库存信息、优惠券信息每个下游调用平均50ms串行就是250ms。但实际上这些信息互相没有依赖完全可以把它们并行发出总耗时只有最慢的那个大概50到80ms。这一块我常用CompletableFuture来做代码很简洁ExecutorService bizPool Executors.newFixedThreadPool(8); CompletableFutureUserVO userFuture CompletableFuture .supplyAsync(() - userService.getUser(userId), bizPool); CompletableFutureOrderVO orderFuture CompletableFuture .supplyAsync(() - orderService.getOrder(orderId), bizPool); CompletableFutureStockVO stockFuture CompletableFuture .supplyAsync(() - stockService.getStock(skuId), bizPool); CompletableFuture.allOf(userFuture, orderFuture, stockFuture) .get(2, TimeUnit.SECONDS); UserVO user userFuture.get();这里有三个细节你一定要注意。第一所有并行任务都要加总超时allOf().get(2, TimeUnit.SECONDS)如果超时了要处理避免线程池里堆积未完成任务。第二一定要用独立的线程池不要用公共的ForkJoinPool否则其他接口的任务会互相干扰。第三有依赖关系的任务不能强行并行比如B需要A的返回值那就只能串行强行并行只会让代码复杂度暴涨、收益为零。2.3 技巧3批量查询替代循环调用说到性能杀手N1查询绝对排得上号。最常见写法是先查一个订单列表然后在循环里逐个查每个订单的买家信息列表有100条就查100次数据库。循环里的每次查询都是一次网络往返加一次SQL执行耗时自然是线性增长。我的习惯是凡是循环里查数据库全部改成批量接口。改造方式很简单先查出所有订单收集买家ID集合然后一次性WHERE id IN (...)查出所有买家再在内存里组装。MyBatis里我一般这样写select idselectBatchByIds resultTypeUser SELECT * FROM user WHERE id IN foreach collectionids itemid open( separator, close) #{id} /foreach /select这里有个经验值要分享IN子句的id数量我一般控制在500个以内超过这个量就拆分成多批再决定是顺序执行还是并行执行。原因很简单IN数量过大时SQL文本会很长数据库优化器可能放弃索引走全表扫描反而更慢。另外批量查询虽然减少了IO次数但如果数据量大还是会慢后续可以结合并行和缓存继续优化。2.4 技巧4幂等与防重设计给重复请求上一道闸接口幂等性看起来是正确性问题其实它也直接影响性能。想一想如果支付回调没有幂等同一个回调事件被MQ重投三次订单状态就被更新三次、短信发了三次、积分加了三次DB压力翻了数倍接口当然变慢。再比如前端按钮没有做防重复点击用户手抖点了两次两次请求一起进来同样的业务被重复执行。实现幂等我推荐组合拳Redis SETNX做前置过滤数据库唯一索引做最终兜底。// Redis 前置防重key 带业务语义 Boolean first redisTemplate.opsForValue() .setIfAbsent(order:create: orderNo, 1, Duration.ofMinutes(30)); if (Boolean.TRUE.equals(first)) { // 执行核心业务 } else { // 重复请求直接返回成功或提示处理中 }只要Redis的SETNX返回false说明这个请求已经处理过直接丢弃核心业务代码压根不用执行这就是防重对性能的贡献。但Redis的key有过期时间过期之后重复请求依然可能进来所以数据库唯一索引才是最终防线。真实项目里我都是两层全上Redis拦截99%的重复流量唯一索引兜底剩下的1%。3. 数据层优化绝大多数性能问题的根源3.1 技巧5SQL与索引优化做了这么多年Java后端我越来越确定一件事接口性能问题七成以上出在数据库。数据库慢接口就不可能快。而数据库慢的最大原因就是SQL没走对索引。遇到慢接口先打开数据库慢查询日志把long_query_time设为0.3秒找出Top SQL逐个执行EXPLAIN分析。重点看这几个字段type是不是从const降到了ALLrows是不是扫描了十万行key是不是干脆没命中索引。我见过一个典型例子用户表user_phone是varchar类型SQL里用where user_phone 13800001111传了个数字MySQL会把数字隐式转成字符串去比较导致索引失效全表扫描。把参数改成字符串类型后扫描行数从几十万降到一行接口从2秒变10ms。其他常见的索引失效场景我整理一下索引列上套函数或运算、LIKE前置百分号、OR条件跨字段、组合索引不满足最左前缀。优化手段不外乎改SQL、建索引、加覆盖索引。但我要提醒一句索引不是越多越好每多一个索引写入就多一次维护存储也多占一份空间关键是要建立起“按执行计划建索引”的思维而不是把所有列都建一遍。3.2 技巧6消除N1查询把查询次数压下来N1问题在ORM框架里尤其泛滥。用MyBatis的时候很多人图省事写嵌套查询查一个订单列表每条订单再查一次买家信息。表面上看代码很规整实际发出去的SQL是1加N条N是订单条数。100条订单就是101次数据库查询。我的标准做法是拆成两步第一步查出订单列表第二步收集所有买家的ID用WHERE id IN (...)一次查出所有买家然后在内存里按ID组装成Map循环订单列表时直接Map.get。查询次数从101降到2这一下就能把接口耗时打下来一半还多。还要提一个隐藏坑MyBatis的collection嵌套查询如果配置不当本质上就是N1。你要打开SQL日志确认一下看它到底是发了一条JOIN还是发了N条子查询。如果发现是N1优先改为两步查询在内存中组装或者用一条带JOIN的SQL直接查出来。3.3 技巧7深分页优化别让LIMIT成为性能瓶颈分页是几乎所有业务系统都绕不开的功能但分页写到后面性能问题会越来越明显。问题出在MySQL执行LIMIT 1000000, 20时它得先扫描前100万行然后丢掉只返回最后20行。页码越深扫描量越大接口自然越慢而且这个慢是数量级的慢。我常用的两种优化方案你按业务场景选。第一种是延迟关联先用子查询只查主键ID再回表取完整数据SELECT t.* FROM order t JOIN ( SELECT id FROM order WHERE status 1 ORDER BY id DESC LIMIT 1000000, 20 ) tmp ON t.id tmp.id;这种写法让MySQL内侧的子查询先做一次纯索引扫描速度会快不少。第二种是游标分页适合按ID或时间滚动的场景直接WHERE id lastId ORDER BY id LIMIT 20每次只往后翻20条不管翻到多深性能都稳定。游标分页的代价是不能跳页业务要求能直接跳到第50页的话只能用延迟关联。3.4 技巧8多级缓存性能优化的最大杠杆如果说数据层优化是治本那缓存就是见效最快的“特效药”。我做的每一个高并发读接口基本都遵循“Caffeine本地缓存L1 Redis分布式缓存L2 数据库兜底”的三级结构。// 一级缓存本地 Caffeine User user localCache.getIfPresent(userId); if (user ! null) { return user; } // 二级缓存Redis String json redisTemplate.opsForValue().get(user: userId); if (json ! null) { user JSON.parseObject(json, User.class); localCache.put(userId, user); return user; } // 三级兜底数据库 user userMapper.selectById(userId); redisTemplate.opsForValue().set(user: userId, JSON.toJSONString(user), Duration.ofMinutes(30)); localCache.put(userId, user); return user;为什么本地缓存和Redis都用因为访问Redis也有一次网络开销要1到2ms而Caffeine直接读JVM内存是微秒级。对热点数据来说本地缓存是真正的杀手锏。但本地缓存也有代价多实例部署时数据不一致而且占应用内存。所以本地缓存只放热点小数据Redis放全量缓存数据。用缓存最怕的是三个经典问题。穿透查一个不存在的ID缓存和数据库都没有每次请求都打DB解决方案是布隆过滤器或者缓存空值。击穿一个热点key刚好过期瞬间大量请求同时打DB解决方案是用互斥锁只让一个线程去重建缓存。雪崩大量key在同一时间过期数据库被打爆解决方案是过期时间加随机数把过期时间打散。4. JVM与并发优化把线程和内存用明白4.1 技巧9接口专属线程池与参数调优线程池很多人会用但用得粗。我强烈建议关键接口的异步任务、并行任务全部使用独立线程池不要和Tomcat的工作线程混在一起。原因很简单Tomcat的默认线程池是共享的如果某个接口里所有业务都在公共池里跑一旦这个接口并发量上来或者下游变慢整个应用的线程资源都会被占满其他接口跟着遭殃这叫线程池饥饿。独立线程池的参数怎么定我的经验是分场景。CPU密集型任务核心线程数设为CPU核数1IO密集型任务核心线程数可以设为CPU核数×2左右或者用公式N_threads N_cpu × (1 waitTime / computeTime)去估算。线程池的队列一定要有界配合一个合理的拒绝策略不然任务会无限堆积挤爆内存。ThreadPoolExecutor ioPool new ThreadPoolExecutor( 16, // 核心线程数 32, // 最大线程数 60L, TimeUnit.SECONDS, // 空闲回收时间 new ArrayBlockingQueue(2000), // 有界队列 new ThreadFactoryBuilder().setNameFormat(biz-io-pool-%d).build(), new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 );线程池跑起来之后一定要监控三个指标活跃线程数、队列长度、拒绝次数。活跃线程数长期等于最大线程数说明池子不够用拒绝次数不为零说明流量已经超过处理能力要么扩容要么削峰。拒绝策略的选择也要看业务CallerRunsPolicy会把任务退回到调用线程执行等于用调用方的线程去兜底适合不希望丢任务的场景如果任务本身可以丢弃就自定义一个丢弃策略并打日志。4.2 技巧10序列化方案选型被低估的CPU杀手序列化在Java接口里的比重经常被人低估。一个接口从接收请求到返回响应请求体和响应体都要序列化反序列化RPC调用要序列化Redis读写JSON要序列化MQ消息也要序列化。序列化方案选错了CPU会被消耗掉一大块。我对比一下几个主流方案的实际表现方案序列化体积速度跨语言适用场景JSON大中好HTTP对外接口、日志Protobuf小快好内部RPC、存储Kryo小快差Java服务间通信、缓存Hessian中中较差Java老项目我实际做过一个改造把一个频繁调用的内部查询接口从JSON换成Protobuf响应体从22KB变成6KB接口平均耗时下降了接近30%带宽消耗下降70%。Protobuf的优点是压缩率高、解析快而且自带版本兼容机制。但它有个硬伤要维护proto定义文件字段编号一旦上线就不能变否则老的消费者反序列化会出错。所以我的建议是对外HTTP接口保留JSON方便所有客户端接入内部RPC、高并发读接口如果流量很大值得上Protobuf。还有个更轻量的优化思路如果你暂时不想引入Protobuf那至少在JSON层面做两件事第一配置序列化框架忽略null字段第二检查是否频繁对大对象做toJSONString有时候这个动作会是CPU和GC的双重瓶颈。4.3 技巧11控制GC与对象分配稳住的不仅仅是内存GC对接口性能的影响最直观的表现是RT毛刺。一次Full GC的Stop-The-World停顿可能就是几百毫秒这段时间内所有请求都在排队。你去看监控图会发现接口的P99曲线偶尔冒出一个尖峰时间点和GC日志里的停顿完全吻合。我的经验是GC优化优先做“减分配”而不是“调参数”。减少对象分配的手段很具体循环内部不要创建大对象不要用字符串拼接改成StringBuilder不要对大对象做深拷贝用只读视图或者直接操作引用频繁使用的对象用ThreadLocal复用大数组、大Buffer用静态或池化管理。还有一个细节直接内存操作尽量用ByteBuffer.allocateDirect()避免在堆内和堆外之间来回拷贝这在涉及网络IO和文件IO时效果明显。有一个真实案例我印象很深。某接口每次请求里都会对两个很大的对象做深拷贝再加上循环里频繁JSON.toJSONStringGC频率高得吓人。把深拷贝去掉、改用不可变对象之后GC次数直接降了一半接口P99从300ms降到150ms。这个例子说明很多性能问题根本不在SQL也不在代码逻辑而在内存分配策略。代码跑得慢有时候是JVM在不停地“打扫卫生”而没时间干活。5. 异步化与容错让接口不被慢依赖拖死5.1 技巧12异步化改造把非核心逻辑挪出主流程不是所有逻辑都必须同步完成。积分累加、短信通知、报表统计、消息推送这类操作用户根本不关心它们什么时候完成它们只要最终执行到就行。把这些逻辑从接口主流程里挪出去改成异步执行接口的RT能立竿见影地降下来。最常用的异步化手段是消息队列。创建一个订单主流程只做落库然后把“订单创建成功”这个消息发给MQ由下游消费端去做积分、通知、推荐等操作。代码上看很简单orderService.createOrder(order); mqTemplate.send(order-create-topic, orderId); return Result.success();但有三个问题必须想清楚。第一异步化牺牲了强一致性业务要能接受“最终一致”。如果用户下单后立刻查询积分可能查不到刚加的积分这个场景就不适合异步。第二MQ消息可能丢也可能重复必须有重试机制和幂等消费。第三事务边界很容易被忽略主流程的事务提交了异步任务才应该执行如果事务还没提交就把消息发出去消费者查数据可能查不到。稳妥的做法是使用本地消息表配合定时任务或者用支持事务消息的MQ。还有一种轻量级异步适合不想引入MQ的场景直接用业务线程池把非核心任务提交出去。但注意异步任务里的异常一定要捕获处理不然会直接消失连日志都没有。5.2 技巧13超时与熔断降级慢依赖不能拖垮整个服务一个服务变慢往往不是自己出了问题而是下游依赖变慢了。更可怕的是慢依赖会传染某个下游接口从50ms变成3秒你的接口就跟着等3秒期间所有请求都占着线程不放线程池很快被占满然后你的服务也开始“变慢”最后上游网关也开始超时重试整条链路雪崩。所以给所有下游调用设置超时是接口性能优化的底线。HTTP客户端和RPC框架都要配连接超时和读超时连接超时一般是500ms到1秒读超时按业务容忍度设1秒到3秒千万不要用默认的“永不超时”。超时之后还要做兜底比如返回降级数据而不是把异常抛给上层。熔断是超时的升级版。我常用Sentinel或者Resilience4j参数一般这样配在10秒的滑动窗口内如果请求失败率达到50%就触发熔断熔断持续10秒10秒后进入半开状态放少量请求试探如果成功就关闭熔断恢复流量。这套机制的核心思想是下游已经病了你就别再疯狂往里打流量了让它缓一缓。我踩过一次很深的坑优惠券服务因为上线bug变得极慢我们的订单详情接口要调它当时没有超时和熔断结果一个1%的慢请求把所有Tomcat线程全部占住整个订单系统的P99从60ms飙到1秒。最后加上了超时熔断并且给优惠券模块做了降级兜底接口P99才恢复正常。从那以后凡是涉及外部调用的接口超时熔断成了我的标配。5.3 技巧14连接与压缩优化把网络开销降下来网络层面的开销常常被忽略但它在接口耗时里占的比例不低。最明显的是HTTP连接建立每发起一次HTTPS请求都要经历DNS解析、TCP握手、TLS握手光握手就要好几个RTT。如果每次请求都新建连接性能损耗非常可观。解法就是连接池复用连接。Java这边用Apache HttpClient或OkHttp都有连接池配置我一般这样设参数推荐值说明connectTimeout500ms建连超时socketTimeout2000ms读超时按业务调整maxConnTotal600总连接数上限maxConnPerRoute100单路由连接数上限connectionRequestTimeout200ms从连接池获取连接的超时连接池也不是越大越好连接数太多反而会增加服务器负担和内存开销压测出一个合适的值就好。另一个网络优化手段是HTTP/2它支持多路复用同一连接上可以并发多个请求彻底解决HTTP/1.1的队头阻塞问题。如果你的下游服务支持HTTP/2尽量升级。再就是响应体压缩Gzip压缩JSON文本一般能压掉60%到70%。但要注意压缩和解压都要耗CPU小于1KB的响应压缩收益很小不建议开。我是先压测对比确定收益大于开销才开Gzip。6. 压测与监控优化效果要看得见6.1 技巧15压测与监控先行用数据证明优化有效没有压测的优化都不能算数。我见过太多人改完代码直接上线自我感觉良好结果线上P99不降反升。原因很简单你优化的是A点但真正的问题是B点不压测你根本不知道A点有没有优化到位。我的标准流程是优化前先做一次基准压测记下RT的P99、QPS、错误率优化后再做一次相同条件的压测对比两次的数据用数据说话。压测工具我用得最多的是JMeter和wrkwrk适合简单的HTTP接口压测一条命令就能跑wrk -t4 -c200 -d60s --latency http://localhost:8080/api/user/detail-t4是4个线程-c200是200个并发连接-d60s是持续60秒--latency会输出延迟分布。跑完会直接给出QPS和P99非常直观。复杂场景比如带登录态的多步操作就用JMeter跑脚本。压测有个容易犯的错误拿生产环境的监控数据当基准。生产环境的流量不均匀有低峰有高峰不适合对比。压测最好在独立的压测环境里做配置和数据量要和线上对齐至少要压到目标QPS的1.5倍才算有点参考价值。压测机本身也要注意别把压测机的CPU压满了出来的数据就不准了。6.2 线上问题排查实录四个真实案例先讲一个我实际排查过的RT毛刺问题。监控图上P99稳定但每隔一段时间会出现一个尖峰持续一两百毫秒。我第一反应是GC影响直接看GC日志发现这段时间正好有Full GC发生停顿时间在300ms左右。再往下查发现某个统计接口每10分钟会加载一次全量数据到内存做计算大对象分配直接触发Full GC。把统计逻辑改成异步执行后毛刺消失P99曲线平滑下来。这个案例给我的启发是RT毛刺问题优先排查GC再排查定时任务和大对象分配。第二个案例是Tomcat线程池耗尽。现象是接口超时率上升日志里大量出现等待线程超时。我用jstack抓线程转储发现大量线程卡在下游HTTP调用上进一步看那个下游服务因为发布问题变慢了而我们的调用没有超时设置所有线程都在死等。最后加上了读超时和熔断降级问题彻底解决。第三个案例是数据库连接池耗尽。Druid监控面板显示池子里活跃连接数一直顶到上限但CPU并不高。排查下来是一条报表查询SQL没用上索引全表扫描跑了快4秒把连接都占住了。用EXPLAIN确认后加了联合索引查询时间降到50ms连接池恢复健康。这个案例说明连接池耗尽很多时候不是配置问题是慢SQL问题。第四个案例是缓存穿透。某个商品查询接口在搞活动期间缓存命中率骤降数据库负载飙升。原因是活动页面上线了一批不存在的商品ID这些ID在缓存和数据库里都没有每次请求都直接打DB。后来加了布隆过滤器把不存在的ID在入口处直接拦截数据库压力降了80%。6.3 常见问题排查速查表一看一个准这几个案例我整理成了一张排查速查表遇到同类问题可以直接对着查症状可能原因定位手段处理方向RT偶发尖峰毛刺Full GC、定时任务、大对象分配查看GC日志、jstat、Arthas减少对象分配、异步化、调整GC参数Tomcat线程池爆满下游慢无超时、业务死等jstack线程转储、监控线程池指标设置超时、熔断降级、独立业务线程池CPU持续高位序列化频繁、正则、循环创建对象async-profiler火焰图替换序列化方案、减少对象分配数据库连接池耗尽慢SQL、连接泄漏Druid监控、EXPLAIN索引优化、SQL改写、治理连接泄漏接口超时不断下游依赖变慢、重试风暴链路追踪、耗时明细超时熔断、降级兜底、限流缓存命中率低key设计差、过期时间短缓存监控面板调整TTL、添加随机数、热点key识别这张表我在团队里贴了很长时间每次线上接口报警大家先对着表排查一轮大多数问题都能快速定位。排查性能问题的方法论其实就一句话先看监控再抓现场最后改代码。顺序反了效率会差很多。最后再分享一个我个人的习惯。跟我合作过的人都知道我改性能问题有一个铁律每次只改一个变量。这次只加缓存跑一轮压测下次只换序列化再跑一轮压测。几个优化点混在一起改出了问题你根本不知道是哪个改动导致的回滚也不知道回滚什么。15个技巧里如果让我只留三个我会留下监控压测、超时熔断、多级缓存——这三样是防守和基础其他技巧都是放大器。接口性能优化不是一次性的工作它是个持续的过程你的接口每慢一毫秒用户流失的概率就多一分希望这些技巧能帮你少走点弯路。
返回列表