文档章节

Spring Batch_使用多线程运行一组JOB

秋风醉了
 秋风醉了
发布于 2014/11/12 14:02
字数 1630
阅读 673
收藏 1

Spring Batch_使用多线程运行一组JOB

主要思路:在spring batch中,一个job会完成一个任务,处理一个数据集,有时这个数据集会很大,导致运行时间很长(虽然做了各种优化,数据库访问的优化,代码的优化等等),但是我想如果把这个数据集分成几块,配置几个相同的job来完成同一个任务,每个job处理其中一个数据块。这样不是也能提高效率,节省时间吗?

那么我们就来实验一下,看看可操作性。

如何给给一个大的数据集分块:可以利用limit。通过limit 构造两个sql语句,通过jobParameters 动态传递给运行中的job,那么job的item reader就会读取特定sql 语句查询上来的数据,然后进行处理。

下面我的spring batch的配置文件:

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:batch="http://www.springframework.org/schema/batch"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
		http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


	<!-- 包的扫描 -->
	<context:component-scan base-package="com.lyx.batch" />

	<bean id="exceptionHandler" class="com.lyx.batch.ExceptionListener" />

	<batch:step id="abstractStep" abstract="true">
		<batch:listeners>
			<batch:listener ref="exceptionHandler" />
		</batch:listeners>
	</batch:step>
	<bean id="abstractCursorReader" abstract="true"
		class="org.springframework.batch.item.database.JdbcCursorItemReader">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<batch:job id="addPeopleDescJob_1">
		<batch:step id="addDescStep_1" parent="abstractStep">
			<batch:tasklet>
				<batch:chunk reader="peopleAddDescReader_1" processor="addDescProcessor"
					writer="addDescPeopleWriter" commit-interval="2" />
			</batch:tasklet>
		</batch:step>
	</batch:job>
	<bean id="peopleAddDescReader_1" parent="abstractCursorReader"
		scope="step">
		<property name="sql" value="#{jobParameters['sql1']}" />
		<property name="rowMapper" ref="peopleRowMapper" />
		<property name="preparedStatementSetter" ref="preparedStatementSetter" />
		<property name="fetchSize" value="20" />
	</bean>


	<batch:job id="addPeopleDescJob_2">
		<batch:step id="addDescStep_2" parent="abstractStep">
			<batch:tasklet>
				<batch:chunk reader="peopleAddDescReader_2" processor="addDescProcessor"
					writer="addDescPeopleWriter" commit-interval="2" />
			</batch:tasklet>
		</batch:step>
	</batch:job>
	<bean id="peopleAddDescReader_2" parent="abstractCursorReader"
		scope="step">
		<property name="sql" value="#{jobParameters['sql2']}" />
		<property name="rowMapper" ref="peopleRowMapper" />
		<property name="preparedStatementSetter" ref="preparedStatementSetter" />
		<property name="fetchSize" value="20" />
	</bean>


	<bean id="peopleRowMapper" class="com.lyx.batch.PeopleRowMapper" />
	<bean id="preparedStatementSetter" class="com.lyx.batch.PeoplePreparedStatementSetter" />
	<bean id="addDescProcessor" class="com.lyx.batch.AddPeopleDescProcessor" />
	<bean id="addDescPeopleWriter" class="com.lyx.batch.AddDescPeopleWriter">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<!--tomcat jdbc pool数据源配置 -->
	<bean id="dataSource" class="org.apache.tomcat.jdbc.pool.DataSource"
		destroy-method="close">
		<property name="poolProperties">
			<bean class="org.apache.tomcat.jdbc.pool.PoolProperties">
				<property name="driverClassName" value="com.mysql.jdbc.Driver" />
				<property name="url" value="jdbc:mysql://localhost:3306/test" />
				<property name="username" value="root" />
				<property name="password" value="034039" />
			</bean>
		</property>
	</bean>

	<!-- spring batch 配置jobRepository -->
	<batch:job-repository id="jobRepository"
		data-source="dataSource" transaction-manager="transactionManager"
		isolation-level-for-create="REPEATABLE_READ" table-prefix="BATCH_"
		max-varchar-length="1000" />
	<!-- spring的事务管理器 -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<!-- batch luncher -->
	<bean id="jobLauncher"
		class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
		<property name="jobRepository" ref="jobRepository" />
	</bean>
</beans>

可以看到有两个job -addPeopleDescJob_1 和 addPeopleDescJob_2,每个job的reader 是不一样的,不一样的地方在 sql参数的不一样,是通过job parameter 动态传递进来的。

下面是AppMain4.java

package com.lyx.batch;

import javax.sql.DataSource;

import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class AppMain4 {

	private static JdbcTemplate jdbcTemplate;

	@Autowired
	public void setDataSource(DataSource dataSource) {
		jdbcTemplate = new JdbcTemplate(dataSource);
	}

	public static void main(String[] args)
			throws JobExecutionAlreadyRunningException, JobRestartException,
			JobInstanceAlreadyCompleteException, JobParametersInvalidException {

		long startTime = System.currentTimeMillis(); // 获取开始时间

		@SuppressWarnings("resource")
		final ApplicationContext context = new ClassPathXmlApplicationContext(
				new String[] { "classpath:spring-batch4.xml" });
		final JobLauncher launcher = (JobLauncher) context
				.getBean("jobLauncher");

		int rowCount = jdbcTemplate.queryForObject(
				"select count(*) from people where "
						+ "first_name like '%JOHN%' or last_name like '%DOE%'",
				Integer.class);

		final String sql1;
		final String sql2;
		int mid = (rowCount - 1) >>> 1;
		if ((rowCount & 1) == 0) { // 偶数
			sql1 = "select first_name ,last_name from people where "
					+ "first_name like ? or last_name like ? limit 0," + mid;
			sql2 = "select first_name ,last_name from people where "
					+ "first_name like ? or last_name like ? order by person_id desc limit 0,"
					+ mid;
		} else { // 奇数
			sql1 = "select first_name ,last_name from people where "
					+ "first_name like ? or last_name like ? limit 0," + mid;
			sql2 = "select first_name ,last_name from people where "
					+ "first_name like ? or last_name like ? order by person_id desc limit 0,"
					+ (mid + 1);
		}

		Thread thread_1 = new Thread(new Runnable() {
			public void run() {
				long t1 = System.currentTimeMillis(); // 获取开始时间

				// TODO Auto-generated method stub
				JobParametersBuilder job1 = new JobParametersBuilder();
				job1.addString("sql1", sql1);
				Job task1 = (Job) context.getBean("addPeopleDescJob_1");

				try {
					JobExecution result1 = launcher.run(task1,
							job1.toJobParameters());
					ExitStatus es1 = result1.getExitStatus();
					if (es1.getExitCode().equals(
							ExitStatus.COMPLETED.getExitCode())) {
						System.out.println("job1任务正常完成");
					} else {
						System.out.println("job1任务失败,exitCode="
								+ es1.getExitCode());
					}
				} catch (JobExecutionAlreadyRunningException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (JobRestartException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (JobInstanceAlreadyCompleteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (JobParametersInvalidException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				long t2 = System.currentTimeMillis(); // 获取结束时间
				System.out.println(Thread.currentThread().getName() + "运行时间: "
						+ (t2 - t1) + "ms");
			}
		});

		thread_1.start();

		Thread thread_2 = new Thread(new Runnable() {
			public void run() {
				long t1 = System.currentTimeMillis();
				// TODO Auto-generated method stub
				JobParametersBuilder job2 = new JobParametersBuilder();
				// 设置JobParameter
				job2.addString("sql2", sql2);
				Job task2 = (Job) context.getBean("addPeopleDescJob_2");
				try {
					JobExecution result2 = launcher.run(task2,
							job2.toJobParameters());
					ExitStatus es2 = result2.getExitStatus();
					if (es2.getExitCode().equals(
							ExitStatus.COMPLETED.getExitCode())) {
						System.out.println("job2任务正常完成");
					} else {
						System.out.println("job2任务失败,exitCode="
								+ es2.getExitCode());
					}
				} catch (JobExecutionAlreadyRunningException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (JobRestartException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (JobInstanceAlreadyCompleteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (JobParametersInvalidException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

				long t2 = System.currentTimeMillis(); // 获取结束时间
				System.out.println(Thread.currentThread().getName() + "运行时间: "
						+ (t2 - t1) + "ms");
			}
		});
		thread_2.start();
		long endTime = System.currentTimeMillis(); // 获取结束时间
		System.out.println("程序运行时间: " + (endTime - startTime) + "ms");
	}
}

PeoplePreparedStatementSetter.java

package com.lyx.batch;

import java.sql.PreparedStatement;
import java.sql.SQLException;

import org.springframework.jdbc.core.PreparedStatementSetter;

public class PeoplePreparedStatementSetter implements PreparedStatementSetter {

	public void setValues(PreparedStatement ps) throws SQLException {
		// TODO Auto-generated method stub
		ps.setString(1, "%JOHN%");
		ps.setString(2, "%DOE%");
		// ps.setInt(3, 1);
		// ps.setInt(4, 100);
	}
}

 

运行结果:

job1任务正常完成

Thread-3运行时间: 4573ms

 

job2任务正常完成

Thread-4运行时间: 4627ms

看到每个线程的运行时间都在4秒多。

 

再看一下在一个线程中运行一组Job的情况:

AppMain3.java

package com.lyx.batch;

import javax.sql.DataSource;

import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class AppMain3 {

	private static JdbcTemplate jdbcTemplate;

	@Autowired
	public void setDataSource(DataSource dataSource) {
		jdbcTemplate = new JdbcTemplate(dataSource);
	}

	public static void main(String[] args)
			throws JobExecutionAlreadyRunningException, JobRestartException,
			JobInstanceAlreadyCompleteException, JobParametersInvalidException {

		long startTime = System.currentTimeMillis(); // 获取开始时间

		@SuppressWarnings("resource")
		ApplicationContext context = new ClassPathXmlApplicationContext(
				new String[] { "classpath:spring-batch4.xml" });
		JobLauncher launcher = (JobLauncher) context.getBean("jobLauncher");

		int rowCount = jdbcTemplate.queryForObject(
				"select count(*) from people where "
						+ "first_name like '%JOHN%' or last_name like '%DOE%'",
				Integer.class);

		String sql1 = null;
		String sql2 = null;
		int mid = (rowCount - 1) >>> 1;
		if ((rowCount & 1) == 0) { // 偶数
			sql1 = "select first_name ,last_name from people where "
					+ "first_name like ? or last_name like ? limit 0," + mid;
			sql2 = "select first_name ,last_name from people where "
					+ "first_name like ? or last_name like ? order by person_id desc limit 0,"
					+ mid;
		} else { // 奇数
			sql1 = "select first_name ,last_name from people where "
					+ "first_name like ? or last_name like ? limit 0," + mid;
			sql2 = "select first_name ,last_name from people where "
					+ "first_name like ? or last_name like ? order by person_id desc limit 0,"
					+ (mid + 1);
		}

		JobParametersBuilder job1 = new JobParametersBuilder();
		job1.addString("sql1", sql1);
		Job task1 = (Job) context.getBean("addPeopleDescJob_1");

		JobExecution result1 = launcher.run(task1, job1.toJobParameters());
		ExitStatus es1 = result1.getExitStatus();
		if (es1.getExitCode().equals(ExitStatus.COMPLETED.getExitCode())) {
			System.out.println("job1任务正常完成");
		} else {
			System.out.println("job1任务失败,exitCode=" + es1.getExitCode());
		}

		JobParametersBuilder job2 = new JobParametersBuilder();
		// 设置JobParameter
		job2.addString("sql2", sql2);

		Job task2 = (Job) context.getBean("addPeopleDescJob_2");
		JobExecution result2 = launcher.run(task2, job2.toJobParameters());
		ExitStatus es2 = result2.getExitStatus();
		if (es2.getExitCode().equals(ExitStatus.COMPLETED.getExitCode())) {
			System.out.println("job2任务正常完成");
		} else {
			System.out.println("job2任务失败,exitCode=" + es2.getExitCode());
		}

		long endTime = System.currentTimeMillis(); // 获取结束时间
		System.out.println("程序运行时间: " + (endTime - startTime) + "ms");
	}
}

运行结果:

job1任务正常完成

job2任务正常完成

程序运行时间: 8706ms

结果你也看到了,是不是多线程运行一组job效率更高。但是用多线程,配置一组相同的job带来的问题我没有预计到,虽然提高了效率,但可能给job的重试和重启还有job的管理带来了问题。

==============END==============

© 著作权归作者所有

共有 人打赏支持
秋风醉了
粉丝 236
博文 577
码字总数 418437
作品 0
朝阳
程序员
Spring Batch_JobParameters

Spring BatchJobParameters spring batch的JobParameters是设置job运行的参数,同时也具有标志job的作用,就是判断两个job是不是同一个job( "how is one JobInstance distinguished from a...

秋风醉了
2014/11/12
0
0
Spring Batch_异步并发的processor && writer

Spring Batch_异步并发的processor && writer 普通的配置一个job,在这个demo中:http://my.oschina.net/xinxingegeya/blog/343190 job的reader是通过游标读取,commit-interval="2"表示每读...

秋风醉了
2014/11/13
0
0
Spring Batch_Configuring a Step for Restart

Spring Batch_Configuring a Step for Restart spring官方文档:http://docs.spring.io/spring-batch/trunk/reference/html/configureStep.html#stepRestart 当一个普通的 job 处于complete ......

秋风醉了
2014/11/14
0
0
Spring Batch_JOB重启机制

Spring Batch_JOB重启机制 在这一篇文章 对于restart做了试验,http://my.oschina.net/xinxingegeya/blog/344817在这片文章里,我们只是当job成功时,重启了job,对于job失败后,重启job有...

秋风醉了
2014/11/19
0
0
Duplicate entry '0' for key 'PRIMARY'_Spring Batch

Duplicate entry '0' for key 'PRIMARY'Spring Batch 在运行一个job成功后,在运行一次,会出现这个异常Duplicate entry '0' for key 'PRIMARY',也就是主键冲突。 在truncate 表之后执行以下...

秋风醉了
2014/11/12
0
1

没有更多内容

加载失败,请刷新页面

加载更多

SSO单点登录PHP简单版

  前面做了一个新项目,需要用户资源可以需要共享。由于之前没有做过这样的东西,回家之后,立马网站百度“单点登录”。帖子很多,甄别之后,这里列几篇认为比较有营养。   http://blog...

slagga
13分钟前
0
0
Java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一

对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下。 本文参考java 泛型详解、Java中的泛型方法、 java泛型详解 1 概述 泛型在j...

hensemlee
17分钟前
0
0
Annotation注解详细介绍

目录介绍 1.Annotation库的简单介绍 2.@Nullable和@NonNull 3.资源类型注释 4.类型定义注释 5.线程注释 6.RGB颜色纸注释 7.值范围注释 8.权限注释 9.重写函数注释 10.返回值注释 11.@Keep注释...

潇湘剑雨
19分钟前
0
0
一步步编写自己的PHP爬取代理IP项目(二)

这一章节我们正式开展我们的爬虫项目,首先我们先要知道哪个网站能获取到免费代理IP,目前比较火的有西刺代理,快代理等,这里我们拿西刺代理作为例子。 这里就是一个个免费的IP地址以及各自...

NateHuang
38分钟前
1
0
11-利用思维导图梳理JavaSE-Java的反射机制

11-利用思维导图梳理JavaSE-Java的反射机制 主要内容 1.反射与Class类 1.1.反射概念 1.2.Class类 1.3.实例化Class类 1.4.反射的作用 1.5.Class对象的作用 2.反射的深入应用 2.1.调用无参的成...

飞鱼说编程
44分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部