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



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

当多个作业同时启动时,我们会得到SQL死锁异常。下面可以找到更详细的堆栈跟踪。从数据库的角度来看,死锁受害者正在执行以下操作之一:Insert into BATCH_JOB_SEQ default values or Delete From BATCH_JOB_SEQ where ID<some_number。

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

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

如果我们在DB2数据库中实现SpringBatch元数据,我们就不会有问题。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>

在进一步研究了这一点,并部分着手开发支持JobRepository并使用SQL Server IDENTITY而不是序列的DAO版本后,我偶然发现了在不进行任何配置的情况下解决这一问题的方法。

解决此问题的简单方法是配置JobRepositorydatabaseTypeisolationLevelForCreate属性。以下是我在SQLServer2008:中使用的设置

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

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

在启动作业时,我还保留了重试代码(请参阅问题注释),只是为了捕捉任何可能的死锁并允许其重试。这可能是一个没有意义的问题,但我不能冒险让工作无法启动。

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

最新更新