为啥 Spring Batch 为每个线程使用 1 个数据库连接?

Posted

技术标签:

【中文标题】为啥 Spring Batch 为每个线程使用 1 个数据库连接?【英文标题】:Why does Spring Batch use 1 database connection for each thread?为什么 Spring Batch 为每个线程使用 1 个数据库连接? 【发布时间】:2018-08-13 15:47:20 【问题描述】:

为什么 Spring Batch 为每个线程使用 1 个数据库连接?

堆栈:

Java 8 Spring Boot 1.5 春季批次 3.0.7 HikariCP 2.7.6

数据源配置:

batcdb (postgres) readdb (oracle) writedb (postgres)

每个数据源都使用 HikariCP,每个默认连接 10 个。

Spring 批处理配置: ThreadExecutor-1:

core-pool-size: 10
max-pool-size: 10
throttle-limit: 10

Job-1 配置/ThreadPoolTask​​Executor: (通过 application.yml 设置池大小和油门限制)

@Bean
public Step job1Step() 
    return stepBuilderFactory.get("job1Step")
            .<ReadModel, WriteModel>chunk(chunkSize)
            .reader(itemReader())
            .processor(compositeProcessor())
            .writer(itemWriter())
            .faultTolerant()
            .taskExecutor(job1TaskExecutor())
            .throttleLimit(throttleLimit)
            .build();


@Bean
public ThreadPoolTaskExecutor job1TaskExecutor() 
     ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
     pool.setCorePoolSize(poolSize);
     pool.setMaxPoolSize(maxPoolSize);
     pool.setWaitForTasksToCompleteOnShutdown(false);
     return pool;
 

@Bean
@StepScope
public Job1ItemReader job1ItemReader() 
    return new Job1ItemReader(readdb, pageSize);

Job1-ItemReader 的缩写代码

public class Job1ItemReader extends JdbcPagingItemReader<ReadModel> 
...

ThreadExecutor-2:

core-pool-size: 5
max-pool-size: 5
throttle-limit: 5

Job-2 配置/ThreadPoolTask​​Executor:

@Bean
public Step job2Step() throws Exception 
    return stepBuilderFactory.get("job2Step")
            .<ReadModel2, WriteModel2>chunk(chunkSize)
            .reader(job2ItemReader())
            .processor(job2CompositeProcessor())
            .writer(job2ItemWriter())
            .faultTolerant()
            .taskExecutor(job2TaskExecutor())
            .throttleLimit(throttleLimit)
            .build();


@Bean
public ThreadPoolTaskExecutor job2TaskExecutor() 
    ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
    pool.setCorePoolSize(corePoolSize);
    pool.setMaxPoolSize(maxPoolSize);
    pool.setQueueCapacity(queueCapacity);
    pool.setWaitForTasksToCompleteOnShutdown(false);
    return pool;


@Bean
@StepScope
public Job2ItemReader job2ItemReader() 
    return new Job2ItemReader(readdb, pageSize);    

Job2-ItemReader 的缩写代码

public class Job2ItemReader extends JdbcPagingItemReader<ReadModel2> 
...

有 2 个工作 Job-1 长时间运行(多天) Job-2 通常在一两个小时内完成,并且每天都按计划运行 作业在同一个“应用程序”中,在同一个 JVM 上运行 每个 Job 都有自己定义的 ThreadPoolTask​​Executor

当 Job-1 正在运行并且 Job-2 启动时,Job-2 无法连接到readdb。 Job-2 的 Batch Reader 抛出以下错误。

Caused by: org.springframework.jdbc.support.MetaDataAccessException: Could not get Connection for extracting meta data; nested exception is org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLTransientConnectionException: HikariPool-3 - Connection is not available, request timed out after 30000ms.
at org.springframework.jdbc.support.JdbcUtils.extractDatabaseMetaData(JdbcUtils.java:339)
at org.springframework.jdbc.support.JdbcUtils.extractDatabaseMetaData(JdbcUtils.java:366)
at org.springframework.batch.support.DatabaseType.fromMetaData(DatabaseType.java:97)
at org.springframework.batch.item.database.support.SqlPagingQueryProviderFactoryBean.getObject(SqlPagingQueryProviderFactoryBean.java:158)
... 30 common frames omitted
Caused by: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLTransientConnectionException: HikariPool-3 - Connection is not available, request timed out after 30000ms.
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:80)
at org.springframework.jdbc.support.JdbcUtils.extractDatabaseMetaData(JdbcUtils.java:326)
... 33 common frames omitted
Caused by: java.sql.SQLTransientConnectionException: HikariPool-3 - Connection is not available, request timed out after 30000ms.
at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:666)
at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:182)
at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:147)
at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:123)
at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111)
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77)

(为保护无辜者而编辑)

参考:

multi-threaded step 类似question/issue,除了我的交易应该在writedb上,在这种情况下HikariPool-3readdb

【问题讨论】:

可能是您的代码根本没有将连接返回到池中吗?你没有共享任何代码,所以很难说。 我依赖 Spring Framework 来管理数据库连接;我没有在我的代码中明确打开或关闭任何数据库连接。我将尝试添加一些有关管理数据库连接的框架部分的更多详细信息。 你能发布你是如何定义 taskExecutors 的吗? 【参考方案1】:

Spring Batch 每个线程使用一个数据库连接(在某些情况下实际上可以使用更多)的原因是事务。 Spring 事务与线程相关联。 Spring Batch 中的几乎所有事情都发生在事务中。所以当你有一个单线程的工作时,你最多只使用几个连接。但是,如果您有一个多线程步骤,则希望每个线程至少有一个连接用于事务处理。

【讨论】:

谢谢@michaelminella。也许用另一种方式管理太复杂了,但是如果我的 Writer 使用的数据源与 Reader 不同,那么 Spring Batch 怎么知道事务范围是哪个数据源?您是否可以推荐更多涵盖该主题的资源、文档或博客? 写入者是应该绑定到事务的那个。您可以通过将 DataSource 与 Spring Batch 使用的 TransactionManager 关联来确保发生这种情况。否则,您可能会得到意想不到的结果。 我没有为我的任何步骤明确设置事务管理器。两个 Job 都使用 JpaItemWriter,而 EntityManager 配置了明确定义的 transactionManager。我仍然有点不清楚这把我留在了哪里? @MichaelMinella - 处理数百万条记录和使用分区时可以创建的最大连接池是多少?

以上是关于为啥 Spring Batch 为每个线程使用 1 个数据库连接?的主要内容,如果未能解决你的问题,请参考以下文章

Spring Batch多线程卡住没有任何异常输出

Spring Batch分区不适用于复合项目处理器

Spring Batch JpaItemWriter vs HibernateItemWriter 以及为啥在使用 HibernateItemWriter 时需要 HibernateTransacti

(Spring Batch)为啥表'batch_job_instance'已经存在?

Spring Batch 事务管理 - 多线程步骤

在 Spring Batch 中动态设置 gridSize(线程数)