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
相关产品推荐
相关产品推荐

