优化接口速度真实案例[百万级别数据量]

场景

在高并发的数据处理场景中,接口响应时间的优化显得尤为重要。本文将分享一个真实案例,其中一个数据量达到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)
字段名数据类型描述
idserial主键,自增
create_timetimestamp(6) with time zone创建时间
programhandleidlistcharacter varying[]程序处理ID列表(数组类型

我以为是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>
  1. 配置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;
    }
}
  1. 修改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等)则适合大数据量的分析和统计,特别是在读取性能方面有显著优势。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【经验贴】如何高效进行项目文档的编制及管理?

“做完一个项目到底会产出多少份文档&#xff1f;” 今天看到这样一个吐槽贴&#xff1a;小李作为刚入行的项目经理&#xff0c;每天上班期间电话、会议、邮件各种不停歇&#xff0c;晚上还要加班做各种文档&#xff1b;由于经验不足&#xff0c;熬到十一二点还做不完是常态。年…

【QML COOK】- 007-Item对象、信号和槽

信号&#xff08;signal&#xff09;和槽&#xff08;slot&#xff09;是Qt的独特的设计&#xff0c;自然在QML中也被支持。 Item是QML所有类型的基类&#xff0c;Item类型不会显示在窗口上&#xff0c;但是可以支持信号和槽。本节就用Item编写一个信号和槽的实例。 1. 创建Q…

Springboot注解@EnableConfigurationProperties和@ConfigurationProperties关系和作用

目录 EnableConfigurationProperties和ConfigurationProperties关系是什么&#xff1f; 简介 ConfigurationProperties EnableConfigurationProperties 二者之间的联系 总结 EnableConfigurationProperties和ConfigurationProperties关系是什么&#xff1f; 其实我能明白…

电脑/设备网络共享给其他设备上网

文章目录 一、概述二、设置网络共享2.1 电脑可以上网&#xff0c;通过网络共享让其他设备也可以上网2.2 手机如何使用USB数据线共享网络给电脑 一、概述 现在有如下几种情况&#xff1a; 设备本身不能上网&#xff0c;需要通过电脑上网 笔记本WIFI连热点上网&#xff0c;然后…

2023年全国职业院校技能大赛软件测试赛题—单元测试卷①

单元测试 一、任务要求 题目1&#xff1a;根据下列流程图编写程序实现相应分析处理并显示结果。返回文字“xa*a*b的值&#xff1a;”和x的值&#xff1b;返回文字“xa-b的值&#xff1a;”和x的值&#xff1b;返回文字“xab的值&#xff1a;”和x的值。其中变量a、b均须为整型…

记录一下误删除libc.so.6的经历

起因&#xff1a; 在配置环境时&#xff0c;出现’GLIBCXX_3.4.29 not found’的错误&#xff0c;在解决这个问题的过程中&#xff0c;需要删除sudo rm /usr/lib/x86_64-linux-gnu/libstdc.so.6软连接&#xff0c;但是一不小心sudo rm /lib/x86_64-linux-gpu/libc.so.6&#xf…

第二百五十九回

文章目录 知识回顾示例代码经验总结 我们在上一章回中介绍了MethodChannel的使用方法&#xff0c;本章回中将介绍EventChannel的使用方法.闲话休提&#xff0c;让我们一起Talk Flutter吧。 知识回顾 我们在前面章回中介绍了通道的概念和作用&#xff0c;并且提到了通道有不同的…

VSCode使用技巧

选择python 解释器 使用快捷键CtrlShiftP Python: Select Interpreter快捷键 返回上一次光标的位置 重新设置一下 navigate

可以打印试卷的软件有哪些?推荐这几款

可以打印试卷的软件有哪些&#xff1f;随着科技的飞速发展&#xff0c;越来越多的学习工具如雨后春笋般涌现&#xff0c;其中&#xff0c;能够打印试卷的软件尤其受到广大学生和家长的青睐。这些软件不仅方便快捷&#xff0c;而且内容丰富&#xff0c;可以满足不同学科、不同年…

BUUCTF--get_started_3dsctf_20161

这题我本来以为是简单的ret2text.结果还是中了小坑。 先看保护&#xff1a; 32位程序&#xff0c;接下来测试下效果&#xff1a; 看看IDA中逻辑&#xff1a; 题目一进来有很多函数&#xff0c;盲猜是静态编译了。而且在函数堆中发现了个get_flag。信心慢慢的直接写代码返回get…

如何进行有竞争力的SEO审计以超越行业竞争对手

许多营销人员都有兴趣密切关注竞争对手的搜索引擎优化 &#xff08;SEO&#xff09;。这是有道理的——无论你是刚开始做SEO&#xff0c;还是已经做了一段时间&#xff0c;你都希望对搜索引擎结果页面&#xff08;SERP&#xff09;的竞争格局有一个清晰的认识&#xff0c;这样你…

Fiddler工具 — 13.AutoResponder应用场景

简单介绍几个应用场景&#xff1a; 场景一&#xff1a;生产环境的请求重定向到本地文件&#xff0c;验证结果。 例如&#xff1a;某网站或者系统修改了问题&#xff0c;但尚未更新到生产环境&#xff0c;可重定向到本地修改后的文件进行验证&#xff0c;这样能够避免更新到生产…

【AI视野·今日Robot 机器人论文速览 第七十四期】Wed, 10 Jan 2024

AI视野今日CS.Robotics 机器人学论文速览 Wed, 10 Jan 2024 Totally 17 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers Hold em and Fold em: Towards Human-scale, Feedback-Controlled Soft Origami Robots Authors Immanuel Ampomah Mensah, Je…

中科院国产多语言大模型-YAYI2开源!家族AI应用场景全覆盖!

项目简介 YAYI 2 是中科闻歌研发的新一代开源大语言模型&#xff0c;中文名&#xff1a;雅意&#xff0c;采用了超过 2 万亿 Tokens 的高质量、多语言语料进行预训练。 开源地址&#xff1a;https://github.com/wenge-research/YAYI2 YAYI2-30B是其模型规模&#xff0c;是基…

【C语言题解】 | 144. 二叉树的前序遍历

144. 二叉树的前序遍历 144. 二叉树的前序遍历代码 144. 二叉树的前序遍历 提示&#xff1a; 树中节点数目在范围 [0, 100] 内 函数原型&#xff1a; int* preorderTraversal(struct TreeNode* root, int* returnSize) {首先先观察一下这个函数原型&#xff0c;TreeNode* roo…

为什么要进行漏洞扫描工作

随着互联网的普及和信息技术的飞速发展&#xff0c;网络安全问题愈发引人关注。其中&#xff0c;漏洞扫描作为保障网络安全的重要手段&#xff0c;受到了广泛的关注和应用。本文将详细介绍漏洞扫描的概念、效果、使用场景等&#xff0c;以期为读者提供有关漏洞扫描的全面了解。…

01.坦克大战项目-Java绘图坐标体系

01. Java绘图 01. Java绘图坐标体系 1. 坐标体系介绍 ​ 下图说明了java坐标系。坐标原点位于左上角&#xff0c;以像素为单位。在Java坐标系中&#xff0c;第一个是x坐标系&#xff0c;表示当前位置为水平方向&#xff0c;距离坐标原点x个像素&#xff1b;第二个是y坐标表示…

电脑弹窗‘找不到msvcp120dll,无法继续执行代码’要怎么解决?快速修复msvcp120dll

当你的电脑弹窗‘找不到msvcp120dll,无法继续执行代码’&#xff0c;你是否一脸懵逼不知道要怎么去解决呢&#xff1f;其实这种dll丢失的问题还是比较常见的&#xff0c;所以我们遇到也不会担心&#xff0c;只要了解了&#xff0c;那么我们就可以轻松的修复msvcp120dl文件。下面…

C#实现Excel合并单元格数据导入数据集

目录 功能需求 Excel与DataSet的映射关系 范例运行环境 Excel DCOM 配置 设计实现 组件库引入 ​方法设计 返回值 参数设计 打开数据源并计算Sheets 拆分合并的单元格 创建DataTable 将单元格数据写入DataTable 总结 功能需求 将Excel里的worksheet表格导入到Da…

为什么企业容易陷入“自嗨式营销”,媒介盒子分析

互联网时代&#xff0c;各类信息都传播的非常快&#xff0c;同时信息技术的成熟也让许多企业可以监测广告效果&#xff0c;比如曝光、互动、转化等都可以通过数据体现&#xff0c;然而很多企业在营销过程中却发现&#xff0c;大部分的钱、精力、人力等都被浪费了。出现这种情况…