批处理框架 Spring Batch,数据迁移量过大如何保证内存?

简介: 批处理框架 Spring Batch,数据迁移量过大如何保证内存?

目录



概述


本篇博客是记录使用spring batch做数据迁移时时遇到的一个关键问题:数据迁移量大时如何保证内存。当我们在使用spring batch时,我们必须配置三个东西: reader,processor,和writer。


其中,reader用于从数据库中读数据,当数据量较小时,reader的逻辑不会对内存带来太多压力,但是当我们要去读的数据量非常大的时候,我们就不得不考虑内存等方面的问题,因为若数据量非常大,内存,执行时间等等都会受到影响。


问题是什么


在上面的内容当中我们已经提到了,我们面临的问题是数据迁移量大时的内存问题。但是这样的描述非常笼统,因此博主决定将这一部分单独拎出来说。


在学习了spring batch的知识之后我们应该很清楚的一点是,每一个spring batch的step都包含如下的部分:

76.png

即读数据,处理数据,写数据。这三个步骤里面最可能会导致内存变大问题的无疑是读数据环节。读数据作为spring batch的数据输入,是整个spring batch job的开头逻辑。


若我们的数据量不大,如只有几十万条,那我们无疑不会面临内存问题,即便一次将所有数据加载到内存当中,占的内存也不会非常多,且spring batch数据迁移的速度非常之快,几十万条的数据往往是几十秒的时间就可以迁移完成。但是当数据量变大之后,问题就不一样了。


当我们的数据量达到数百万或上千万时,若一次性将所有数据全部读到内存当中,则会占据远远超出正常范围的非常大的内存。该问题示意图如下所示:


77.png

77.png

我们写的任何程序都会有一个运行内存,假设这个内存的总容量现在只有4g,而我们数据库里需要操作的数据有8g,那么无疑,一次性的将数据读出来就会出错。这便是需要考虑得问题。


Spring提供的reader实现

spring提供了非常丰富的Reader实现,其中比较常用的从数据库读数据的有JdbcCursorItemReader,JdbcPagingItemReader等。


JdbcCursorItemReader

使用JdbcCursorItemReader的示例代码如下:


@Bean
public JdbcCursorItemReader<CustomerCredit> itemReader() {
        return new JdbcCursorItemReaderBuilder<CustomerCredit>()
                        .dataSource(this.dataSource)
                        .name("creditReader")
                        .sql("select ID, NAME, CREDIT from CUSTOMER")
                        .rowMapper(new CustomerCreditRowMapper())
                        .build();
}


JdbcCursorItemReader的好处在于使用简单,但是我们从它的sql就能发现,JdbcCursorItemReader会一次把所有的数据全部拿回来,当数据量过大而服务器内存不够时,就会遇到下面无法分配内存的问题:78.png


报错信息为:Resource exhaustion event:The JVM was unable to allocate memory from the heap. 意思就是需要分配内存的数据太多,但是无法找到足够的内存了。

反映在内存里,堆内存会呈现出如下的情况:79.png

随着每一次数据读入,堆内存都会增大,原因就在于JdbcCursorItemReader一次性读回了所有的数据,返回之后就会存在一个对象里面,而这个对象的尺寸过大,因此直接进入了老年代。在数据迁移完成之前,这些数据都不会被回收。如下图所示:


80.png

毫无疑问,当我们的数据量大时不应该使用这种类型的reader来读取数据。


JdbcPagingItemReader

JdbcPagingItemReader的作用和它的名字一样,它可以分页读取数据,但是使用起来相比于JdbcCursorItemReader更加复杂,示例代码如下:


@Bean
public JdbcPagingItemReader itemReader(DataSource dataSource, PagingQueryProvider queryProvider) {
        Map<String, Object> parameterValues = new HashMap<>();
        parameterValues.put("status", "NEW");
        return new JdbcPagingItemReaderBuilder<CustomerCredit>()
                          .name("creditReader")
                          .dataSource(dataSource)
                          .queryProvider(queryProvider)
                          .parameterValues(parameterValues)
                          .rowMapper(customerCreditMapper())
                          .pageSize(1000)
                          .build();
}
@Bean
public SqlPagingQueryProviderFactoryBean queryProvider() {
        SqlPagingQueryProviderFactoryBean provider = new SqlPagingQueryProviderFactoryBean();
        provider.setSelectClause("select id, name, credit");
        provider.setFromClause("from customer");
        provider.setWhereClause("where status=:status");
        provider.setSortKey("id");
        return provider;
}


可以看到我们能够设置page的大小,JdbcPagingItemReader将根据这个页的大小,每次读取这么多的数据,因此这些数据返回保存的对象,就只会是小对象,因此他们不会直接在老年代里分配,而是先分配在年轻代,随着年轻代不断变大,minor gc也不断进行,回收掉已经处理完的数据,老年代的内存使用量不会有任何增大,类似下图


81.png


82.png


老年代内存不会有任何变化,年轻带会随着服务器数据迁移进行而增大同时被回收。


在使用JdbcPagingItemReader时,有一个必须注意的地方就是排序关键字是必须指定的,原因在于排序是分页实现原理的技术基础。sortKey和我们指定的其他字句一起构建出SQL语句出来。在sortKey上必须使用unique key constraint约束,因为只有这样才能得以确保执行之间不会丢失任何数据。这也可以说是JdbcCursorItemReader相对便利的一点优势。


小结

必须使用unique key constraint约束,因为只有这样才能得以确保执行之间不会丢失任何数据。这也可以说是JdbcCursorItemReader相对便利的一点优势。


总结

数据量小时选择的方案差别不会很大,当数据量大时,为了有好的内存表现则使用分页的reader是必要的。但同时,因为要实现分页,也会带来一些不可避免的限制。

目录
相关文章
|
10天前
|
安全 Java 开发者
如何在Spring框架中实现横切关注点的集中管理和重用?
【4月更文挑战第30天】如何在Spring框架中实现横切关注点的集中管理和重用?
17 0
|
10天前
|
安全 Java 程序员
Spring框架的核心特性是什么?
【4月更文挑战第30天】Spring 的特性
16 0
|
2天前
|
前端开发 安全 Java
使用Spring框架加速Java开发
使用Spring框架加速Java开发
5 0
|
2天前
|
设计模式 数据采集 监控
Spring日志框架
Spring日志框架
6 0
|
2天前
|
前端开发 Java 应用服务中间件
Spring MVC框架概述
Spring MVC 是一个基于Java的轻量级Web框架,采用MVC设计模型实现请求驱动的松耦合应用开发。框架包括DispatcherServlet、HandlerMapping、Handler、HandlerAdapter、ViewResolver核心组件。DispatcherServlet协调这些组件处理HTTP请求和响应,Controller处理业务逻辑,Model封装数据,View负责渲染。通过注解@Controller、@RequestMapping等简化开发,支持RESTful请求。Spring MVC具有清晰的角色分配、Spring框架集成、多种视图技术支持以及异常处理等优点。
9 1
|
10天前
|
SQL Java 数据库连接
Springboot框架整合Spring JDBC操作数据
JDBC是Java数据库连接API,用于执行SQL并访问多种关系数据库。它包括一系列Java类和接口,用于建立数据库连接、创建数据库操作对象、定义SQL语句、执行操作并处理结果集。直接使用JDBC涉及七个步骤,包括加载驱动、建立连接、创建对象、定义SQL、执行操作、处理结果和关闭资源。Spring Boot的`spring-boot-starter-jdbc`简化了这些步骤,提供了一个在Spring生态中更便捷使用JDBC的封装。集成Spring JDBC需要添加相关依赖,配置数据库连接信息,并通过JdbcTemplate进行数据库操作,如插入、更新、删除和查询。
|
10天前
|
SQL Java 数据库连接
Springboot框架整合Spring Data JPA操作数据
Spring Data JPA是Spring基于ORM和JPA规范封装的框架,简化了数据库操作,提供增删改查等接口,并可通过方法名自动生成查询。集成到Spring Boot需添加相关依赖并配置数据库连接和JPA设置。基础用法包括定义实体类和Repository接口,通过Repository接口可直接进行数据操作。此外,JPA支持关键字查询,如通过`findByAuthor`自动转换为SQL的`WHERE author=?`查询。
|
10天前
|
安全 Java 开发者
在Spring框架中,IoC和AOP是如何实现的?
【4月更文挑战第30天】在Spring框架中,IoC和AOP是如何实现的?
21 0
|
10天前
|
Java API 数据安全/隐私保护
【亮剑】如何在Java项目中结合Spring框架实现邮件发送功能
【4月更文挑战第30天】本文介绍了如何在Java项目中结合Spring框架实现邮件发送功能。首先,需在`pom.xml`添加Spring和JavaMail依赖。然后,在`applicationContext.xml`配置邮件发送器,包括SMTP服务器信息。接着,创建一个使用依赖注入的`EmailService`类,通过`JavaMailSender`发送邮件。最后,调用`EmailService`的`sendSimpleEmail`方法即可发送邮件。最佳实践包括:使用配置管理敏感信息,利用`MimeMessage`构造复杂邮件,异常处理和日志记录,以及在大量发送时考虑使用邮件队列。
|
16天前
|
安全 Java 数据库连接
[AIGC] Spring框架的基本概念和优势
[AIGC] Spring框架的基本概念和优势