簡體   English   中英

多個 Spring Batch 作業同時執行導致 Spring Batch 元數據表中的死鎖

[英]Multiple Spring Batch jobs executing concurrently causing deadlocks in the Spring Batch metadata tables

我們有多個 Spring Batch 作業,每個作業都使用 CommandLineJobRunner 在自己的 java 實例中運行。 所有作業同時啟動,僅讀取/寫入平面文件並更新 SQL Server 中托管的相同 Spring Batch 元數據。 唯一涉及的數據庫是 Spring Batch 元數據數據庫。

當多個作業同時啟動時,我們會遇到 SQL 死鎖異常。 可以在下面找到更詳細的堆棧跟蹤。 從數據庫的角度來看,我們可以看到死鎖受害者正在執行以下操作之一:插入 BATCH_JOB_SEQ 默認值或從 ID < some_number 的 BATCH_JOB_SEQ 中刪除。

我們正在使用默認的 MapJobRegistry,以及默認的作業存儲庫或指定 JobRepositoryFactoryBean。 對於用於與 Spring Batch 數據庫交互的數據源,我們使用標准 Microsoft SQL Server SQLServerDriver 嘗試了 DriverManagerDataSource 或 DBCP2 pooling BasicDataSource。 我可以上傳更具體的配置文件,但在我的測試中,只要我使用 SQL Server 和標准 Spring 配置,就會出現問題。

在我的調查中,我認為問題是由於默認的增量器類 org.springframework.jdbc.support.incrementer.SqlServerMaxValueIncrementer 如何結合 SQL Server 數據庫表的構造方式來增加作業和步驟實例 ID。 SqlServerMaxValueIncrementer 中的代碼是同步的,因此如果我們在同一個 Java 實例中運行所有作業,這將不是問題。

如果我們在 DB2 數據庫中實現 Spring Batch 元數據,我們沒有問題。 SQL Server 實現使用實際的表,而 DB2 實現使用序列對象。

有沒有人遇到過這個問題? 我只是錯過了什么嗎? 似乎每當我們遇到這樣的問題時,就像 go set setting xxx in yyy 一樣簡單。 如果沒有,有誰知道為什么 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屬性。 以下是我在 SQL Server 2008 中使用的設置:

<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。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM