30s到0.8s,记录一次接口优化成功案例!

news2024/12/24 6:09:30

大家好,我是沐子,推荐一个程序员免费学习的编程网站
我爱编程网(www.love-coding.com)

** 场景**

在高并发的数据处理场景中,接口响应时间的优化显得尤为重要。本文将分享一个真实案例,其中一个数据量达到200万+的接口的响应时间从30秒降低到了0.8秒内。

这个案例不仅展示了问题诊断的过程,也提供了一系列有效的优化措施。

交易系统中,系统需要针对每一笔交易进行拦截(每一笔支付或转账就是一笔交易),拦截时需要根据定义好的规则拦截,这次需要优化的接口是一个统计规则拦截率的接口。

问题诊断

最初,接口的延迟非常高,大约需要30秒才能完成。为了定位问题,我们首先排除了网络和服务器设备因素,并打印了关键代码的执行时间。经过分析,发现问题出在SQL执行上。

发现Sql执行时间太久,查询200万条数据的执行时间竟然达到了30s,下面是是最耗时的部分相关代码逻辑:

查询代码(其实就是使用Mybatis查询,看起来正常的很)

List<Map<String, Object>> list = transhandleFlowMapper.selectDataTransHandleFlowAdd(selectSql);

统计当天的Id号(programhandleidlist字段)

SELECT programhandleidlist FROM anti_transhandle WHERE create_time BETWEEN '2024-01-08 00:00:00.0' AND '2024-01-09 00:00:00.0';

表结构(Postgresql)

图片

我以为是Sql写的有问题,先拿着sql执行了一边,发现只执行sql的执行时间是大约800毫秒,和30秒差距巨大。

Sql层面分析

使用EXPLAIN ANALYZE函数分析sql。

EXPLAIN ANALYZE
SELECT programhandleidlist FROM anti_transhandle WHERE create_time BETWEEN '2024-01-08 00:00:00.0' AND '2024-01-09 00:00:00.0';

分析结果

图片

看来是代码的部分有问题。

代码层面分析

List<Map<String, Object>> list = transhandleFlowMapper.selectDataTransHandleFlowAdd(selectSql);

Map的Key是programhandleIdList,Map的value是每一行的值。

在这里插入图片描述

图片

在Java层面,每条数据都创建了一个Map对象,对于200万+的数据量来说,这显然是非常耗时的操作,速度是被创建了大量的Map集合给拖垮的。。

为了解决这个问题,我们尝试了将200万行数据转换为单行返回,使用PostgreSQL的array_agg和unnest函数来优化查询。

第一次遇到Mybatis查询返回导致接口速度慢的问题。

优化措施

1. SQL优化

我的思路是将200万行转为一行返回。

要将 PostgreSQL 中查询出的 programhandleidlist 字段(假设这是一个数组类型)的所有元素拼接为一行,您可以使用数组聚合函数 array_agg 结合 unnest 函数。

这样做可以先将数组展开为多行,然后将这些行再次聚合为一个单一的数组。如果您希望最终结果是一个字符串,而不是数组,您还可以使用 string_agg 函数。

以下是相应的 SQL 语句:

SELECT array_agg(elem) AS concatenated_array
FROM (
    SELECT unnest(programhandleidlist) AS elem
    FROM anti_transhandle
    WHERE create_time BETWEEN '2024-01-08 00:00:00.0' AND '2024-01-09 00:00:00.0'
) sub;

在这个查询中:

  • unnest(programhandleidlist)programhandleidlist 数组展开成多行。
  • string_agg(elem) 将这些行聚合成一个以逗号分隔的字符串。

这将返回一个包含所有元素的单一数组。

查询结果由多行,拼接为了一行。

图片

再测试,现在是正常速度了,但是查询时间依旧很高。Sql查询时间0.8秒,代码中平均1秒8左右,还有优化的空间。

图片

将一列数据转换为了数组类型,查看一下内存占用,这一段占用了54比特,虽然占用不大,但是不知道为什么会mybatis处理时间这么久。

  • 因为mybatis不知道数组的大小,先给数组设定一个初始大小,如果超出了数组长度,因为数组不能扩容,增加长度只能再复制一份到另一块内存中,复制的次数多了也就增加了计算时间。
  • 数据需要在两个设备之间传输,磁盘和网络都需要时间。

在这里插入图片描述

2. 部分业务逻辑转到数据库中计算

再次优化sql,将一部分的逻辑放到Sql中处理,减少数据量。

业务上我需要统计programhandleidlist字段中id出现的次数,所以我直接在sql中做统计。

要统计每个数组中元素出现的次数,您需要首先使用 unnest 函数将数组展开为单独的行,然后使用 GROUP BY 和聚合函数(如 count)来计算每个元素的出现次数。这里是修改后的 SQL 语句:

SELECT elem, COUNT(*) AS count
FROM (
    SELECT unnest(programhandleidlist) AS elem
    FROM anti_transhandle
    WHERE create_time BETWEEN '2024-01-08 00:00:00.0' AND '2024-01-09 00:00:00.0'
) sub
GROUP BY elem;

在这个查询中:

  • unnest(programhandleidlist) 将每个 programhandleidlist 数组展开成多个行。
  • GROUP BY elem 对每个独立的元素进行分组。
  • COUNT(*) 计算每个分组(即每个元素)的出现次数。

这个查询将返回两列:一列是元素(elem),另一列是该元素在所有数组中出现的次数(count)。

在这里插入图片描述

这条sql在代码中执行时间是0.7秒,还是时间太长,毕竟数据库的数据量太大,搜了很多方法,已经是我能做到的最快查询了。

关系型数据库 不适合做海量数据计算查询。

这个业务场景牵扯到了海量数据的统计,并不适合使用关系型数据库,如果想要真正的做到毫秒级的查询,需要从设计上改变数据的存储结果。比如使用cilckhouse、hive等存储计算。

3. 引入缓存机制

减少查询数据库的次数,决定引入本地缓存机制。选择了Caffeine作为缓存框架,易于与Spring集成。

分析业务后,当天的统计数据必须查询数据库,但是查询历史日期的采用缓存的方式。如果业务中对时效性不敏感,也可以缓存当天的数据,每隔一段时间更新一次。我这里采用缓存历史日期的数据。

1.引入Caffeine依赖
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.8</version>
</dependency>
2.配置Caffeine缓存

创建一个专门的Caffeine缓存配置。使用本地缓存选择淘汰策略很重要,由于我的业务场景使根据实现来查询,所以Caffeine将按照最近最少使用(LRU)的策略来淘汰旧数据成符合业务。

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .maximumSize(500)
                .expireAfterWrite(60, TimeUnit.MINUTES));
        return cacheManager;
    }
}
3.修改ruleHitRate方法来使用Caffeine缓存

在计算昨天命中率的逻辑前加入缓存检查和更新的逻辑。

使用Caffeine缓存:

@Autowired
private CacheManager cacheManager; // 注入Spring的CacheManager

private static final String YESTERDAY_HIT_RATE_CACHE = "hitRateCache"; // 缓存名称

@Override
public RuleHitRateResponse ruleHitRate(LocalDate currentDate) {
    // ... 其他代码 ...

    // 使用缓存获取昨天的命中率
    double hitRate = cacheManager.getCache(YESTERDAY_HIT_RATE_CACHE).get(currentDate.minusDays(1), () -> {
     // 查询数据库
        Map<String, String> hitRateList = dataTunnelClient.selectTransHandleFlowByTime(currentDate.minusDays(1));
       
  // ... 其他代码 ...
  // 返回计算后的结果
        return hitRate;
    });
    // ... 其他代码 ...
}

总结

最后,测试接口,成功将接口从30秒降低到了0.8秒以内。

这次优化让我重新真正审视了关系型数据库的劣势。选择哪种类型的数据库,取决于具体的应用场景和需求。

  • 关系型数据库(Mysql、Oracle等)适合事务性强、数据一致性和完整性要求高的应用。
  • 列式数据库(HBase、ClickHouse等)则适合大数据量的分析和统计,特别是在读取性能方面有显著优势。

此次的业务场景显然更适合使用列式数据库,所以导致使用关系型数据库无论如何也不能够达到足够高的性能。

🔥免费学习编程的网站 热门推荐🔥

我爱编程网(www.love-coding.com),程序员免费学习的编程网站
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2103794.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(5) 归并排序

归并排序 归并排序是一种分治策略的排序算法。它是一种比较特殊的排序算法&#xff0c;通过递归地先使每个子序列有序&#xff0c;再将两个有序的序列进行合并成一个有序的序列。 归并排序首先由著名的现代计算机之父 John_von_Neumann 在 1945 年发明&#xff0c;被用在了 E…

【Python】Python 读取Excel、DataFrame对比并选出差异数据,重新写入Excel

背景&#xff1a;我在2个系统下载出了两个Excel&#xff0c;现在通过对下载的2个Excel数据&#xff0c;并选出差异数据 从新写入一个新的Excel中 differences_url rC:\Users\LENOVO\Downloads\differences.xlsx; //要生成的差异Excel的位置及名称 df1_url rC:\Users\LENOVO\Dow…

终于知道如何简化时间序列的特征工程了!

在处理时间序列数据时&#xff0c;时间特征往往是最基础且独特的要素&#xff0c;我们的目标通常是预测某种未来的响应或结果。 不过在很多情况下&#xff0c;除了时间特征之外&#xff0c;我们还能获取到一系列其他相关的特征或变量。 时间序列数据中的特征工程涉及从原始时…

进程、线程、时间片

1、操作系统中的程序&#xff08;如微信&#xff09;在运行时&#xff0c;系统会产生一个或多个进程&#xff0c;往往是一个 2、进程内可以包含多个线程&#xff0c;有一个主线程&#xff0c;主线程结束时&#xff0c;进程结束&#xff0c;进而程序结束 3、线程是cpu调度执行…

sql日期函数

目录 sql日期函数 1.获取日期时间函数 1.1 获取当前日期时间 1.2 获取当前日期 1.3 获取当前时间 2.datetime数据类型格式化 3.字符串数据类型转换成datetime数据类型 4.增加和减少时间间隔 5. 日期相差天数&#xff08;天&#xff09; 6. 相差时间&#xff08;小时&am…

GitHub Star 数量前 11 的开源内部工具

欢迎回到我们的 GitHub Star 系列文章&#xff01; 在之前的文章中&#xff0c;我们深入探讨了 GitHub 上最受欢迎的开源低代码项目《GitHub Star 数量前 15 的开源低代码项目》和开源无代码工具《GitHub Star 数量前 12 的开源无代码工具》&#xff0c;获得了热烈的反馈。本周…

【嵌入式学习笔记】---- OLED屏幕工作原理

1 驱动芯片SSD1603简介 1.1 SSD1603芯片图 SSD1603是一款点阵显示屏控制器&#xff0c;可嵌入在屏幕中&#xff0c;用于执行接收数据、显示存储、扫描刷新等任务驱动接口&#xff1a;128个SEG引脚和64个COM引脚&#xff0c;对应 128 64 128\times 64 12864像素点阵显示屏内置…

增强RAG:选择最佳的嵌入和重排模型

对于如何选择最佳的嵌入模型和重排模型&#xff0c;给出了详细的步骤和代码。 在构建检索增强生成&#xff08;RAG&#xff09;管道时&#xff0c;关键组件之一是检索器。我们有多种嵌入模型可供选择&#xff0c;包括 OpenAI、CohereAI 和开源的sentence transformers。此外&a…

排序(插入,希尔,选择,堆,冒泡,快速,归并,计数)

本文中的Swap()函数都是下面这段代码 // 交换 void Swap(int* p1, int* p2) {int tmp *p1;*p1 *p2;*p2 tmp; }文章目录 常见排序&#xff1a;一.插入排序1.直接插入排序&#xff1a;2.希尔排序&#xff1a; 二.选择排序1.选择排序&#xff1a;2.堆排序&#xff1a; 三.交换排…

C语言编译的过程

文章目录 1. 预处理&#xff08;Preprocessing&#xff09;2. 编译&#xff08;Compilation&#xff09;3. 汇编&#xff08;Assembly&#xff09;4. 链接&#xff08;Linking&#xff09;总结 c语言通过编译器直接编译成机器语言程序。 C语言程序的编译过程通常分为四个主要步…

STM32G474之TIM1输出PWM信号支持互补输出,死区时间和刹车

STM32G474之TIM1输出PWM信号&#xff0c;互补输出&#xff0c;支持死区时间和刹车。PWM第1通道输出引脚配置&#xff1a;TIM1_CH1映射到PA8,TIM1_CH1N映射到PA7&#xff0c;TIM1_BKIN映射到PA6&#xff0c;用作刹车输入信号。当刹车时&#xff0c;停止PWM波形输出。在使用“比较…

海上8km远距离无线通信模组,无人船MESH组网,飞睿WiFi助力海洋船只通信无障碍

在蔚蓝无垠的海洋世界里&#xff0c;每一次科技的进步都如同海上的灯塔&#xff0c;为我们照亮了前行的道路。今天&#xff0c;我要为大家介绍的&#xff0c;就是一款能够打破传统通信界限的模块——飞睿智能8km远距离无线通信模组。它不仅在陆地通信中展现出强大的实力&#x…

4. 第一个3D案例—创建3D场景

入门Three.js的第一步&#xff0c;就是认识场景Scene、相机Camera、渲染器Renderer三个基本概念&#xff0c;接下来&#xff0c;咱们通过三小节课&#xff0c;大家演示“第一个3D案例”完成实现过程。 学习建议&#xff1a;只要你能把第一个3D案例搞明白&#xff0c;后面学习就…

幼儿园数字化探索:从入园适应到全面启智

金秋九月&#xff0c;全国各地幼儿园迎来开学季。幼儿园门口&#xff0c;一幅幅温情与成长的画面交织在一起。针对小班幼儿普遍存在的分离焦虑问题&#xff0c;幼儿园教师团队展现出了高度的专业性和人文关怀。据上海市普陀区汇丽幼儿园叶老师介绍&#xff0c;为有效缓解这一挑…

【技术分享】顶尖 GIS 技术

谈到 GIS&#xff0c;就不能不提到现代地理智能。是指基于 GIS、遥感和卫星定位技术的地理空间可视化、分析、决策、设计和控制的技术总称。地理智能是 GIS 区别于其他信息技术最重要的价值之一。它由地理可视化、地理决策、地理设计、地理控制四个层次组成。它们形成了一个地理…

wordpress发送邮件的方法?怎么配置功能?

wordpress发送邮件设置教程&#xff1f;WordPress如何配置发信&#xff1f; WordPress作为最受欢迎的内容管理系统之一&#xff0c;被广泛用于创建和管理网站。AokSend将详细介绍WordPress发送邮件的方法&#xff0c;并指导您如何配置这一功能&#xff0c;确保您的邮件发送既高…

跑步用耳机哪款好?这五款运动骨传导耳机健身人士都说好!

在无线耳机市场持续繁荣的今天&#xff0c;入耳式耳机以其卓越的音质体验赢得了众多用户的青睐。然而&#xff0c;随着健康意识的提升&#xff0c;长时间佩戴入耳式耳机所带来的健康隐患日益受到关注。正是在这样的背景下&#xff0c;骨传导耳机凭借其独特的非入耳式设计&#…

iMeta: 南医大余光创组ggtree最新文章-系统发育树存储与可视化的数据结构

Ggtree&#xff1a;用于系统发育树及相关数据存储与可视化的数据结构 https://onlinelibrary.wiley.com/doi/10.1002/imt2.56 SHORT COMMUNICATION ● 2022年9月28日&#xff0c;南方医科大学基础医学院余光创团队在iMeta在线发表了题为“Ggtree: a serialized data object f…

UOS系统下Java执行权限问题

在程序部署中&#xff0c;出现 /bin/java 权限不足 问题&#xff0c;这个问题是由于java文件无运行权限导致&#xff0c;通过 sudo chmod ax bin/java 项目中需要展示系统当前所有运行程序窗口界面截图功能&#xff0c;这个功能在实现时通过 import 截图可获取界面图片&…

【Story】国际标准组织及其相关信息(全)

目录 1. ISO&#xff08;International Organization for Standardization&#xff09;2. IEC&#xff08;International Electrotechnical Commission&#xff09;3. ITU&#xff08;International Telecommunication Union&#xff09;4. ISO/IEC 合作标准5. IEEE&#xff08…