精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

自從用了 Spring Batch,效率飆升500%!

開發(fā) 前端
痛點:?凌晨時段需掃描百萬級賬戶數(shù)據(jù),手工計算容易遺漏Spring Batch方案:?分片讀取賬戶數(shù)據(jù),批量計算利息,失敗自動重試實際案例:?某銀行系統(tǒng)改造后,利息計算時間從4小時縮短至23分鐘

一、為什么需要批處理?

1. 應(yīng)用場景解析

場景1:銀行每日利息計算

圖片圖片

  • 痛點: 凌晨時段需掃描百萬級賬戶數(shù)據(jù),手工計算容易遺漏
  • Spring Batch方案: 分片讀取賬戶數(shù)據(jù),批量計算利息,失敗自動重試
  • 實際案例: 某銀行系統(tǒng)改造后,利息計算時間從4小時縮短至23分鐘
場景2:電商訂單歸檔
// 傳統(tǒng)SQL示例(存在性能問題)
DELETE FROM active_orders 
WHERE create_time < '2023-01-01'
LIMIT 5000; // 需循環(huán)執(zhí)行直到無數(shù)據(jù)
  • 問題: 直接刪除百萬級數(shù)據(jù)會導致數(shù)據(jù)庫鎖表
  • 正確做法: 使用Spring Batch分頁讀取→寫入歷史表→批量刪除
場景3:日志分析

圖片圖片

  • 典型需求: 分析Nginx日志中的API響應(yīng)時間分布
  • 特殊挑戰(zhàn): 處理GB級文本文件時的內(nèi)存控制
場景4:醫(yī)療數(shù)據(jù)遷移

圖片圖片

  • 特殊要求: 遷移過程中老系統(tǒng)仍需正常使用
  • 解決方案: 使用Spring Batch的增量遷移模式

2. 傳統(tǒng)方式痛點

圖片圖片

詳細解釋每個痛點:

  • 資源管理復(fù)雜
// 典型的多線程錯誤示例
ExecutorService executor = Executors.newFixedThreadPool(8);
try {
    while(hasNextPage()) {
        List<Data> page = fetchNextPage();
        executor.submit(() -> processPage(page)); // 可能引發(fā)內(nèi)存泄漏
    }
} finally {
    executor.shutdown(); // 忘記調(diào)用會導致線程堆積
}

常見問題:線程池配置不當導致OOM、數(shù)據(jù)庫連接泄露

  • 容錯性黑洞
// 偽代碼:脆弱的錯誤處理
for (int i=0; i<3; i++) {
    try {
        processBatch();
        break;
    } catch (Exception e) {
        if (i == 2) sendAlert(); // 簡單重試無法處理部分成功場景
    }
}

真實案例:某支付系統(tǒng)因未處理部分失敗,導致重復(fù)出款

  • 維護噩夢
# 典型硬編碼配置
batch.size=1000
input.path=/data/in
output.path=/data/out

問題根源:參數(shù)修改需要重新部署、不同環(huán)境配置混雜

  • 監(jiān)控盲區(qū)
# 開發(fā)人員常用的臨時方案
nohup java -jar batch.jar > log.txt 2>&1 &
tail -f log.txt # 無法獲知實時進度

關(guān)鍵缺陷:無法回答"處理到哪了?"、"還剩多少?"等業(yè)務(wù)問題

Spring Batch對比優(yōu)勢表

圖片圖片

二、Spring Batch核心架構(gòu)

1. 四大金剛組件深度解析

組件1:Job(作業(yè)工廠)

圖片圖片

  • 核心作用: 定義完整的批處理流水線(如月度報表生成流程)
  • 真實案例: 某銀行的日終對賬Job包含三個Step
@Bean
public Job reconciliationJob(){
    return jobBuilderFactory.get("dailyReconciliation")
            .start(downloadBankFileStep())
            .next(validateDataStep())
            .next(generateReportStep())
            .build();
}
組件2:Step(裝配流水線)

圖片圖片

設(shè)計模式:采用分塊(Chunk)處理機制

配置示例:

@Bean
public Step importStep(){
    return stepBuilderFactory.get("csvImport")
            .<User, User>chunk(500)  // 每500條提交一次
            .reader(csvReader())
            .processor(validationProcessor())
            .writer(dbWriter())
            .faultTolerant()
            .skipLimit(10)
            .skip(DataIntegrityViolationException.class)
            .build();
}
組件3:ItemReader(數(shù)據(jù)搬運工)

圖片圖片

  • 典型實現(xiàn):
// 讀取CSV文件示例
@Bean
public FlatFileItemReader<User> csvReader(){
    returnnew FlatFileItemReaderBuilder<User>()
            .name("userReader")
            .resource(new FileSystemResource("data/users.csv"))
            .delimited().delimiter(",")
            .names("id", "name", "email")
            .fieldSetMapper(new BeanWrapperFieldSetMapper<User>() {{
                setTargetType(User.class);
            }})
            .linesToSkip(1) // 跳過標題行
            .build();
}
組件4:ItemWriter(數(shù)據(jù)收納師)

圖片圖片

  • 復(fù)合寫入示例:
@Bean
public CompositeItemWriter<User> compositeWriter(){
    returnnew CompositeItemWriterBuilder<User>()
            .delegates(dbWriter(), logWriter(), mqWriter())
            .build();
}

// 數(shù)據(jù)庫寫入組件
private JdbcBatchItemWriter<User> dbWriter(){
    returnnew JdbcBatchItemWriterBuilder<User>()
            .dataSource(dataSource)
            .sql("INSERT INTO users (name,email) VALUES (:name,:email)")
            .beanMapped()
            .build();
}

2. 架構(gòu)示意圖

圖片圖片

3. 隱藏BOSS:ItemProcessor(數(shù)據(jù)變形金剛)

圖片圖片

  • 典型應(yīng)用:數(shù)據(jù)脫敏處理
publicclassDataMaskProcessorimplementsItemProcessor<User, User> {
    @Override
    public User process(User user){
        // 手機號脫敏
        String phone = user.getPhone();
        user.setPhone(phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"));
        
        // 郵箱轉(zhuǎn)小寫
        user.setEmail(user.getEmail().toLowerCase());
        
        return user;
    }
}

4. 組件生命周期探秘

圖片圖片

三、手把手開發(fā)指南

1. 環(huán)境搭建

<!-- 完整POM配置 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.1.5</version>
</parent>

<dependencies>
    <!-- Batch核心依賴 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-batch</artifactId>
    </dependency>
    
    <!-- 內(nèi)存數(shù)據(jù)庫(生產(chǎn)環(huán)境可更換為MySQL等) -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- Lombok簡化代碼 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
# application.properties
spring.batch.jdbc.initialize-schema=always # 自動創(chuàng)建Batch元數(shù)據(jù)表
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver

2. 第一個批處理任務(wù)

  • 領(lǐng)域模型類:
@Data// Lombok注解
@NoArgsConstructor
@AllArgsConstructor
publicclassUser{
    private String name;
    privateint age;
    private String email;
}
  • 完整Job配置:
@Configuration
@EnableBatchProcessing
publicclassBatchConfig{

    @Autowiredprivate JobBuilderFactory jobBuilderFactory;
    @Autowiredprivate StepBuilderFactory stepBuilderFactory;

    // 定義Job
    @Bean
    public Job importUserJob(){
        return jobBuilderFactory.get("importUserJob")
                .start(csvProcessingStep())
                .build();
    }

    // 定義Step
    @Bean
    public Step csvProcessingStep(){
        return stepBuilderFactory.get("csvProcessing")
                .<User, User>chunk(100) // 每處理100條提交一次
                .reader(userReader())
                .processor(userProcessor())
                .writer(userWriter())
                .build();
    }

    // CSV文件讀取器
    @Bean
    public FlatFileItemReader<User> userReader(){
        returnnew FlatFileItemReaderBuilder<User>()
                .name("userReader")
                .resource(new ClassPathResource("users.csv")) // 文件路徑
                .delimited()
                .delimiter(",")
                .names("name", "age", "email") // 字段映射
                .targetType(User.class)
                .linesToSkip(1) // 跳過標題行
                .build();
    }

    // 數(shù)據(jù)處理(示例:年齡校驗)
    @Bean
    public ItemProcessor<User, User> userProcessor(){
        return user -> {
            if (user.getAge() < 0) {
                thrownew IllegalArgumentException("年齡不能為負數(shù): " + user);
            }
            return user.toBuilder() // 使用Builder模式創(chuàng)建新對象
                    .email(user.getEmail().toLowerCase())
                    .build();
        };
    }

    // 數(shù)據(jù)庫寫入器
    @Bean
    public JdbcBatchItemWriter<User> userWriter(DataSource dataSource){
        returnnew JdbcBatchItemWriterBuilder<User>()
                .dataSource(dataSource)
                .sql("INSERT INTO users (name, age, email) VALUES (:name, :age, :email)")
                .beanMapped()
                .build();
    }
}
  • CSV文件示例(src/main/resources/users.csv):
name,age,email
張三,25,zhangsan@example.com
李四,30,lisi@example.com
王五,-5,wangwu@example.com
  • 啟動類:
@SpringBootApplication
publicclassBatchApplicationimplementsCommandLineRunner{

    @Autowired
    private JobLauncher jobLauncher;

    @Autowired
    private Job importUserJob;

    publicstaticvoidmain(String[] args){
        SpringApplication.run(BatchApplication.class, args);
    }

    @Override
    publicvoidrun(String... args)throws Exception {
        JobParameters params = new JobParametersBuilder()
                .addLong("startAt", System.currentTimeMillis())
                .toJobParameters();
        jobLauncher.run(importUserJob, params);
    }
}

3. 執(zhí)行流程可視化

圖片圖片

4. 運行效果驗證

  • 控制臺輸出:
2023-10-01 10:00:00 INFO  o.s.b.c.l.support.SimpleJobLauncher - Job: [SimpleJob: [name=importUserJob]] launched
2023-10-01 10:00:05 INFO  o.s.batch.core.job.SimpleStepHandler - Executing step: [csvProcessing]
2023-10-01 10:00:15 ERROR o.s.batch.core.step.AbstractStep - Encountered an error executing step csvProcessing
org.springframework.batch.item.validator.ValidationException: 年齡不能為負數(shù): User(name=王五, age=-5, email=wangwu@example.com)
  • 數(shù)據(jù)庫結(jié)果:
SELECT * FROMusers;

圖片圖片

5. 調(diào)試技巧

  • 查看元數(shù)據(jù):
SELECT * FROM BATCH_JOB_INSTANCE;
SELECT * FROM BATCH_STEP_EXECUTION;
  • 重試失敗任務(wù):
// 在Job配置中添加容錯機制
@Bean
public Step csvProcessingStep(){
    return stepBuilderFactory.get("csvProcessing")
            .<User, User>chunk(100)
            .reader(userReader())
            .processor(userProcessor())
            .writer(userWriter())
            .faultTolerant()
            .skipLimit(3) // 最多跳過3條錯誤
            .skip(IllegalArgumentException.class)
            .build();
}
  • 日志監(jiān)控配置:
logging.level.org.springframework.batch=DEBUG
logging.level.org.hibernate.SQL=WARN

四、實戰(zhàn)案例:銀行交易對賬

1. 場景需求增強說明

核心流程:

圖片圖片

技術(shù)挑戰(zhàn):

  • 雙數(shù)據(jù)源讀取(文件+數(shù)據(jù)庫)
  • 千萬級數(shù)據(jù)高效比對
  • 差異記錄快速入庫
  • 分布式環(huán)境運行

2. 完整架構(gòu)設(shè)計

圖片圖片

3. 領(lǐng)域模型定義

@Data
@AllArgsConstructor
@NoArgsConstructor
publicclassTransaction{
    // 公共字段
    private String transactionId;
    private LocalDateTime tradeTime;
    private BigDecimal amount;
    
    // 銀行端數(shù)據(jù)
    private String bankSerialNo;
    private BigDecimal bankAmount;
    
    // 內(nèi)部系統(tǒng)數(shù)據(jù)
    private String internalOrderNo;
    private BigDecimal systemAmount;
    
    // 對賬結(jié)果
    private ReconStatus status;
    private String discrepancyType;
}

publicenum ReconStatus {
    MATCHED,       // 數(shù)據(jù)一致
    AMOUNT_DIFF,   // 金額不一致
    STATUS_DIFF,    // 狀態(tài)不一致
    ONLY_IN_BANK,   // 銀行單邊賬
    ONLY_IN_SYSTEM  // 系統(tǒng)單邊賬
}

4. 完整Job配置

@Configuration
@EnableBatchProcessing
publicclassBankReconJobConfig{

    // 主Job定義
    @Bean
    public Job bankReconciliationJob(Step downloadStep, Step reconStep, Step reportStep){
        return jobBuilderFactory.get("bankReconciliationJob")
                .start(downloadStep)
                .next(reconStep)
                .next(reportStep)
                .build();
    }

    // 文件下載Step
    @Bean
    public Step downloadStep(){
        return stepBuilderFactory.get("downloadStep")
                .tasklet((contribution, chunkContext) -> {
                    // 實現(xiàn)SFTP下載邏輯
                    sftpService.download("/bank/recon/20231001.csv");
                    return RepeatStatus.FINISHED;
                })
                .build();
    }

    // 核心對賬Step
    @Bean
    public Step reconStep(){
        return stepBuilderFactory.get("reconStep")
                .<Transaction, Transaction>chunk(1000)
                .reader(compositeReader())
                .processor(compositeProcessor())
                .writer(compositeWriter())
                .faultTolerant()
                .skipLimit(100)
                .skip(DataIntegrityViolationException.class)
                .retryLimit(3)
                .retry(DeadlockLoserDataAccessException.class)
                .build();
    }

    // 組合數(shù)據(jù)讀取器
    @Bean
    public CompositeItemReader<Transaction> compositeReader(){
        returnnew CompositeItemReaderBuilder<Transaction>()
                .delegates(bankFileReader(), internalDbReader())
                .build();
    }

    // 銀行文件讀取器
    @Bean
    public FlatFileItemReader<Transaction> bankFileReader(){
        returnnew FlatFileItemReaderBuilder<Transaction>()
                .name("bankFileReader")
                .resource(new FileSystemResource("recon/20231001.csv"))
                .delimited()
                .names("transactionId","tradeTime","amount","bankSerialNo")
                .fieldSetMapper(fieldSet -> {
                    Transaction t = new Transaction();
                    t.setTransactionId(fieldSet.readString("transactionId"));
                    t.setBankSerialNo(fieldSet.readString("bankSerialNo"));
                    t.setBankAmount(fieldSet.readBigDecimal("amount"));
                    return t;
                })
                .build();
    }

    // 內(nèi)部數(shù)據(jù)庫讀取器
    @Bean
    public JdbcCursorItemReader<Transaction> internalDbReader(){
        returnnew JdbcCursorItemReaderBuilder<Transaction>()
                .name("internalDbReader")
                .dataSource(internalDataSource)
                .sql("SELECT order_no, amount, status FROM transactions WHERE trade_date = ?")
                .rowMapper((rs, rowNum) -> {
                    Transaction t = new Transaction();
                    t.setInternalOrderNo(rs.getString("order_no"));
                    t.setSystemAmount(rs.getBigDecimal("amount"));
                    return t;
                })
                .preparedStatementSetter(ps -> ps.setString(1, "2023-10-01"))
                .build();
    }

    // 組合處理器
    @Bean
    public CompositeItemProcessor<Transaction> compositeProcessor(){
        List<ItemProcessor<?, ?>> delegates = new ArrayList<>();
        delegates.add(new DataMatchingProcessor());
        delegates.add(new DiscrepancyClassifier());
        returnnew CompositeItemProcessorBuilder<>()
                .delegates(delegates)
                .build();
    }

    // 組合寫入器
    @Bean
    public CompositeItemWriter<Transaction> compositeWriter(){
        returnnew CompositeItemWriterBuilder<Transaction>()
                .delegates(
                    discrepancyDbWriter(),
                    alertMessageWriter()
                )
                .build();
    }
}

5. 核心處理器實現(xiàn)

publicclassDataMatchingProcessorimplementsItemProcessor<Transaction, Transaction> {

    @Override
    public Transaction process(Transaction item){
        // 雙數(shù)據(jù)源匹配邏輯
        if (item.getBankSerialNo() == null) {
            item.setStatus(ReconStatus.ONLY_IN_SYSTEM);
        } elseif (item.getInternalOrderNo() == null) {
            item.setStatus(ReconStatus.ONLY_IN_BANK);
        } else {
            compareAmounts(item);
            compareStatuses(item);
        }
        return item;
    }

    privatevoidcompareAmounts(Transaction t){
        if (t.getBankAmount().compareTo(t.getSystemAmount()) != 0) {
            t.setDiscrepancyType("AMOUNT_MISMATCH");
            t.setStatus(ReconStatus.AMOUNT_DIFF);
            BigDecimal diff = t.getBankAmount().subtract(t.getSystemAmount());
            t.setAmount(diff.abs());
        }
    }

    privatevoidcompareStatuses(Transaction t){
        // 假設(shè)從數(shù)據(jù)庫獲取內(nèi)部狀態(tài)
        String internalStatus = transactionService.getStatus(t.getInternalOrderNo());
        if(!"SETTLED".equals(internalStatus)){
            t.setDiscrepancyType("STATUS_MISMATCH");
            t.setStatus(ReconStatus.STATUS_DIFF);
        }
    }
}

publicclassDiscrepancyClassifierimplementsItemProcessor<Transaction, Transaction> {
    @Override
    public Transaction process(Transaction item){
        if (item.getStatus() != ReconStatus.MATCHED) {
            // 添加告警標記
            item.setAlertLevel(calculateAlertLevel(item));
        }
        return item;
    }

    private AlertLevel calculateAlertLevel(Transaction t){
        if (t.getAmount().compareTo(new BigDecimal("1000000")) > 0) {
            return AlertLevel.CRITICAL;
        }
        return AlertLevel.WARNING;
    }
}

6. 差異報告生成Step

@Bean
public Step reportStep(){
    return stepBuilderFactory.get("reportStep")
            .<Transaction, Transaction>chunk(1000)
            .reader(discrepancyReader())
            .writer(excelWriter())
            .build();
}

@Bean
public JdbcPagingItemReader<Transaction> discrepancyReader(){
    returnnew JdbcPagingItemReaderBuilder<Transaction>()
            .name("discrepancyReader")
            .dataSource(reconDataSource)
            .selectClause("SELECT *")
            .fromClause("FROM discrepancy_records")
            .whereClause("WHERE recon_date = '2023-10-01'")
            .sortKeys(Collections.singletonMap("transaction_id", Order.ASCENDING))
            .rowMapper(new BeanPropertyRowMapper<>(Transaction.class))
            .build();
}

@Bean
public ExcelFileItemWriter<Transaction> excelWriter(){
    returnnew ExcelFileItemWriterBuilder<Transaction>()
            .name("excelWriter")
            .resource(new FileSystemResource("reports/2023-10-01.xlsx"))
            .sheetName("差異報告")
            .headers(new String[]{"交易ID", "差異類型", "金額差異", "告警級別"})
            .fieldExtractor(item -> new Object[]{
                    item.getTransactionId(),
                    item.getDiscrepancyType(),
                    item.getAmount(),
                    item.getAlertLevel()
            })
            .build();
}

7. 性能優(yōu)化配置

# 應(yīng)用配置
spring.batch.job.enabled=false# 禁止自動啟動
spring.batch.initialize-schema=never # 生產(chǎn)環(huán)境禁止自動建表

# 性能調(diào)優(yōu)參數(shù)
spring.batch.chunk.size=2000 # 根據(jù)內(nèi)存調(diào)整
spring.datasource.hikari.maximum-pool-size=20
spring.jpa.properties.hibernate.jdbc.batch_size=1000

8. 執(zhí)行監(jiān)控看板

圖片圖片

五、生產(chǎn)級特性

1. 容錯機制

圖片圖片

  • 完整容錯配置示例:
@Bean
public Step secureStep(){
    return stepBuilderFactory.get("secureStep")
            .<Input, Output>chunk(500)
            .reader(jdbcReader())
            .processor(secureProcessor())
            .writer(restApiWriter())
            .faultTolerant()
            .retryLimit(3)
            .retry(ConnectException.class) // 網(wǎng)絡(luò)問題重試
            .retry(DeadlockLoserDataAccessException.class) // 數(shù)據(jù)庫死鎖重試
            .skipLimit(100)
            .skip(DataIntegrityViolationException.class) // 數(shù)據(jù)問題跳過
            .skip(InvalidDataAccessApiUsageException.class)
            .noRollback(ValidationException.class) // 驗證異常不回滾
            .listener(newErrorLogListener()) // 自定義監(jiān)聽器
            .build();
}

// 錯誤日志監(jiān)聽器示例
publicclassErrorLogListenerimplementsItemProcessListener<Input, Output> {
    @Override
    publicvoidonProcessError(Input item, Exception e){
        ErrorLog log = new ErrorLog();
        log.setItemData(item.toString());
        log.setErrorMsg(e.getMessage());
        errorLogRepository.save(log);
    }
}

2. 性能優(yōu)化策略(千萬級數(shù)據(jù)處理)

策略1:并行Step執(zhí)行

圖片圖片

配置代碼:

@Bean
public Job parallelJob(){
    return jobBuilderFactory.get("parallelJob")
            .start(step1())
            .split(new SimpleAsyncTaskExecutor()) // 啟用異步執(zhí)行器
            .add(step2(), step3())
            .build();
}
策略2:分區(qū)處理(Partitioning)

圖片圖片

  • 分區(qū)處理器實現(xiàn):
@Bean
public Step masterStep(){
    return stepBuilderFactory.get("masterStep")
            .partitioner("slaveStep", partitioner())
            .gridSize(10) // 分區(qū)數(shù)量=CPU核心數(shù)*2
            .taskExecutor(new ThreadPoolTaskExecutor())
            .build();
}

@Bean
public Partitioner partitioner(){
    returnnew Partitioner() {
        @Override
        public Map<String, ExecutionContext> partition(int gridSize){
            Map<String, ExecutionContext> result = new HashMap<>();
            long total = getTotalRecordCount();
            
            long range = total / gridSize;
            for (int i = 0; i < gridSize; i++) {
                ExecutionContext context = new ExecutionContext();
                context.putLong("min", i * range);
                context.putLong("max", (i+1) * range);
                result.put("partition"+i, context);
            }
            return result;
        }
    };
}

// Slave Step配置
@Bean
public Step slaveStep(){
    return stepBuilderFactory.get("slaveStep")
            .<Record, Result>chunk(1000)
            .reader(rangeReader(null, null))
            .processor(processor())
            .writer(writer())
            .build();
}

@StepScope
@Bean
public ItemReader<Record> rangeReader(
        @Value("#{stepExecutionContext[min]}") Long min,
        @Value("#{stepExecutionContext[max]}") Long max) {
    returnnew JdbcCursorItemReaderBuilder<Record>()
            .sql("SELECT * FROM records WHERE id BETWEEN ? AND ?")
            .preparedStatementSetter(ps -> {
                ps.setLong(1, min);
                ps.setLong(2, max);
            })
            // 其他配置...
            .build();
}
  • 策略3:異步ItemProcessor

圖片圖片

  • 異步處理配置:
@Bean
public Step asyncStep(){
    return stepBuilderFactory.get("asyncStep")
            .<Input, Output>chunk(1000)
            .reader(reader())
            .processor(asyncItemProcessor())
            .writer(writer())
            .build();
}

@Bean
public AsyncItemProcessor<Input, Output> asyncItemProcessor(){
    AsyncItemProcessor<Input, Output> asyncProcessor = new AsyncItemProcessor<>();
    asyncProcessor.setDelegate(syncProcessor()); // 同步處理器
    asyncProcessor.setTaskExecutor(new ThreadPoolTaskExecutor());
    return asyncProcessor;
}

@Bean
public AsyncItemWriter<Output> asyncItemWriter(){
    AsyncItemWriter<Output> asyncWriter = new AsyncItemWriter<>();
    asyncWriter.setDelegate(syncWriter()); // 同步寫入器
    return asyncWriter;
}

3. 性能對比測試數(shù)據(jù)

圖片圖片

優(yōu)化技巧:

  • 數(shù)據(jù)庫連接池調(diào)優(yōu):
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
  • JVM參數(shù)優(yōu)化:
java -jar -Xmx4g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 ...
  • 批處理參數(shù)調(diào)整:
.chunk(2000) // 根據(jù)內(nèi)存容量調(diào)整
.setQueryTimeout(60) // 數(shù)據(jù)庫查詢超時

六、監(jiān)控與管理(生產(chǎn)級方案)

1. 監(jiān)控方案升級(Spring Batch Admin替代方案)

圖片圖片

  • 現(xiàn)代監(jiān)控棧配置:
// 添加監(jiān)控依賴
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
// 暴露監(jiān)控端點
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags(){
    return registry -> registry.config().commonTags("application", "batch-service");
}

// 自定義Batch指標
publicclassBatchMetricsListenerextendsJobExecutionListenerSupport{
    privatefinal Counter processedRecords = Counter.builder("batch.records.processed")
            .description("Total processed records")
            .register(Metrics.globalRegistry);
    
    @Override
    publicvoidafterStep(StepExecution stepExecution){
        processedRecords.increment(stepExecution.getWriteCount());
    }
}

2. 元數(shù)據(jù)表結(jié)構(gòu)詳解

圖片圖片

關(guān)鍵表用途:

  • BATCH_JOB_INSTANCE:作業(yè)指紋庫(相同參數(shù)只能存在一個實例)
  • BATCH_JOB_EXECUTION_PARAMS:存儲每次運行的參數(shù)
  • BATCH_STEP_EXECUTION_CONTEXT:保存步驟上下文數(shù)據(jù)(重啟恢復(fù)的關(guān)鍵)

3. 自定義監(jiān)控看板

-- 常用監(jiān)控SQL示例
-- 最近5次作業(yè)執(zhí)行情況
SELECT j.JOB_NAME, e.START_TIME, e.END_TIME, 
       TIMEDIFF(e.END_TIME, e.START_TIME) ASDURATION,
       s.READ_COUNT, s.WRITE_COUNT
FROM BATCH_JOB_EXECUTION e
JOIN BATCH_JOB_INSTANCE j ON e.JOB_INSTANCE_ID = j.JOB_INSTANCE_ID
JOIN BATCH_STEP_EXECUTION s ON e.JOB_EXECUTION_ID = s.JOB_EXECUTION_ID
ORDERBY e.START_TIME DESCLIMIT5;

七、常見問題Q&A(終極指南)

1. 內(nèi)存溢出問題深度解決方案

場景:處理10GB CSV文件時OOM

圖片圖片

  • 優(yōu)化代碼示例:
@Bean
@StepScope
public FlatFileItemReader<LargeRecord> largeFileReader(
        @Value("#{jobParameters['filePath']}") String filePath) {
    
    returnnew FlatFileItemReaderBuilder<LargeRecord>()
            .resource(new FileSystemResource(filePath))
            .lineMapper(new DefaultLineMapper<>() {{
                setLineTokenizer(new DelimitedLineTokenizer());
                setFieldSetMapper(new BeanWrapperFieldSetMapper<>() {{
                    setTargetType(LargeRecord.class);
                }});
            }})
            .linesToSkip(1)
            .strict(false) // 允許文件結(jié)尾空行
            .saveState(false) // 禁用狀態(tài)保存
            .build();
}

// JVM參數(shù)建議
// -XX:+UseG1GC -Xmx2g -XX:MaxGCPauseMillis=200

2. 定時任務(wù)高級配置

  • 多任務(wù)調(diào)度方案:
@Configuration
@EnableScheduling
publicclassScheduleConfig{

    @Autowiredprivate JobLauncher jobLauncher;
    @Autowiredprivate Job reportJob;
    
    // 工作日凌晨執(zhí)行
    @Scheduled(cron = "0 0 2 * * MON-FRI")
    publicvoiddailyJob()throws Exception {
        JobParameters params = new JobParametersBuilder()
                .addString("date", LocalDate.now().toString())
                .toJobParameters();
        jobLauncher.run(reportJob, params);
    }

    // 每小時輪詢
    @Scheduled(fixedRate = 3600000)
    publicvoidpollJob(){
        if(checkNewDataExists()) {
            jobLauncher.run(dataProcessJob, new JobParameters());
        }
    }
    
    // 優(yōu)雅停止示例
    publicvoidstopJob(Long executionId){
        JobExecution execution = jobExplorer.getJobExecution(executionId);
        if(execution.isRunning()) {
            execution.setStatus(BatchStatus.STOPPING);
            jobRepository.update(execution);
        }
    }
}

3. 高頻問題集錦

Q:如何重新運行失敗的任務(wù)?

-- 步驟1:查詢失敗的任務(wù)ID
SELECT * FROM BATCH_JOB_EXECUTION WHERESTATUS = 'FAILED';

-- 步驟2:使用相同參數(shù)重新啟動
JobParameters params = new JobParametersBuilder()
        .addLong("restartId", originalExecutionId)
        .toJobParameters();
jobLauncher.run(job, params);

Q:處理過程中斷電怎么辦?

圖片圖片

Q:如何實現(xiàn)動態(tài)參數(shù)傳遞?

// 命令行啟動方式
java -jar batch.jar --spring.batch.job.name=dataImportJob date=2023-10-01

// 編程式參數(shù)構(gòu)建
publicvoidrunJobWithParams(Map<String, Object> params){
    JobParameters jobParams = new JobParametersBuilder()
            .addString("mode", "forceUpdate")
            .addLong("timestamp", System.currentTimeMillis())
            .toJobParameters();
    jobLauncher.run(importJob, jobParams);
}

4. 性能調(diào)優(yōu)檢查清單

數(shù)據(jù)庫優(yōu)化
  • 添加批量處理索引
  • 配置連接池參數(shù)
  • 啟用JDBC批處理模式
JVM優(yōu)化
-XX:+UseStringDeduplication
-XX:+UseCompressedOops
-XX:MaxMetaspaceSize=512m
Batch配置
spring.batch.jdbc.initialize-schema=never
spring.batch.job.enabled=false
spring.jpa.open-in-view=false


責任編輯:武曉燕 來源: 蘇三說技術(shù)
相關(guān)推薦

2025-07-09 04:00:00

2025-08-04 09:33:42

2022-12-29 08:43:43

項目接口請求

2025-06-05 00:00:00

項目接口合并

2021-03-08 08:02:40

IDEA插件JSON

2021-02-02 15:38:19

Disruptor緩存Java

2025-04-29 08:00:36

2022-02-23 11:47:57

CharlesFiddler抓包

2021-03-26 15:18:11

代碼工具Mockoon

2025-07-23 09:34:24

2025-09-01 01:25:00

SpringMVC注解

2021-05-31 09:02:55

KPI考核工具公司

2022-01-27 08:12:50

Potplayer播放器

2025-05-09 08:40:42

插件頁面Vite

2025-09-08 09:58:06

2009-06-18 15:40:07

Spring Batc

2020-12-11 11:26:47

Spring批處理重試

2025-03-03 10:04:49

2025-10-10 01:00:00

2022-08-02 20:47:38

Spring框架應(yīng)用程序
點贊
收藏

51CTO技術(shù)棧公眾號

亚洲欧美日韩直播| 精品久久久久久中文字幕| 成人精品视频99在线观看免费| 少妇人妻丰满做爰xxx| 91精品短视频| 色噜噜偷拍精品综合在线| 一区二区三区av在线| www.国产黄色| 亚洲一区二区三区高清| 最近2019年日本中文免费字幕 | 精品九九九九| 中文字幕日本人妻久久久免费 | 欧美高跟鞋交xxxxxhd| 欧美亚一区二区三区| 日本电影久久久| 天涯成人国产亚洲精品一区av| 图片区小说区区亚洲五月| 蜜桃av中文字幕| 美女免费视频一区| 38少妇精品导航| 国产美女福利视频| 激情综合网站| 日韩av网址在线| 爱情岛论坛亚洲自拍| 日韩毛片一区| 日韩欧美福利视频| 亚洲精品蜜桃久久久久久| av大片在线播放| 久久综合久久久久88| 99在线观看视频| 国产乱码精品一区二区| 日韩精品视频网| 国外成人性视频| 欧美黄色一区二区三区| 99精品一区| 在线观看精品国产视频| 中文字幕在线免费看线人| 色悠久久久久综合先锋影音下载| 欧美在线看片a免费观看| 成人午夜免费在线| 色呦呦在线播放| 亚洲视频在线一区| 亚洲电影网站| 国产黄在线播放| 久久日韩粉嫩一区二区三区 | 北条麻妃国产九九九精品小说| 亚洲国产欧美一区二区三区同亚洲| 国内精品国产三级国产aⅴ久| jizzjizz少妇亚洲水多| 在线欧美日韩精品| av免费中文字幕| 男人皇宫亚洲男人2020| 色综合久久综合网| av网址在线观看免费| 一区二区三区短视频| 精品国产户外野外| 免费毛片网站在线观看| 黄色软件视频在线观看| 欧美性极品xxxx娇小| 日韩欧美亚洲天堂| 在线能看的av网址| 精品久久中文字幕| 国产在线观看福利| 一区二区视频免费完整版观看| 欧美大片一区| 亚洲午夜色婷婷在线| 亚洲一级中文字幕| 精品大片一区二区| 在线成人一区二区| 懂色av蜜臀av粉嫩av永久| 天天色综合色| 九九久久久久99精品| 久久久精品视频在线| 悠悠资源网久久精品| 91黑丝高跟在线| 青青视频在线免费观看| 欧美aaa在线| 成人疯狂猛交xxx| 精品久久人妻av中文字幕| 成人综合婷婷国产精品久久免费| 国产欧美一区二区三区不卡高清| 婷婷五月综合激情| 国产欧美日韩在线视频| 成人在线观看www| 国产伦理精品| 欧美亚洲日本一区| 午夜免费视频网站| www.豆豆成人网.com| 亚洲精品v欧美精品v日韩精品| 加勒比一区二区| 国产精品99在线观看| 久久久中精品2020中文| 国产中文字幕视频| 久久国产日韩欧美精品| 国产精品免费看一区二区三区| 日韩av免费观影| 中文字幕视频一区| 国产av人人夜夜澡人人爽麻豆 | 天天影视涩香欲综合网| 手机在线看福利| 婷婷综合国产| 一区二区欧美日韩视频| 欧美成人三级在线观看| 久久久久久穴| 成人午夜电影在线播放| 国产爆初菊在线观看免费视频网站| 亚洲特黄一级片| 5月婷婷6月丁香| 99久久99九九99九九九| 日韩精品丝袜在线| 九九热最新地址| 日日夜夜精品视频天天综合网| 91aaaa| 久热av在线| 亚洲一区二区视频在线观看| 久久久精品麻豆| 美女扒开腿让男人桶爽久久动漫| 日韩综合视频在线观看| 欧美另类一区二区| 国产成人aaaa| 最新欧美日韩亚洲| 欧美xxx性| 亚洲激情视频在线播放| 日韩va亚洲va欧美va清高| 日韩成人一区二区| 久久艹中文字幕| 欧美四级在线| 91精品在线观看入口| 亚洲精品成人av久久| 亚洲一区二区成人| 国内精品视频免费| 午夜伦理大片视频在线观看| 欧美精品色一区二区三区| 91成人在线免费视频| 国产一级一区二区| 国产另类自拍| 美洲精品一卡2卡三卡4卡四卡| 成人av网站在线观看| 激情深爱一区二区| 成人疯狂猛交xxx| h视频在线免费| 色偷偷久久人人79超碰人人澡| 中文字幕乱码一区| 亚洲小说欧美另类婷婷| 国产精品免费一区二区三区| 性欧美猛交videos| 日韩欧美激情在线| 免费无码毛片一区二区app| 激情五月婷婷综合网| 亚洲自拍的二区三区| 久久精品资源| 久久在线免费视频| 国产ts人妖调教重口男| 亚洲自拍偷拍麻豆| 在线精品视频播放| 99在线|亚洲一区二区| 精品国产一区二区三区麻豆小说 | 国产69精品99久久久久久宅男| www.com在线观看| 亚洲国产视频在线| 娇妻高潮浓精白浆xxⅹ| 性欧美hd调教| 国产日韩欧美三级| 国产免费一区二区三区在线观看 | 国产一区二区网站| 亚洲欧美怡红院| 日日夜夜精品视频免费观看| 女人天堂亚洲aⅴ在线观看| 成人av资源| 一区二区电影免费观看| 在线视频免费一区二区| 国产精品嫩草影院桃色| 一区二区在线免费观看| 亚洲av成人精品一区二区三区| 国产日韩精品视频一区二区三区 | 国产小视频在线| 欧美亚洲国产bt| 日韩欧美综合视频| 99久久精品免费| 精品久久久噜噜噜噜久久图片| 国产精品一在线观看| 国产精品一区二区三区免费视频 | 欧美天天综合| 91九色视频在线观看| 7777kkk亚洲综合欧美网站| 亚洲欧美日韩国产成人| 一级黄色片免费| 亚洲成人激情综合网| 无码一区二区三区在线| 国产一区二区电影| 国产极品尤物在线| 日本一本不卡| 国产日韩精品推荐| 成人精品三级| 久久久久久久电影一区| 国产精品久久久久一区二区国产| 欧美大片免费久久精品三p| 天堂网中文字幕| 一区2区3区在线看| 国产aⅴ激情无码久久久无码| 国产乱对白刺激视频不卡| 大j8黑人w巨大888a片| 国产精品videosex性欧美| 精品在线观看一区二区| 成人黄色91| 国产精品69久久| 日韩深夜视频| 欧美精品在线免费播放| 成人高清免费观看mv| 精品国产一区二区三区久久久蜜月| 中文字幕人妻色偷偷久久| 午夜a成v人精品| 一区二区在线观看免费视频| 国产片一区二区| 日本黄色动态图| 国产一区二区不卡在线| 在线免费视频a| 国产精品久久久久9999高清| wwwjizzjizzcom| 久久国产电影| 欧美日韩精品久久| 欧美精品密入口播放| 亚洲最大成人免费视频| 国产精品成人国产| 国产高清视频一区三区| 桃色av一区二区| 欧美日本中文字幕| 麻豆传媒视频在线| 在线观看精品自拍私拍| 九色视频在线播放| 日韩精品一二三四区| 人人妻人人澡人人爽久久av| 日韩一区二区三区免费观看| 国产又粗又长视频| 色综合网站在线| 手机看片久久久| 精品久久中文字幕久久av| 久久影院一区二区| 一区二区三区在线免费视频| 免费成人深夜夜行网站| 国产精品久久久久久一区二区三区 | 中文日韩在线视频| 久青青在线观看视频国产| 亚洲欧美激情在线视频| 三级在线播放| 亚洲欧美国产日韩中文字幕| 人妻无码中文字幕| 亚洲国产成人久久综合| 蜜桃视频在线观看www| 亚洲国产成人一区| 天堂а√在线8种子蜜桃视频| 亚洲精品国产综合久久| 亚洲区小说区图片区| 精品偷拍各种wc美女嘘嘘| 无码国产精品一区二区免费16| 亚洲黄色片网站| 亚洲AV第二区国产精品| 亚洲老司机av| 国产精品麻豆一区二区三区| www国产91| 大地资源网3页在线观看| 欧美国产乱视频| 日韩av影片| 国产精品嫩草影院久久久| 国产精品99| 97超碰人人看人人| 久久悠悠精品综合网| 欧美精品在线一区| 日韩黄色大片| 青青草视频国产| 欧美亚洲三区| 羞羞的视频在线| 国产精品白丝av| www.免费av| 欧美国产日本韩| 日本精品在线免费观看| 亚洲国产毛片aaaaa无费看| av黄色在线看| 欧美久久一二区| 免费观看黄色一级视频| 亚洲社区在线观看| 欧美成人三区| 国内精品视频在线| 欧美成人精品三级网站| 97影院在线午夜| 九九久久电影| 日韩精品福利片午夜免费观看| 亚洲在线成人| 久久精品国产露脸对白| www.久久久久久久久| 懂色av粉嫩av浪潮av| 亚洲一区二三区| 亚洲天堂2021av| 精品国产自在久精品国产| 精品电影在线| 久久免费国产视频| 免费视频观看成人| 狠狠色噜噜狠狠狠狠色吗综合| 久久精品国产68国产精品亚洲| 无码人妻少妇伦在线电影| 日日夜夜一区二区| 99精品一区二区三区无码吞精| 国产欧美日韩激情| 国产香蕉视频在线| 欧美高清视频在线高清观看mv色露露十八 | 大黄网站在线观看| 国产精品久久久久秋霞鲁丝| 北条麻妃一区二区三区在线| 在线电影看在线一区二区三区| 久久国产精品毛片| 国产人妻精品午夜福利免费| 国产精品日产欧美久久久久| 成人毛片18女人毛片| 日韩色视频在线观看| 97电影在线| 日本在线精品视频| 菁菁伊人国产精品| 最新视频 - x88av| 美国一区二区三区在线播放 | 日本精品一区二区三区在线| 视频二区欧美| 中文字幕欧美日韩一区二区| 久久精品亚洲| 亚洲av永久无码精品| 亚洲色图视频免费播放| 自拍偷拍色综合| 亚洲小视频在线观看| 成人av三级| 国内精品久久久久久久果冻传媒| 国产精品草草| 91蝌蚪视频在线| 亚洲免费视频成人| 在线观看免费黄色小视频| 国产性猛交xxxx免费看久久| 亚洲伊人av| 久久精品国产精品国产精品污| 激情欧美一区| 在线xxxxx| 亚洲国产美国国产综合一区二区| 亚洲av永久纯肉无码精品动漫| 美女av一区二区| 麻豆国产一区二区三区四区| 大桥未久一区二区三区| 国内不卡的二区三区中文字幕| 成年人二级毛片| 欧美一区二区视频在线观看2020 | 污污的视频网站在线观看| 久久久久久久久久久免费精品 | 精品国产乱码久久久久久久久| 99视频免费在线观看| 99精品国产高清一区二区| 国模吧视频一区| yjizz视频| 精品露脸国产偷人在视频| 免费黄网站在线观看| 国产精品黄色av| 日韩免费特黄一二三区| 日本三级黄色网址| 日韩美女视频一区二区| 国产福利第一视频| 欧美激情性做爰免费视频| 超碰97久久国产精品牛牛| 久久久久久久中文| 国产拍欧美日韩视频二区| 91影院在线播放| 欧美丰满老妇厨房牲生活| 黄色成人美女网站| 少妇性l交大片| 中文字幕中文乱码欧美一区二区| 国产男男gay体育生白袜| 欧美激情一区二区三区在线视频观看| 国产精品对白| caoporn超碰97| 亚洲美女一区二区三区| 熟妇人妻av无码一区二区三区| 人人澡人人澡人人看欧美| 色呦哟—国产精品| 最好看的中文字幕| 精品高清一区二区三区| 国产系列电影在线播放网址| 亚洲japanese制服美女| 国产亚洲精品bv在线观看| 公肉吊粗大爽色翁浪妇视频| 欧美另类videos死尸| a国产在线视频| 亚洲美女搞黄| av男人天堂一区| 亚洲一区二区三区高清视频| 欧美精品久久久久久久免费观看| 欧美人与物videos另类xxxxx| www.99r| 天天操天天干天天综合网| 日韩成人影视| 狠狠色狠狠色综合人人| 狠狠色综合播放一区二区| 五月激情六月丁香| 美女视频久久黄| 精品国产一级毛片| 久久精品aⅴ无码中文字字幕重口| 欧美午夜片在线观看|