ARTICLE DETAIL

资讯详情

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

后端接口性能优化六技巧:缓存、批量、异步、索引、内存、日志

后端接口性能优化六技巧:缓存、批量、异步、索引、内存、日志 最近网上“6的嘞”这个词特别火代码写得清爽、SQL 连索引都不走偏、接口响应时间从秒级降到毫秒级身边的同事就会来一句“6的嘞”。但后端的“6”不是喊出来的而是一步一步优化出来的。这篇文章从一个常见的订单查询接口入手梳理一套可以直接落地的性能优化方法包含六个实战技巧缓存、批量处理、异步并发、索引优化、内存治理、日志异步化。无论你是刚工作不久的后端新人还是已经在维护高并发服务的开发同学都可以拿这套思路去对照自己的项目看看瓶颈到底出在哪一层。1. 背景从“慢接口”到“6的嘞”1.1 慢接口的常见表现一个接口变慢通常不是单一原因造成的。最直观的表现是响应时间很长用户侧看到转圈监控面板上的 TP99 持续上升。从应用层排查可能是循环查数据库、重复查缓存、远程服务串行等待从数据层排查可能是 SQL 没走索引、扫描行数过大、排序走了文件排序从基础设施层排查可能是 CPU 飙高、GC 频繁、网络带宽打满。很多团队一开始会习惯性加机器但如果代码里有明显的 N1 查询加机器也只是把慢放大到更多节点上。典型的业务场景是订单列表页。前端要展示订单号、用户昵称、商品名、商品图片、物流状态等等。这些数据分散在订单表、用户表、商品表、物流服务中。如果处理方式是在循环里逐条查询用户和商品那么每页 20 条订单就可能产生 40 次甚至更多次数据库查询再加上一次远程物流调用接口很难快起来。“6的嘞”不是说这种代码六而是说这种代码糟糕得让人无语。1.2 六个优化方向性能优化要先分方向再动手。后端接口的性能模型可以拆成计算、IO、内存、并发四个维度。计算层面要考虑算法复杂度IO 层面要减少数据库查询和远程调用次数内存层面要控制对象创建和 GC 压力并发层面要把串行等待变成并行处理。本文的六个实战技巧分别对应这几类问题缓存先行解决重复 IO批量处理解决 N1 查询异步并发解决串行等待索引优化解决 SQL 扫描过大内存治理解决对象分配和 GC 压力日志异步化解决同步日志对业务线程的阻塞。在开始改代码之前还有一件更重要的事先量化慢在哪里。最理想的方式是接入链路追踪把一次请求拆成数据库耗时、Redis 耗时、远程调用耗时、业务计算耗时。没有监控数据就凭感觉优化很可能把时间花在不重要的节点上。比如一个接口 90% 时间都在等待远程服务返回那么优化本地 SQL 索引只能带来微弱的提升。先测量、再定位、最后优化是这篇文章贯穿始终的原则。2. 环境准备与版本说明2.1 技术栈与版本选择为了把六个技巧讲清楚后面所有代码示例都围绕一个 Spring Boot 项目展开。示例技术栈如下组件版本建议JDK8 或 11Spring Boot2.xMyBatis-Plus3.5.xMySQL5.7 / 8.xRedis5.xMaven3.6版本需要根据你的项目实际情况调整。如果当前项目已经是 Spring Boot 3.x那么很多javax.*包会变成jakarta.*Redis 连接方式和部分配置类路径也会变化。本文重点演示配置思路和优化思路不保证所有代码在你的版本中一字不差地运行。遇到版本差异时优先查看官方迁移文档。示例工程的依赖以 Maven 管理核心依赖如下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency注意MyBatis-Plus 的版本要与 Spring Boot 版本兼容。如果你用的是 Spring Boot 2.x可以使用mybatis-plus-boot-starter如果是 Spring Boot 3.x则需要使用mybatis-plus-spring-boot3-starter。2.2 示例工程结构后面出现的关键代码可以按照下面的结构放入工程中src/main/java/com/example/order/ ├── controller/OrderController.java ├── service/OrderService.java ├── service/UserCacheService.java ├── mapper/OrderMapper.java ├── entity/OrderDO.java ├── entity/UserInfo.java ├── entity/ProductInfo.java └── config/AsyncConfig.java代码示例中会出现OrderDO、UserInfo、ProductInfo这些简单实体类为了节省篇幅不再贴重复的 getter/setter。如果你复制到本地需要补全字段定义。文章里的核心代码都会标明放哪个文件里方便对照。3. 六个实战优化技巧3.1 技巧一缓存先行先看一个最常见的场景订单列表接口每次请求都要查询用户昵称、头像、商品名称、商品图片。这些数据的特点是读多写少短期内基本不变。如果每次都从数据库或者远程接口取不仅响应慢还会给数据库和下游服务增加压力。这时候缓存先行是最直接的优化手段。缓存不是简单加一层 Redis 就结束还要考虑 key 的设计、过期时间、穿透、击穿和雪崩。先说基础用法。下面是UserCacheService的简化实现用于从缓存中读取用户信息缓存不存在时回源数据库Service public class UserCacheService { private static final String USER_KEY_PREFIX user:info:; private final StringRedisTemplate stringRedisTemplate; private final ObjectMapper objectMapper; public UserCacheService(StringRedisTemplate stringRedisTemplate, ObjectMapper objectMapper) { this.stringRedisTemplate stringRedisTemplate; this.objectMapper objectMapper; } public UserInfo getUserInfo(Long userId) { String key USER_KEY_PREFIX userId; try { String json stringRedisTemplate.opsForValue().get(key); if (json ! null) { return objectMapper.readValue(json, UserInfo.class); } UserInfo user loadFromDb(userId); if (user ! null) { stringRedisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(user), 30, TimeUnit.MINUTES); } return user; } catch (JsonProcessingException e) { throw new IllegalStateException(用户信息序列化失败, e); } } private UserInfo loadFromDb(Long userId) { // 这里实际是调用 userMapper.selectById(userId) return userMapper.selectById(userId); } }这个实现有几个地方需要注意。key 使用user:info:前缀避免与业务内其他缓存冲突。过期时间设置为 30 分钟适合用户昵称、头像这类低实时性数据。如果数据更新频繁可以在更新数据库后主动删除缓存再由下一次请求回源重建做到最终一致。如果缓存查询返回空字符串说明是空值缓存可以单独判断防止每次查询都打到数据库。缓存穿透是指查询一个不存在的 key缓存和数据库都没有数据请求每次都穿透到数据库。解决思路是缓存空值或使用布隆过滤器。缓存击穿是指某个热点 key 过期瞬间大量并发请求同时回源。解决思路是加互斥锁或者用“逻辑过期”模式。缓存雪崩是指大量 key 同一时间过期导致数据库压力激增。解决思路是给过期时间加随机扰动。对于订单查询这类场景至少要处理好缓存空值和过期时间随机化避免突发流量打挂数据库。3.2 技巧二批量处理干掉 N1 查询数据库交互次数是影响接口性能的核心因素。很多后端代码在初学时都会写成循环查库ListOrderVO orderVOList new ArrayList(); for (OrderDO order : orderList) { UserInfo user userService.getById(order.getUserId()); ProductInfo product productService.getById(order.getProductId()); orderVOList.add(buildOrderVO(order, user, product)); }这段代码的问题在于N 条订单会触发 2N 次用户和商品的单条查询。订单列表一页 20 条就意味着 40 次数据库查询。数据量大时数据库的连接资源会被迅速耗尽接口耗时呈线性增长。这种问题在代码评审中非常常见也是最值得优先修复的。优化思路是先取出全部订单再收集所有需要的 userId 和 productId最后用批量查询一次性取回数据在内存中组装。改造后的核心逻辑如下ListLong userIds orderList.stream() .map(OrderDO::getUserId) .distinct() .collect(Collectors.toList()); ListLong productIds orderList.stream() .map(OrderDO::getProductId) .distinct() .collect(Collectors.toList()); MapLong, UserInfo userMap userService.listByIds(userIds).stream() .collect(Collectors.toMap(UserInfo::getId, Function.identity())); MapLong, ProductInfo productMap productService.listByIds(productIds).stream() .collect(Collectors.toMap(ProductInfo::getId, Function.identity())); ListOrderVO orderVOList orderList.stream() .map(order - buildOrderVO(order, userMap.get(order.getUserId()), productMap.get(order.getProductId()))) .collect(Collectors.toList());这里的listByIds是 MyBatis-PlusIService提供的批量查询方法底层会生成SELECT ... WHERE id IN (...)。优化后数据库查询次数从 2N 次降到了 2 次性能提升非常明显。要注意的是IN 查询的集合大小需要控制。MySQL 对 IN 列表的长度没有硬性限制但过长的 IN 列表会导致 SQL 解析变慢也会让索引选择变得不可控。一般建议单批不超过 500 到 1000 个 id如果超过就拆成多批查询最后合并 Map。批量处理不仅能用在数据库查询上也适用于 Redis 的 pipeline、远程服务的批量接口。在设计接口时如果发现业务逻辑里有“循环查下游”的模式都可以先思考能不能改成“先收集、再批量、后组装”的方式。这是从根上减少 IO 次数最有效的手段之一。3.3 技巧三异步化与并发编排订单列表接口除了查询数据库还可能调用物流信息、营销标签、库存状态等远程服务。如果这些服务是串行调用的那么一个接口的总耗时就是所有下游服务耗时的总和。假设每个远程服务平均耗时 50ms三个服务串行就需要 150ms加上数据库查询接口很容易超过 500ms。更合理的做法是让这些互不依赖的调用并行执行。JDK 8 的CompletableFuture是处理异步编排的常用工具。先定义一个业务线程池避免直接使用公共的ForkJoinPoolConfiguration public class AsyncConfig { Bean(bizExecutor) public ThreadPoolTaskExecutor bizExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(8); executor.setMaxPoolSize(16); executor.setQueueCapacity(200); executor.setThreadNamePrefix(biz-async-); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } }接着在业务代码中用CompletableFuture.supplyAsync并行获取用户、商品、物流信息CompletableFutureUserInfo userFuture CompletableFuture.supplyAsync( () - userCacheService.getUserInfo(order.getUserId()), bizExecutor); CompletableFutureProductInfo productFuture CompletableFuture.supplyAsync( () - productCacheService.getProductInfo(order.getProductId()), bizExecutor); CompletableFutureLogisticsInfo logisticsFuture CompletableFuture.supplyAsync( () - logisticsClient.queryByOrderId(order.getId()), bizExecutor); UserInfo user userFuture.get(2, TimeUnit.SECONDS); ProductInfo product productFuture.get(2, TimeUnit.SECONDS); LogisticsInfo logistics logisticsFuture.get(2, TimeUnit.SECONDS);使用get(timeout, unit)是必须的因为异步任务不能无限期等待。如果某个下游服务恰好超时这里会抛出TimeoutException。实际项目中要单独捕获异常给用户一个降级后的默认值不要让订单列表页整体失败。比如物流信息取不到时可以返回“暂无物流信息”而不是把整个接口拖垮。异步优化还要注意线程池参数。核心线程数、最大线程数、队列容量、拒绝策略需要根据下游服务的平均耗时和机器资源计算。不要盲目调大线程数过大的线程数反而会增加上下文切换成本。拒绝策略建议使用CallerRunsPolicy当线程池队列满时让提交任务的线程自己执行起到天然限流的作用同时避免任务被静默丢弃。如果使用Async注解还需要注意方法自调用不会触发异步代理因此更推荐直接用线程池包装业务逻辑或者把异步方法放到另一个 Bean 中。3.4 技巧四索引设计与慢 SQL 治理前端做了缓存、业务层减少了查询次数数据层仍然可能是瓶颈。以订单查询为例如果表结构简单SQL 是SELECT ... FROM order WHERE user_id ? ORDER BY create_time DESC LIMIT 20在没有索引的情况下MySQL 会走全表扫描还要把结果集排序后才能取出前 20 条。当订单表数据达到千万级别时这条 SQL 的延迟会变得无法接受。给订单表添加一个联合索引ALTER TABLE order ADD INDEX idx_user_create_time (user_id, create_time);添加之后查询条件user_id ?可以快速定位用户订单create_time作为联合索引第二列也正好满足排序需求避免filesort。我们可以在执行前使用EXPLAIN验证EXPLAIN SELECT id, user_id, product_id, create_time FROM order WHERE user_id 1001 ORDER BY create_time DESC LIMIT 20;执行计划里应该能看到keyidx_user_create_timetype至少是ref而不是ALL。rows会明显减少Extra中不再出现Using filesort说明索引确实生效了。索引设计有几个容易踩的坑。第一最左前缀原则联合索引(user_id, create_time)只有在条件包含user_id时才能充分利用如果只查询create_time索引可能失效。第二不要在索引列上做函数运算比如WHERE DATE(create_time) 2025-01-01会让索引失效应该写成范围条件create_time ? AND create_time ?。第三注意隐式类型转换例如user_id是 bigint却传入了字符串MySQL 可能无法高效使用索引。索引不是越多越好。每个索引都会占用磁盘空间还会拖慢写入速度。实际项目中建议通过慢查询日志找到最耗时的 SQL再针对这些 SQL 设计索引。上线索引时也要避开业务高峰期因为 MySQL 8 之前的部分 DDL 操作会锁表。如果表数据量很大可以用在线 DDL 工具或者在维护窗口执行。3.5 技巧五内存与 GC 优化接口慢不一定都是 IO 问题也可能是因为内存分配压力大、GC 频繁。比如一次性从数据库查出几十万条数据到内存再逐条处理会直接导致年轻代迅速被占满Minor GC 频繁发生。更严重的是如果业务代码中存在无意持有的对象引用还会引发内存泄漏最终导致 Full GC 甚至 OOM。内存优化的第一步是控制单次加载的数据量。列表接口必须分页PageOrderDO page new Page(current, 20); LambdaQueryWrapperOrderDO wrapper new LambdaQueryWrapper(); wrapper.eq(OrderDO::getUserId, userId) .orderByDesc(OrderDO::getCreateTime); IPageOrderDO orderPage orderMapper.selectPage(page, wrapper); ListOrderDO orderList orderPage.getRecords();分页可以结合索引优化让数据库只需要返回当前页数据而不是把所有候选行都加载到应用层。第二步是只查询需要的字段。很多实体类字段很多比如商品详情包含富文本内容、用户信息包含备注字段这些字段在列表页根本用不到。可以使用 MyBatis-Plus 的select方法指定查询列LambdaQueryWrapperOrderDO wrapper new LambdaQueryWrapper(); wrapper.select(OrderDO::getId, OrderDO::getUserId, OrderDO::getProductId, OrderDO::getCreateTime);减少字段读取有两个好处一是数据库回表的数据量变小二是应用层创建的对象变小。对象越小每次 Young GC 能清理的对象越多GC 压力自然下降。代码层面还要注意循环体内不要创建大对象能提到循环外创建的集合尽量提出来。使用基本类型代替包装类型可以减少拆箱和装箱但要看业务场景不要为了优化牺牲可读性。另一个容易踩坑的是ThreadLocal。线程池中的线程是复用的如果在线程里往ThreadLocal中写入了数据使用完没有清理下一次复用同一条线程时可能会读到脏数据更重要的是 ThreadLocal 中的对象会一直被子线程持有造成内存泄漏。正确做法是在 finally 块中执行remove()确保每个请求结束后及时释放。现在的 JVM 其实已经非常智能不要做无意义的微优化。真正的内存优化核心是少加载、少创建、及时释放。比如 Excel 导出、报表计算这类批量任务可以分批读取、分批写入而不是一次性把全量数据塞进内存。3.6 技巧六日志异步化与可观测性日志系统看起来不影响业务逻辑但在高并发场景下同步日志会产生巨大的性能损耗。每打印一条日志都会涉及格式化、磁盘 IO、行锁等待。当 QPS 比较高时日志写入会成为不可忽视的瓶颈甚至阻塞业务线程。解决方案是把日志写入改为异步模式使用 Logback 的AsyncAppender。以logback-spring.xml为例关键配置如下configuration appender nameFILE classch.qos.logback.core.rolling.RollingFileAppender filelogs/app.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePatternlogs/app.%d{yyyy-MM-dd}.log/fileNamePattern maxHistory30/maxHistory /rollingPolicy encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] [%X{traceId}] %logger{36} - %msg%n/pattern /encoder /appender appender nameASYNC_FILE classch.qos.logback.classic.AsyncAppender queueSize2048/queueSize discardingThreshold0/discardingThreshold neverBlocktrue/neverBlock appender-ref refFILE/ /appender root levelINFO appender-ref refASYNC_FILE/ /root /configuration配置里queueSize是异步队列长度neverBlocktrue表示当队列满了之后新的日志事件不会阻塞业务线程而是直接丢弃。这在保护业务可用性上有帮助但代价是可能丢日志。对日志完整性要求高的系统不建议设置neverBlocktrue而是调整队列大小或者把丢弃策略做成可监控的。另外日志格式里使用了%X{traceId}这是从 MDC 中读取链路编号。如果我们没有在代码中向 MDC 写入 traceId这个格式化串会输出空值。为了在异步线程中打印 traceId尽量在线程池提交任务时把主线程的 MDC 上下文传递到子线程。一个简单思路是定义一个包装 Runnable在任务执行前设置 MDC执行后清理。如果项目中已经在用 SkyWalking、Zipkin 等链路追踪组件可以使用它们提供的线程包装器避免自己重复实现。异步日志只是可观测性的基础更完整的做法是记录每个接口的耗时、下游调用状态、数据库慢查询日志把这些信息汇总到监控大盘才能及时发现性能问题。4. 联合优化后的压测验证4.1 优化前后的效果差异六个技巧讲完之后还需要回答一个问题这样优化后接口到底能快多少性能优化的效果不能靠脑补必须用压测数据说话。建议用 JMeter 或 wrk 在相同条件下分别对优化前的接口和优化后的接口压测至少压测 10 分钟以上观察平均 RT、TP99、TPS、错误率。按照经验如果一个接口原本存在 N1 查询并且缺少索引把批量查询和索引优化落地后数据库耗时往往能下降一个数量级。如果远程调用占大头再加上异步化RT 会有明显改善。但具体数字取决于表数据量、机器配置、下游服务耗时以及并发度。不要直接参考别人的压测报告也不要相信任何“一劳永逸”的优化方案。关键是每次只改一个变量压测后对比这样才能知道哪一个技巧真正起作用。4.2 如何复现这套优化复现这套优化可以按以下步骤操作先搭建 Spring Boot 工程导入示例依赖准备订单表、用户表、商品表插入足够多测试数据。编写一个最原始的订单列表接口包含循环查询用户和商品、串行调用物流服务、同步打印日志并压测记录基线数据。依次应用缓存、批量处理、异步化、索引、分页查询和异步日志每应用一步压测一次。用 EXPLAIN 分析 SQL 执行计划用 Arthas 或 JProfiler 观察 GC 和线程池状态。最后对比优化前后的报告确认改进项没有引入新的问题。压测环境最好和生产环境保持相近的配置但不要直接在线上压测。如果没有测试环境可以考虑把压测流量打到预发环境并控制在较小范围内。优化上线时还要制定回滚方案因为数据库索引变更和缓存策略改动具备一定不可控性。5. 常见问题与排查思路5.1 典型问题汇总后端性能优化过程中几乎每个技巧都有一个对应的“坑”。下面是常见问题的汇总问题现象常见原因解决思路加了缓存后数据和数据库不一致缓存更新策略不对先更新数据库再删除缓存必要时延迟双删大批量 IN 查询执行很慢集合过大导致 SQL 过长分批查询每批 500 个以内再合并结果异步任务没有返回结果线程池队列满了或任务被丢弃使用有界队列记录拒绝策略合理设置参数EXPLAIN 显示 index 但查询仍慢查询产生了随机 IO 或回表过多使用覆盖索引减少SELECT *索引明明加了却不生效查询条件发生了隐式类型转换检查字段类型和传入参数类型异步日志出现丢失队列满且 neverBlocktrue调大队列关闭 neverBlock或记录丢弃计数GC 频繁且 CPU 高单次查询加载数据量过大分页查询指定查询字段避免大对象堆积ThreadLocal 数据串到下一次请求线程复用没有清理finally 中调用 remove这些问题的共同特点是不会直接导致接口报错但会让性能在不知不觉中劣化。排查时不能只看接口的返回值还要关注耗时分布、数据库慢查询、GC 曲线和线程池状态。5.2 排查清单如果你接手一个慢接口不知道从哪里入手可以按照下面的清单顺序排查先看接口整体 RT 和 TP99确定是平均慢还是少数请求慢。通过链路追踪查看耗时占比是数据库慢、Redis 慢还是远程调用慢。开启 MySQL 慢查询日志找到对应 SQL用 EXPLAIN 看执行计划。看应用日志中是否有大量异常重试和超时日志。查看 GC 日志判断是否频繁 GC。查看线程池活跃线程数判断是否存在线程阻塞。查看 Redis 慢日志和命中率判断缓存是否真正生效。最后再看代码逻辑重点检查循环中的数据库调用、远程调用和对象创建。按照这个顺序排查可以避免一开始就在无关紧要的参数上浪费时间。6. 最佳实践与工程建议6.1 上线前必须确认的事性能优化改动上线前除了常规代码评审还要特别关注几个点。第一缓存上线要考虑缓存穿透和雪崩提前设置好空值缓存、过期时间随机化以及监控报警。第二批量查询不能无限放大 IN 列表如果 SQL 太长要拆分批次。第三异步线程池的拒绝策略必须明确不能默认丢弃任务。第四数据库索引变更要评估锁表时间最好使用在线 DDL 工具或选择业务低峰期执行。第五日志异步化后要确认日志不丢失是最低要求还是可接受丢弃如果是监管要求严格的业务不要开启neverBlocktrue。这些建议背后都指向同一个原则性能优化不能以牺牲数据一致性和可观测性为代价。系统跑得快很重要但跑得稳更重要。优化上线后应至少观察一周重点关注接口耗时、错误率、数据库连接数、Redis 内存增长和 GC 频率。出现异常波动时第一时间回滚到上一个稳定版本不要在现场反复调参。6.2 长期可维护性性能优化不是一次性的活动而是长期的工程习惯。在代码层面应该通过 Code Review 拦截明显的 N1 查询制定团队内部的接口开发规范。比如查询列表接口必须分页循环内禁止查询数据库远程调用必须设置超时时间缓存 key 必须有统一前缀。在监控层面可以建立接口基线当某个接口的 TP99 连续超过阈值时自动告警而不是等用户反馈才发现问题。在项目演进过程中数据量会增长、依赖服务会变化今天有效的优化方案明年可能就不适用了。因此建议关键性能指标尽量沉淀为自动化测试。比如压测脚本可以放在 CI/CD 流水线中每次发布前跑一轮烟雾压测如果新版本比上一版本慢超过 20%就自动拦截发布。这样既能让性能优化可持续也能避免团队因为某个指标波动来回扯皮。7. 总结与学习路线7.1 本文掌握了什么通过这篇文章我们一起走完了一个订单查询接口的完整优化过程。核心知识点包括用缓存降低重复 IO用批量查询干掉 N1用异步并发减少串行等待用联合索引减少 SQL 扫描用分页和对象复用降低 GC 压力用异步日志解除高并发下的日志阻塞。这些技巧不是互相独立的实际项目里往往要组合使用。优化的顺序也值得注意一般建议先通过监控定位瓶颈再针对瓶颈做改造而不是一次性堆上所有技术。7.2 下一步学什么如果这篇文章里的内容你都已经掌握下一步可以继续深入更底层的性能工具和方法。数据库方向可以学习 MySQL 执行计划、覆盖索引、索引条件下推、分库分表Java 应用方向可以学习 JVM 内存模型、Arthas 在线诊断、JMH 基准测试分布式方向可以学习 Redis 集群、消息队列削峰填谷、分布式链路追踪。性能优化是一个需要长期积累的能力最好的方式是拿一个自己的项目做实验记录优化前的数据和优化后的数据慢慢形成稳定的判断力。看再多文章都不如亲手压测一次来得直观。如果这篇文章对你有帮助可以收藏备用。也欢迎在评论区分享一下你在优化接口时遇到过最“坑”的问题看看有没有让你也想说一句“6的嘞”的解法。
返回列表