接口由40秒到200ms优化记录

  场景还原

  一个业务逻辑较为复杂的业务,涉及到n次遍历,其中有循环查询/更新数据库,事务的管理,加上一些业务逻辑的计算.最初的接口,纯粹按照产品提供的相关业务逻辑,单纯的编码,耗时较长,近40秒的处理时间

  解决思路

  碰到这种涉及到数据库相关操作的接口,首先想到的是降低与数据库间的交互,优先考虑将条件类的数据映射关系放在缓存中;其次,理解业务,处理数据写操作之间的对应关系,尽量将循环写操作调整为单条写操作;最后,理清整体业务逻辑,将数据库交互时的逻辑在Java代码中处理,再根据最终结果,统一入库(ps:本次业务场景下可用,具体情况再另说)

  案例引入

  业务:

  引入大家熟悉的打卡考勤做案例来分析,考勤组人员的增删,这里涉及到几层关系:

  1.考勤组--对应员工总数

  2.员工--考勤组

  3.员工--旧考勤组,员工--新考勤组

  简单理解:n个员工,一个新的考勤组id. --> n个员工对应的考勤组变更为新的考勤组, 可能n个旧考勤组对应的员工总数减m个, 新的考勤组对应的员工总数加n个. 

    这里的难点在于 n个员工可能对应 f (f <= n) 个旧考勤组, f个旧考勤组具体根据对应的需变更员工数调整所属总员工数.

  初步设计

  1.遍历所有要修改的员工

  2.得到具体的一个员工,获取其旧考勤组信息

  3.每次循环,对应的旧考勤组所属员工总数减一,更新员工信息(设置所属考勤组为新考勤组)

  4.新考勤组所属员工总数加n

  计算功耗, 1次Java循环,n次查询,n * 2次更新

  优化过程

  1.将员工--考勤组的映射关系存入缓存,并在Java遍历中更新value,留后续操作使用

  2.将考勤组--所属员工总数的映射关系存入缓存,并在Java遍历中++/--value

  3.员工的批量update,使用mybatis的foreach配合多参数的用法

  最终,1次Java循环,f+1次更新

  速度立即降到200ms的标准接口数据返回时间.

  局部代码:  

    // mybatis多参数配合遍历的用法  --> Mapper接口
    Integer updateGroupIdForPersons(@Param("ids")List<Integer> ids,@Param("groupId")Integer groupId);
    // Mapper.xml
    <update id="updateGroupIdForPersons">
        UPDATE person SET group_id = #{groupId}
        WHERE person_id IN
        <foreach collection="ids" item="personId" open="(" separator="," close=")">
          #{personId}
        </foreach>
    </update>
    // 缓存采用的是 ConcurrentHashMap 不做赘述 配合SpringBoot自带的定时作业实现
    /**
     * 启动一秒开启,之后每十分钟一次
   * 注意,此处已将缓存逻辑整合到具体代码,所以可设为十分钟一次查库更新
*/ @Scheduled(initialDelay=1000, fixedRate=600000) public void cachePersonsGroupsUpdateMap(){ List<Person> people = personMapper.queryAllPersons(); for (Person person : people) { if (person.getGroupId() != null) { PersonsGroupsUpdateMap.put(person.getPersonId(),person.getGroupId()); } } }

转载于:https://www.cnblogs.com/nyatom/p/9366162.html

<think>首先,用户的问题是关于接口性能优化的:接口平时响应时间是30ms,但在高并发场景下时延上升到200ms甚至500ms,需要分析原因并提供优化方案。用户提供了几个引用,这些引用是关于性能优化的案例,我应该参考它们来支持我的分析。 回顾引用: - 引用[1]:关于Web服务器性能优化优化后响应时间从500ms降到100ms,吞吐量提升,超时率降低。 - 引用[2]:关于JVM GC优化,通过调整Young区大小,减少了Full GC和Young GC,降低了延时。 - 引用[3]:关于数据库查询优化,通过In查询、索引和业务逻辑重构,将接口响应时间从2s优化到100ms。 这些引用展示了性能优化的常见方法,如GC调优、数据库优化等。我需要将这些应用到用户的问题中。 用户的问题是:接口在高并发下时延升高。分析原因并提供优化方案。 关键点: - 平时响应时间:30ms(低负载时) - 高并发时:200ms-500ms - 需要分析原因和优化方案 可能的原因: 1. **资源竞争**:在高并发下,CPU、内存、I/O资源成为瓶颈,导致处理延迟。 2. **数据库瓶颈**:如果接口涉及数据库操作,高并发时数据库查询变慢(如锁竞争、索引缺失)。 3. **线程阻塞**:线程池配置不当,导致任务排队等待。 4. **垃圾回收(GC)**:在Java应用中,GC停顿可能导致延时增加(如引用[2]所示)。 5. **外部依赖**:调用其他服务或API,在高并发时响应变慢。 6. **代码效率**:算法或数据处理逻辑在高负载下效率低下。 7. **网络延迟**:高并发时网络拥塞。 优化方案: - 基于引用: - 引用[2]:调整JVM参数,优化GC行为。 - 引用[3]:优化数据库查询,添加索引,重构业务逻辑。 - 引用[1]:优化服务器配置,提高吞吐量。 - 其他通用方案: - 缓存:使用缓存减少数据库访问。 - 异步处理:将耗时操作异步化。 - 负载均衡:分散请求到多个实例。 - 代码优化:分析热点代码,优化算法。 回答结构: 1. **分析原因**:逐步解释可能的原因。 2. **提供优化方案**:给出具体可行的建议。 3. **参考引用**:自然地融入引用以支持论点。 4. **生成相关问题**:在末尾添加相关问题,如系统级指令要求。 系统级指令: - 使用$...$ 或 $$...$$ 格式用于数学表达式。 - 回答用中文。 - 生成相关问题。 - 引用的段落末尾添加引用标识。 在回答中,我需要自然地添加引用,例如在讨论GC优化时引用[2],在数据库优化时引用[3]。 最终回答结构: - 引言:确认问题。 - 原因分析:分点列出。 - 优化方案:分点列出。 - 结论:总结。 - §§相关问题§§:生成相关问题。 生成相关问题: - 基于用户问题,如: 1. 如何监控和诊断高并发下的性能瓶颈? 2. 具体如何实现缓存优化? 3. 有哪些工具可以测试接口在高并发下的表现? 确保回答真实可靠:基于常见性能优化实践和提供的引用。</think>### 接口高并发下时延升高的原因分析及优化方案 您的接口在低负载时响应时间为30ms,但在高并发场景下上升到200ms甚至500ms,这表明系统在高负载下存在性能瓶颈。这种问题通常源于资源竞争、外部依赖效率下降或内部处理逻辑缺陷。以下我将逐步分析原因并提供优化方案。分析基于常见性能优化实践和参考案例,确保建议真实可靠。 #### 一、原因分析 1. **资源竞争导致瓶颈**: 在高并发下,CPU、内存或I/O资源成为瓶颈。例如: - CPU过载:线程频繁切换或计算密集型任务堆积,导致任务排队等待。数学上,响应时间 $T$ 可表示为 $T = T_{\text{处理}} + T_{\text{排队}}$,其中 $T_{\text{排队}}$ 在高并发时显著增加。 - 内存不足:如果接口涉及大量数据操作,内存分配竞争可能触发频繁垃圾回收(GC),引起停顿延迟。参考案例显示,GC累积耗时在高并发下显著影响响应时间[^2]。 2. **数据库或外部依赖效率下降**: 接口若依赖数据库或外部服务,高并发时可能出现: - 数据库锁竞争:多个查询竞争同一资源,导致查询延迟。例如,未优化的SQL查询在高并发时执行时间成倍增加。 - 索引缺失或查询效率低:如引用[3]所示,缺失索引会使简单查询从毫级升至级,高并发时放大为数百毫延迟。 - 外部服务调用超时:依赖的API或微服务在高负载下响应变慢,拖累整体接口性能。 3. **线程池或并发模型不合理**: 线程池配置不当(如线程数过少或队列过长)会导致任务积压。响应时间 $T$ 可建模为 $T \propto \frac{1}{\mu - \lambda}$,其中 $\lambda$ 是请求到达率,$\mu$ 是处理率;当 $\lambda \approx \mu$ 时,$T$ 急剧上升。 4. **垃圾回收(GC)停顿影响**: 在JVM等环境中,高并发对象创建会触发Young GC或Full GC。引用[2]证实,Full GC累积耗时在高负载下可占响应时间的20%以上,导致时延波动。 5. **网络或基础设施限制**: 高并发时网络拥塞、连接池耗尽或负载均衡不均,可能增加额外延迟。例如,TCP连接建立和释放开销在每数千请求时变得显著。 #### 二、优化方案 基于原因分析,我提供以下优化方案。这些方案参考了性能优化案例,并优先推荐低成本、高收益的改动。优化顺序建议:先诊断瓶颈,再实施代码/配置优化,最后考虑架构升级。 1. **诊断与监控(首要步骤)**: - 使用工具如Arthas、Prometheus或Jaeger监控高并发下的CPU、内存、GC和SQL查询。 - 关注关键指标:GC停顿时间、数据库查询延迟、线程池队列长度。例如,引用[2]通过分析GC日志定位Full GC问题。 - 建议:模拟高并发测试(如JMeter压测),记录瓶颈点。 2. **优化数据库访问(参考引用[3])**: - **添加索引**:对查询条件字段创建索引,可减少查询时间50%以上。例如,引用[3]通过索引优化使接口从1s降至100ms。 - **批量查询与缓存**:使用`IN`查询代替循环调用,减少数据库交互次数。引入Redis缓存热点数据,降低数据库负载。 - **SQL优化**:避免N+1查询,使用连接(JOIN)或分页。代码示例: ```sql -- 优化前: SELECT * FROM table WHERE id = ? (循环调用) -- 优化后: SELECT * FROM table WHERE id IN (?, ?, ...) ``` 3. **调整JVM和GC参数(参考引用[2])**: - 扩大Young区大小:如引用[2]所示,Young区扩大0.5倍可降低P95/P99延时50%,减少Full GC耗时88%。 - 推荐配置:设置`-XX:NewRatio=2`(Young区占堆1/3)并启用G1垃圾收集器。 - 监控GC日志:确保Full GC频率低于0.1次/。 4. **优化并发模型和代码**: - **线程池调优**:根据CPU核心数设置线程池大小(如N+1规则),使用有界队列避免OOM。代码示例(Java): ```java ExecutorService executor = new ThreadPoolExecutor( corePoolSize: Runtime.getRuntime().availableProcessors() + 1, maxPoolSize: 50, keepAliveTime: 60s, workQueue: new ArrayBlockingQueue<>(100) ); ``` - **异步处理**:将耗时操作(如日志写入或外部调用)异步化,使用CompletableFuture或消息队列。 - **算法优化**:重构热点代码,例如用空间换时间减少计算复杂度。数学上,确保时间复杂度从 $O(n^2)$ 优化到 $O(n \log n)$。 5. **基础设施升级**: - **水平扩展**:通过负载均衡(如Nginx)将请求分发到多个实例,提升吞吐量。引用[1]显示,优化后吞吐量可从500请求/提升至2000请求/。 - **连接池优化**:增大数据库连接池大小(如HikariCP的`maximumPoolSize`),避免连接等待。 - **网络优化**:使用HTTP/2减少连接开销,或启用CDN缓存静态资源。 #### 三、预期效果与注意事项 - **效果估算**:通过上述优化接口在高并发下的时延有望从500ms降至100ms以内,吞吐量提升2-4倍(参考引用[1]和[3])。同时,系统稳定性增强,超时率降低至1%以下[^1]。 - **注意事项**: - 优先优化瓶颈点(80%问题源于20%代码),避免过度优化。 - 测试环境模拟真实负载,逐步实施方案。 - 监控后优化:每次改动后验证指标,确保无副作用。 高并发性能优化是一个迭代过程。如果您提供更多细节(如技术栈或监控数据),我可进一步定制方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值