首页 > 其他分享 >30s到0.8s,记录一次接口优化成功案例!

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

时间:2024-09-03 09:54:36浏览次数:6  
标签:0.8 00 缓存 30s Caffeine 接口 查询 programhandleidlist 数组

大家好,我是沐子,推荐一个程序员免费学习的编程网站
我爱编程网(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等)则适合大数据量的分析和统计,特别是在读取性能方面有显著优势。

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

标签:0.8,00,缓存,30s,Caffeine,接口,查询,programhandleidlist,数组
From: https://blog.csdn.net/u010020088/article/details/141851538

相关文章

  • 解读 XMC 转 PXIE 载板转接卡:突破接口限制的关键桥梁
    在当今高速发展的科技领域,电子设备的性能和功能不断提升,而不同接口标准之间的兼容性和通信问题也日益凸显。XMC转PXIE载板转接卡作为一种创新的解决方案,在实现XMC接口与PXIE接口的连接和数据交互方面发挥着重要作用。首先,让我们来了解一下XMC接口和PXIE接口。XMC(eXten......
  • 解读 PCIE 转 PMC 载板转接卡:跨越接口的桥梁
    在当今科技飞速发展的时代,计算机硬件的不断创新和升级为各种应用带来了更强大的性能和更多的可能性。在这一进程中,不同接口之间的转换需求日益凸显,PCIE转PMC载板转接卡便是应对这种需求的关键组件之一。PCIE转PMC载板转接卡,顾名思义,是一种用于实现PCIE(PeripheralComponen......
  • 解析 XMC 转 CPCI 载板转接卡:连接不同接口的关键纽带
    在计算机和电子设备的世界里,各种接口标准不断涌现和发展,以满足不同应用场景和性能需求。其中,XMC转CPCI载板转接卡作为一种特殊的硬件设备,在实现不同接口之间的转换和连接方面发挥着重要作用。XMC(eXtendedMezzanineCard)和CPCI(CompactPCI)是两种常见的板卡接口标准,各自具有特......
  • 探索 PCI 转 PMC 载板转接卡:连接不同接口的桥梁
    在计算机硬件领域,各种接口和总线标准不断演进,以满足日益增长的性能和功能需求。在这个过程中,不同接口之间的转换设备应运而生,其中PCI转PMC载板转接卡就是一种重要的连接解决方案。PCI转PMC载板转接卡,顾名思义,是一种用于将计算机的PCI(PeripheralComponentInterconnect)接......
  • 快速开店新策略:新手如何利用API接口开启电商之旅
    随着电子商务的蓬勃发展,越来越多的新手卖家希望建立自己的在线商店。但是,对于没有技术背景的新手来说,如何快速、高效地开设网店是一个挑战。幸运的是,API接口提供了一种解决方案,可以帮助您轻松实现网店的搭建和管理。本文将向您展示如何利用API接口快速开设网店。一、了解API接口......
  • Interface接口----一种行为规范
    什么是接口?抽象类和接口有些类似,通过对比抽象类,我们能更好的了解接口的特点。抽象类VS接口抽象类1.抽象类有抽象方法,定义抽象方法是因为有些方法时不需要父类去实现的,只是需要子类根据自己的功能进行实现。2.抽象类是对行为(方法)的一种抽象(只定义不实现),抽象类本身是用来......
  • pytest中用装饰器控制新增接口请求时间
    示例场景假设我们有一个提交数据的函数submit_data,我们希望在每次调用后等待一定的时间,以避免重复提交的问题。1.自动化提交接口的时候可以使用time.sleep()的方式这是最直接的方式,在函数调用后直接使用time.sleep()控制等待时间。importtimedefsubmit_data(da......
  • 使用Redis调用Lua脚本的方式对SpringBoot接口进行限流
    使用Redis调用Lua脚本的方式对SpringBoot接口进行限流使用Redis调用Lua脚本的方式对SpringBoot接口进行限流前言一、步骤1、自定义限流注解Limit.java,用于标注在需要限流的接口上2、编写限流类型枚举类LimitType.java3、编写限流具体实现类LimitAspect.java,通过AOP方式......
  • kimi:如何分析是哪个接口调用了最多次的operator new[]接口?
    如何分析是哪个接口调用了最多次的operatornew[]接口?重载operatornew[]:你可以在你的程序中重载operatornew[],记录每次调用时的调用栈信息。#include<iostream>#include<fstream>#include<new>#include<cstdlib>#include<execinfo.h>//包含backtrace函数#inc......
  • Tushare Pro 新版发布,免费、开源的python财经数据接口包
     Tushare是一个免费、开源的python财经数据接口包。主要实现对股票等金融数据从数据采集、清洗加工 到 数据存储的过程,能够为金融分析人员提供快速、整洁、和多样的便于分析的数据,为他们在数据获取方面极大地减轻工作量,使他们更加专注于策略和模型的研究与实现上。考虑到Py......