You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Batch项目缓存查询耗时过长,寻求性能优化方案

Spring Batch 1500万条数据处理性能优化方案

核心问题定位

当前性能瓶颈集中在缓存查询的线性遍历:将100万条预订数据存在List中,每次查询需遍历全量数据,时间复杂度O(n),单条查询耗时10-30ms,1500万条数据累计耗时10小时。以下是针对性优化方案:


1. 重构缓存存储结构,实现O(1)时间复杂度查询

将缓存中的List改为以(Id, SomeNum)为复合键的Map结构,直接通过键值对获取数据,彻底消除线性遍历开销:

步骤1:定义复合键(Java 16+可用Record,否则需重写equals/hashCode)

// 用Record实现不可变复合键,自动生成equals和hashCode
record CompositeKey(String id, String someNum) {}

步骤2:初始化缓存时构建Map

// 从原缓存加载数据并转换为ConcurrentHashMap
List<SomeBooking> existingCacheData = (List<SomeBooking>) cacheManager.getCache("reference-data").get("data").get();
Map<CompositeKey, SomeBooking> bookingCacheMap = existingCacheData.stream()
    .collect(Collectors.toConcurrentMap(
        booking -> new CompositeKey(booking.getId(), booking.getSomeNum()),
        Function.identity()
    ));
// 将Map存入缓存(或直接在处理器初始化时加载为实例变量)
cacheManager.getCache("reference-data").put("booking-map", bookingCacheMap);

步骤3:优化查询逻辑

// 直接通过复合键获取数据,耗时降至微秒级
Map<CompositeKey, SomeBooking> bookingCacheMap = (Map<CompositeKey, SomeBooking>) cacheManager.getCache("reference-data").get("booking-map").get();
Optional<SomeBooking> opBooking = Optional.ofNullable(bookingCacheMap.get(new CompositeKey(item.getId(), item.getSomeNum())));

2. 缓存数据预加载,避免重复获取

在处理器初始化阶段一次性加载缓存数据到实例变量,避免每次处理item都从缓存读取和类型转换:

private Map<CompositeKey, SomeBooking> bookingCacheMap;

@PostConstruct
public void initCache() {
    List<SomeBooking> existingCacheData = (List<SomeBooking>) cacheManager.getCache("reference-data").get("data").get();
    this.bookingCacheMap = existingCacheData.stream()
        .collect(Collectors.toConcurrentMap(
            booking -> new CompositeKey(booking.getId(), booking.getSomeNum()),
            Function.identity()
        ));
}

// 处理器中直接使用实例变量查询
@Override
public FinalBooking process(SomeItem item) throws Exception {
    Optional<SomeBooking> opBooking = Optional.ofNullable(bookingCacheMap.get(new CompositeKey(item.getId(), item.getSomeNum())));
    // 后续业务逻辑...
}

3. 启用多线程并行处理

利用Spring Batch的多线程TaskExecutor,充分利用CPU资源并行处理item:

XML配置示例

<!-- 配置线程池 -->
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="8"/> <!-- 根据CPU核心数调整,建议为核心数*2 -->
    <property name="maxPoolSize" value="16"/>
    <property name="queueCapacity" value="2000"/>
</bean>

<!-- 步骤配置多线程任务 -->
<step id="dataProcessingStep">
    <tasklet task-executor="taskExecutor">
        <chunk reader="flatFileItemReader" 
               processor="bookingItemProcessor" 
               writer="multiResourceItemWriter" 
               commit-interval="10000"/> <!-- 增大提交间隔,减少事务开销 -->
    </tasklet>
</step>

注意:处理器需保证线程安全,使用ConcurrentHashMap存储缓存数据即可满足要求。


4. 优化FlatFile读取性能

  • 增大缓冲区:给FlatFileItemReader配置更大的读取缓冲区,减少IO次数
    <bean id="flatFileItemReader" class="org.springframework.batch.item.file.FlatFileItemReader">
        <property name="resource" value="file:input/15M_records.txt"/>
        <property name="lineMapper" ref="bookingLineMapper"/>
        <property name="bufferedReaderFactory">
            <bean class="org.springframework.batch.item.file.DefaultBufferedReaderFactory">
                <property name="bufferSize" value="65536"/> <!-- 64KB缓冲区 -->
            </bean>
        </property>
    </bean>
    
  • 调整提交间隔:根据内存情况将commit-interval调至10000以上,减少事务提交的频繁开销。

5. 替换缓存管理器为高性能实现

将ConcurrentMapCacheManager替换为Caffeine缓存,其性能优于原生ConcurrentMap,支持内存回收、过期策略等特性:

Maven依赖

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

XML配置

<bean id="cacheManager" class="org.springframework.cache.caffeine.CaffeineCacheManager">
    <property name="caffeine" ref="caffeineConfig"/>
</bean>

<bean id="caffeineConfig" class="com.github.benmanes.caffeine.cache.Caffeine" factory-method="newBuilder">
    <property name="maximumSize" value="1000000"/> <!-- 缓存100万条数据 -->
    <property name="expireAfterAccess" value="3600"/> <!-- 按需配置过期时间 -->
</bean>

6. 减少不必要的对象开销

  • 避免处理器中重复创建临时对象,比如复用CompositeKey实例(如果item的Id和SomeNum不变)
  • 使用Lombok的@Builder或@Data减少对象创建的冗余代码
  • 尽量使用基本类型而非包装类,避免自动装箱/拆箱开销

内容的提问来源于stack exchange,提问作者PAA

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 01:37:35