SpringBatch介绍及hello Spring Batch教程
一、什么是SpringBatch
1、Spring Batch 是一个轻量级的、完善的批处理框架,旨在帮助企业建立健壮、高效的批处理应用。
2、Spring Batch是Spring的一个子项目,使用Java语言并基于Spring框架为基础开发,使得已经使用 Spring 框架的开发者或者企业更容易访问和利用企业服务;
3、Spring Batch 提供了大量可重用的组件,包括了日志、追踪、事务、任务作业统计、任务重启、跳过、重复、资源管理。通过 Spring Batch 能够支持简单的、复杂的和大数据量的批处理作业。同时它也提供了优化和分片技术用于实现高性能的批处理任务。
什么是批处理?
在大型企业中,由于业务复杂、数据量大、数据格式不同、数据交互格式繁杂,并非所有的操作都能通过交互界面进行处理。而有一些操作需要定期读取大批量的数据,然后进行一系列的后续处理。这样的过程就是“批处理”。
二、Spring Batch的特点
灵活性:Spring批处理应用程序非常灵活。只需更改XML文件即可更改应用程序中的处理顺序。
可维护性:Spring批量应用程序易于维护。 Spring Batch作业包括步骤,每个步骤都可以进行分离,测试和更新,而不影响其他步骤。
可伸缩性:使用分区技术,可以缩放Spring Batch应用程序。 这些技术可以让你并行执行作业的步骤。并行执行单个线程。
可靠性:如果发生任何故障,可以通过拆除步骤来从停止的地方重新开始作业。
支持多种文件格式:Spring Batch为XML,Flat文件,CSV,MYSQL,Hibernate,JDBC,Mongo,Neo4j等大量写入器和读取器提供支持。
多种启动作业的方式:可以使用Web应用程序,Java程序,命令行等来启动Spring Batch作业。
除此之外,Spring Batch应用程序支持失败后自动重试。 跟踪批次执行期间和完成批次处理后的状态和统计数据。 运行并行作业。 一些服务,诸如日志记录,资源管理,跳过和重新启动处理等。
批处理应用的特点
数据量大,从数万到数百万甚至上亿不等;
整个过程全部自动化,并预留一定接口进行自定义配置;
这样的应用通常是周期性运行,比如按日、周、月运行;
对数据处理的准确性要求高,并且需要容错机制、回滚机制、完善的日志监控等。
三、spring batch 的使用场景:(实践)
一个典型的批处理程序应用的一般场景:
- 从数据库、文件或队列中读取大量记录。
- 以某种方式处理数据。
- 修改后回写数据。
Spring Batch
自动的执行这些批处理的迭代操作,提供处理类似事务的功能,通常在脱机环境中处理,无需任何用户交互。批处理作业是大多数IT项目的一部分,Spring Batch
是唯一一个提供强大的企业级解决方案的开源框架。
四、SpringBatch的核心功能
事务管理
基于块的处理过程
声明式的输入/输出操作
启动、终止、重启任务
重试/跳过任务
基于Web的管理员接口
五、优化(使用过程中的问题及解决):
a. 使用Retry和Skip增强批处理工作的健壮性
在处理百万级的数据过程过程中难免会出现异常。如果一旦出现异常而导致整个批处理工作终止的话那么会导致后续的数据无法被处理。Spring Batch内置了Retry(重试)和Skip(跳过)机制帮助我们轻松处理各种异常。我们需要将异常分为三种类型。
第一种是需要进行Retry的异常,它们的特点是该异常可能会随着时间推移而消失,比如数据库目前有锁无法写入、web服务当前不可用、web服务满载等。所以对它们适合配置Retry机制。
第二种是需要Skip的异常,比如解析文件的某条数据出现异常等,因为对这些异常即使执行Retry每次的结果也都是相同,但又不想由于某条数据出错而停止对后续数据的处理。
第三种异常是需要让整个Job立刻失败的异常,比如如果出现了OutOfMemory的异常,那么需要整个Job立刻终止运行。
一般来说需要Retry的异常也要配置Skip选项,从而保证后续的数据能够被继续处理。我们也可以配置SkipLimit选项保证当Skip的数据条目达到一定数量后及时终止整个Job。
b.使用自定义的Decider来实现Job flow
在Job执行过程中不一定都是顺序执行的,我们经常需要根据某个job的输出数据或执行结果来决定下一步的走向。
以前我们会把一些判断放置在下游step中进行,这样可能会导致有些step实际运行了,但其实并没有做任何事情。比如一个step执行过程中会将失败的数据条目记录到一个报告中,而下一个step会判断有没有生成报告,如果生成了报告则将该报告发送给指定联系人,如果没有则不做任何事情。
这种情况下可以通过Decider机制来实现Job的执行流程。在Spring batch 3.0中Decider已经从Step中独立出来,和Step处于同一级别。
六、Spring Batch 结构:
SpringBatch,作为一个 Spring 组件,通过使用 Spring 的依赖注入(Dependency Injection) 来提供处理批处理的功能。
Spring Batch的一个基本层级结构:
首先,Spring Batch运行的基本单位是一个Job,一个Job就做一件批处理的事情。
一个Job包含很多Step,step就是每个job要执行的单个步骤。
Step里面,会有Tasklet,Tasklet是一个任务单元,它是属于可以重复利用的东西。
然后是Chunk,chunk就是数据块,你需要定义多大的数据量是一个chunk。
Chunk里面就是不断循环的一个流程,读数据,处理数据,然后写数据。Spring Batch会不断的循环这个流程,直到批处理数据完成
Spring batch框架有4个主要组件:JobLauncher、Job、Step和JobRepository。
1)JobLauncher(任务启动器):通过它启动任务,可以理解为程序的入口。
2)Job(任务):一个具体的任务。
3)Step(步骤):一个具体的执行步骤,一个Job中可以有多个Step。
4)JobRepository(任务仓库):存储数据的仓库,在任务执行的时候,需要用它来记录任务状态信息,可以看做是一个数据库的接口。
job : 任务,
一个job可以由一个或多个step组成,通过JobBuilderFactory实例创建Bean,使用next指向下一个step;
flow:
1.是多个step的集合;
2.可以被多个Job复用;
3.由flowBuilder 来创建的;
split:
实现并发执行;
七、具体实现功能时用这两种方式:
7.1 Tasklet 方式实现
分析与设计
takslet意味着在step中执行单个任务,job有多个step按一定顺序组成,每个步骤应该执行一个具体任务。
我们的job有三个步骤:
a.从输入csv文件读
b.对每个输入行数据计算年龄
c.写姓名和年龄至输出csv文件
7.2 Chunk方法
分析与设计
见名思议,该方法基于数据块(一部分数据)执行。也就是说,其不是一次读、处理和写所有行,而是一次仅读、处理、写固定数量记录。然后重复循环执行直到读不到数据为止。
因此,此流程与上面有些差异:
while 有数据:
do X 行数据
读一行
处理一行
写 X 行数据
Tasklet 与chunk 总结
两者差异显示了各自适用场景。tasklet更适合一个步骤到另一个步骤场景。chunk提供简单解决方案:实现处理分页读,或我们不想在内存中保留大量数据场景。
总结
Spring Batch为我们提供了非常实用的功能,对批处理场景进行了完善的抽象,它不仅能实现小数据的迁移,也能应对大企业的大数据实践应用。它让我们开发批处理应用可以事半功倍
============================================================
Spring Batch 之 Hello World教程
Spring Batch 之 Hello World教程
本文我们基于spring boot和spring batch 实现一个简单hello world入门批处理程序。如果你刚刚接触spring batch,这篇教程会让你花最短时间理解spring batch框架。
Spring Batch 框架介绍
开始代码之前,我们先了解框架中的核心组件,见下图:
- 批处理过程有Job组成,job是封装整个批处理过程的实体。
- Job有一个或多个Step组成。大多数情况下,step负责读数据(ItemReader),处理数据(ItemProcessor)以及写数据(ItemWriter)。
- JobLauncher 负责启动job。
- JobRepository负责存储关于配置和执行Job的元信息。
理解基本概念之后,我们通过一个简单Hello World程序来说明框架如何工作。示例从person.csv文件读取用户的firstName 和 lastName,然后给每个用户增加问候(hello),最后把结果写入 greetings.txt文件。
项目依赖
通过idea或Spring Initializr 新建spring boot 项目,增加相应依赖:
plugins {
id 'org.springframework.boot' version '2.1.3.RELEASE'
id 'java'
}
apply plugin: 'io.spring.dependency-management'
group = 'com.dataz.batch'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-batch'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.batch:spring-batch-test'
}
spring-boot-starter-batch 引入Spring Boot 和 Spring Batch 依赖.
spring-boot-starter-test 引入Spring Boot 应用的测试依赖,包括JUnit, Hamcrest 和 Mockito.
spring-batch-test 引入测试Spring batch job和step的工具类.
Spring Boot 设置
我们使用了Spring Boot可以让Spring Batch应用立刻运行。
@SpringBootApplication注解包括@Configuration, @EnableAutoConfiguration, @ComponentScan 三个注解,消除或简化配置。
Spring Batch缺省使用数据库存储配置批处理job的元数据。为了简化,我们不使用数据库,而使用基于内存(Map)存储。
spring-boot-starter-batch 自动引入spring-boot-starter-jdbc,后者会尝试实例化datasource。在@SpringBootApplication注解上增加exclude = {DataSourceAutoConfiguration.class}
,避免Spring Boot 自动配置Datasource至数据库连接。
完整 HelloWorldApplication 代码如下:
package com.dataz.batch.helloworld;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class HelloWorldApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWorldApplication.class, args);
}
}
创建域对象模型
处理数据之前,一般需要把数据映射为域对象。我们的数据存储在 src/resources/csv/person.csv
,每行包括内容如下:
John, Doe
Jane, Doe
为了映射数据至Person对象,下面定义Person类:
package com.dataz.batch.helloworld.dto;
import lombok.Data;
@Data
public class Person {
private String firstName;
private String lastName;
}
配置Spring Batch
定义BatchConfig类:
package com.dataz.batch.helloworld.configuration;
import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
@EnableBatchProcessing
public class BatchConfig extends DefaultBatchConfigurer {
@Override
public void setDataSource(DataSource dataSource) {
// initialize will use a Map based JobRepository (instead of database)
}
}
我们创建BatchConfig 类用于配置 Spring Batch。@Configuration注解表明作为bead定义配置类。
@EnableBatchProcessing注解启用必要的Spring Batch特性,也提供构建job的基本配置。该注解功能强大,背后为我们做了很多事情,其创建下列bean:
- JobRepository (bean名为 “jobRepository”)
- JobLauncher (bean名为 “jobLauncher”)
- JobRegistry (bean名为 “jobRegistry”)
- JobExplorer (bean名为 “jobExplorer”)
- PlatformTransactionManager (bean名为 “transactionManager”)
- JobBuilderFactory (bean名为 “jobBuilders”) 用于构建job对象,避免给每个job注入jobRepository
- StepBuilderFactory (bean名为 “stepBuilders”) 用于构建job对象,避免给每个step注入jobRepository 和事物管理bean
因为Spring Batch使用Map存储 ,需要继承DefaultBatchConfigurer,并重写setDataSource方法,不设置数据源。让自动配置使用基于Map的JobRepository.
配置批处理Job
下面继续配置批处理Job,我们创建HelloWorldJobConfig配置类并增加@Configuration注解:
package com.dataz.batch.helloworld.configuration;
import com.dataz.batch.helloworld.dto.Person;
import com.dataz.batch.helloworld.item.PersonItemProcessor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.FlatFileItemWriter;
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder;
import org.springframework.batch.item.file.transform.PassThroughLineAggregator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
@Configuration
public class HelloWorldJobConfig {
@Bean
public Job helloWorlJob(JobBuilderFactory jobBuilders,
StepBuilderFactory stepBuilders) {
return jobBuilders.get("helloWorldJob")
.start(helloWorldStep(stepBuilders)).build();
}
@Bean
public Step helloWorldStep(StepBuilderFactory stepBuilders) {
return stepBuilders.get("helloWorldStep")
.<Person, String>chunk(10).reader(reader())
.processor(processor()).writer(writer()).build();
}
@Bean
public FlatFileItemReader<Person> reader() {
return new FlatFileItemReaderBuilder<Person>()
.name("personItemReader")
.resource(new ClassPathResource("csv/person.csv"))
.delimited().names(new String[] {"firstName", "lastName"})
.targetType(Person.class).build();
}
@Bean
public PersonItemProcessor processor() {
return new PersonItemProcessor();
}
@Bean
public FlatFileItemWriter<String> writer() {
return new FlatFileItemWriterBuilder<String>()
.name("greetingItemWriter")
.resource(new FileSystemResource(
"target/test-outputs/greetings.txt"))
.lineAggregator(new PassThroughLineAggregator<>()).build();
}
}
helloWorlJob bean 使用JobBuilderFactory 通过传递job和step的名称创建job,运行时通过名称进行查找。
需要注意的是jobBuilders 和 stepBuilders bean 会被自动注入。它们在上一节中通过@EnableBatchProcessing负责创建。
helloWorldStep bean 定义step中执行的不同item,这里使用StepBuilderFactory创建step。
首先传入step名称,然后使用chunk()方法指定每个事物处理数据项数量,同时也指明输入类型为person,输出为string。最后给step增加reader、process以及writer。
我们使用FlatFileItemReader读perons.csv文件,该类提供了基本的文件文件处理功能。通过FlatFileItemReaderBuilder创建FlatFileItemReader实现,首先传入reader名称,接着传入处理资源,最后是输出类型Person。FlatFileItemReader处理文件需要额外的信息,分隔符(默认为逗号)及字段映射信息(names方法指定),表明第一列映射到Person的firstName,第二列映射到lastName。
PersonItemProcessor负责处理数据,其转换每个Person对象为一个字符串。
数据处理完通过FlatFileItemWriter 组件写入一个文本文件。FlatFileItemWriterBuilder 创建FlatFileItemWriter组件,指定名称和对应处理资源(greeting.txt文件)。FlatFileItemWriter需要知道如何生成单个字符串,因为本例中输出以及是一个字符串,可以使用PassThroughLineAggregator,这是最基本的实现,其假设对象已经是一个字符串。
处理数据
大多数情况下,job执行过程中需要进行数据处理。我们示例中把Person转成字符串。PersonItemProcessor类实现ItemProcessor接口,在process方法中实现具体业务。
package com.dataz.batch.helloworld.item;
import com.dataz.batch.helloworld.dto.Person;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.item.ItemProcessor;
public class PersonItemProcessor
implements ItemProcessor<Person, String> {
private static final Logger LOGGER =
LoggerFactory.getLogger(PersonItemProcessor.class);
@Override
public String process(Person person) {
String greeting = "Hello " + person.getFirstName() + " "
+ person.getLastName() + "!";
LOGGER.info("converting '{}' into '{}'", person, greeting);
return greeting;
}
}
运行或测试Batch示例
我们可以直接运行HelloWorldApplication类。也可以通过单元测试运行,首先定义单元测试配置类:
package com.dataz.batch.helloworld.configuration;
import org.springframework.batch.core.Job;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({BatchConfig.class, HelloWorldJobConfig.class})
public class BatchTestConfig {
@Autowired
private Job helloWorlJob;
@Bean
JobLauncherTestUtils jobLauncherTestUtils() {
JobLauncherTestUtils jobLauncherTestUtils =
new JobLauncherTestUtils();
jobLauncherTestUtils.setJob(helloWorlJob);
return jobLauncherTestUtils;
}
}
这里主要定义JobLauncherTestUtils bean,用于单元测试。
下面定义测试类:
package com.dataz.batch.helloworld;
import com.dataz.batch.helloworld.configuration.BatchTestConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.test.JobLauncherTestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {BatchTestConfig.class})
public class HelloWorldApplicationTests {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Test
public void testHelloWorldJob() throws Exception {
JobExecution jobExecution = jobLauncherTestUtils.launchJob();
assertThat(jobExecution.getExitStatus().getExitCode()).isEqualTo("COMPLETED");
}
}
注意这里使用@RunWith(SpringRunner.class)和@SpringBootTest(classes = {BatchTestConfig.class})注解,通知junit使用Spring test和spring Boot支持。运行结果如下:
2019-03-29 16:16:23.210 INFO 9004 --- [main] c.d.b.h.HelloWorldApplicationTests : Started HelloWorldApplicationTests in 7.919 seconds (JVM running for 16.449)
2019-03-29 16:16:24.708 INFO 9004 --- [main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=helloWorldJob]] launched with the following parameters: [{random=360857}]
2019-03-29 16:16:24.724 INFO 9004 --- [main] o.s.batch.core.job.SimpleStepHandler : Executing step: [helloWorldStep]
2019-03-29 16:16:24.919 INFO 9004 --- [main] c.d.b.h.item.PersonItemProcessor : converting 'Person(firstName=John, lastName=Doe)' into 'Hello John Doe!'
2019-03-29 16:16:24.921 INFO 9004 --- [main] c.d.b.h.item.PersonItemProcessor : converting 'Person(firstName=Jane, lastName=Doe)' into 'Hello Jane Doe!'
2019-03-29 16:16:24.937 INFO 9004 --- [main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=helloWorldJob]] completed with the following parameters: [{random=360857}] and the following status: [COMPLETED]
最终你能看到结果在文件在target/test-outputs/greetings.txt,其结果如下:
Hello John Doe!
Hello Jane Doe!
总结
本文通过简单示例说明了Spring Batch的原理及关键概念及组件。包括job、step以及处理环节(ItemReader、ItemProcessor、ItemWriter),以及初始化Spring Batch运行环境和单元测试配置。