多个Spring Batch作业并发执行,导致Spring Batch元数据表中出现死锁

时间:2014-10-23 14:16:37

标签: spring-batch

我们有多个Spring Batch作业,每个作业都使用CommandLineJobRunner在自己的java实例中运行。所有作业同时启动,只读/写平面文件并更新SQL Server中托管的相同Spring Batch元数据。涉及的唯一数据库是Spring Batch元数据数据库。

当同时启动多个作业时,我们会遇到SQL死锁异常。可以在下面找到更详细的堆栈跟踪。从数据库的角度来看,我们可以看到死锁受害者正在执行以下操作之一:插入BATCH_JOB_SEQ默认值或从BATCH_JOB_SEQ中删除,其中ID< some_number。

我们正在使用默认的MapJobRegistry,以及默认的作业存储库或指定JobRepositoryFactoryBean。对于用于与Spring Batch数据库交互的数据源,我们使用标准Microsoft SQL Server SQLServerDriver尝试了DriverManagerDataSource或DBCP2池BasicDataSource。我可以上传更具体的配置文件,但在我的测试中,只要我使用SQL Server和标准的Spring配置就会出现问题。

在我的调查中,我认为问题是由于默认的增量器类org.springframework.jdbc.support.incrementer.SqlServerMaxValueIncrementer如何将作业和步骤实例ID与SQL Server数据库表的构造方式相结合。 SqlServerMaxValueIncrementer中的代码是同步的,因此如果我们在同一个Java实例中运行所有作业,这将不是问题。

如果我们在DB2数据库中实现Spring Batch元数据,我们就没有问题。 SQL Server实现使用实际表,DB2实现使用序列对象。

有没有人遇到过这个问题?我只是错过了一些东西吗?似乎每当我们遇到这样的问题时,就像在yyy中设置设置xxx一样简单。如果没有,是否有人知道为什么Spring Batch不在SQL Server实现中实现序列对象?

堆栈追踪:

[org.springframework.batch.core.launch.support.CommandLineJobRunner] - <Job Terminated in error: Could not increment identity; nested exception is com.microsoft.sqlserver.jdbc.SQLServerException: Transaction (Process ID 74) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.>
org.springframework.dao.DataAccessResourceFailureException: Could not increment identity; 
nested exception is com.microsoft.sqlserver.jdbc.SQLServerException: 
Transaction (Process ID 74) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
        at org.springframework.jdbc.support.incrementer.SqlServerMaxValueIncrementer.getNextKey(SqlServerMaxValueIncrementer.java:124)
        at org.springframework.jdbc.support.incrementer.AbstractDataFieldMaxValueIncrementer.nextLongValue(AbstractDataFieldMaxValueIncrementer.java:1
28)
        at org.springframework.batch.core.repository.dao.JdbcJobInstanceDao.createJobInstance(JdbcJobInstanceDao.java:108)
        at org.springframework.batch.core.repository.support.SimpleJobRepository.createJobExecution(SimpleJobRepository.java:135)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

配置:

<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"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="
    http://www.springframework.org/schema/batch
    http://www.springframework.org/schema/batch/spring-batch.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

<bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
    lazy-init="true">
    <property name="dataSource" ref="batchPoolingDataSource" />
</bean>

<bean id="jobRegistry"
    class="org.springframework.batch.core.configuration.support.MapJobRegistry" />

<bean id="jobRegistryBeanPostProcessor"
    class="org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor">
    <property name="jobRegistry" ref="jobRegistry" />
</bean>

<bean id="jobRepository"
    class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
    <property name="databaseType" value="SQLSERVER" />
    <property name="dataSource" ref="batchPoolingDataSource" />
    <property name="transactionManager" ref="transactionManager" />
</bean>

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

<bean id="jobExplorer"
    class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean">
    <property name="dataSource" ref="batchPoolingDataSource" />
</bean>

<bean id="jobOperator"
    class="org.springframework.batch.core.launch.support.SimpleJobOperator">
    <property name="jobExplorer" ref="jobExplorer" />
    <property name="jobLauncher" ref="jobLauncher" />
    <property name="jobRegistry" ref="jobRegistry" />
    <property name="jobRepository" ref="jobRepository" />
</bean>

<bean class="org.springframework.batch.core.scope.StepScope">
    <property name="proxyTargetClass" value="true" />
</bean>

<bean id="batchPoolingDataSource"  class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
    <property name="url" value="jdbc:sqlserver://server info" />
    <property name="username" value="${batch.jdbc.user}" />
    <property name="password" value="${batch.jdbc.password}" />
    <property name="initialSize" value="5" />
    <property name="maxTotal" value="15" />
    <property name="maxWaitMillis" value="5000" />
</bean>

<bean id="batchDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
    <property name="driverClassName" value="org.springframework.jdbc.datasource.DriverManagerDataSource" />
    <property name="url" value="jdbc:sqlserver://server info" />
    <property name="username" value="${batch.jdbc.user}" />
    <property name="password" value="${batch.jdbc.password}" />
</bean>

1 个答案:

答案 0 :(得分:10)

在进一步研究之后,部分地沿着工作于支持JobRepository并使用SQL Server IDENTITY而不是序列的DAO版本工作的路径,我偶然发现了解决方法的问题。这没有多少配置。

解决此问题的简单方法是配置databaseType的{​​{1}}和isolationLevelForCreate属性。以下是我在SQL Server 2008中使用的设置:

JobRepository

我已经通过一组Quartz工作启动了30个工作(具有不同参数的相同工作)进行了测试,到目前为止我还没有看到任何问题。

在启动作业时,我还保留了重试代码(请参阅问题评论)以捕获任何可能的死锁并允许其重试。这可能是一个没有实际意义的点,但我无法承担失业的风险。

我认为在Spring Batch文档中提到有关在使用SQL Server作为数据源时在给定时间启动多个作业的这些设置对其他人非常有帮助。再说一遍,我猜不会有很多人被SQL Server困住。