运维与监控
本章定位:掌握 Spring Batch 的运维工具——元数据表查询、JobOperator 管理 API 和常见故障处理。
定义与作用
Spring Batch 将所有作业执行记录持久化在元数据表中。通过这些表和 JobOperator / JobExplorer API,可以实现作业状态查询、停止、重启和审计。
飞翔科技运维黄俪的日常:
"凌晨 2 点定时 Job 失败了,我不用翻日志。直接查 BATCH_JOB_EXECUTION 表看 ExitMessage,定位到是第 3 个 Step 数据库连接超时。重启后从断点继续。"
核心原理
元数据表关系
完整示例
常用运维 SQL
-- 查看最近 5 次失败的 Job
SELECT je.JOB_EXECUTION_ID, ji.JOB_NAME,
je.START_TIME, je.END_TIME, je.EXIT_CODE, je.EXIT_MESSAGE
FROM BATCH_JOB_EXECUTION je
JOIN BATCH_JOB_INSTANCE ji ON ji.JOB_INSTANCE_ID = je.JOB_INSTANCE_ID
WHERE je.STATUS = 'FAILED'
ORDER BY je.START_TIME DESC
LIMIT 5;
-- 查看某次执行中各 Step 的读写统计
SELECT STEP_NAME, STATUS, READ_COUNT, WRITE_COUNT,
SKIP_COUNT, COMMIT_COUNT, ROLLBACK_COUNT
FROM BATCH_STEP_EXECUTION
WHERE JOB_EXECUTION_ID = 12345
ORDER BY STEP_EXECUTION_ID;
-- 统计每日 Job 执行次数
SELECT DATE(START_TIME) as batch_date, JOB_NAME, COUNT(*) as runs,
SUM(CASE WHEN STATUS='COMPLETED' THEN 1 ELSE 0 END) as success,
SUM(CASE WHEN STATUS='FAILED' THEN 1 ELSE 0 END) as failed
FROM BATCH_JOB_EXECUTION je
JOIN BATCH_JOB_INSTANCE ji ON ji.JOB_INSTANCE_ID = je.JOB_INSTANCE_ID
WHERE START_TIME >= DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY DATE(START_TIME), JOB_NAME;
JobOperator API 运维操作
@RestController
@RequestMapping("/ops/batch")
public class BatchOpsController {
private final JobOperator jobOperator;
private final JobExplorer jobExplorer;
public BatchOpsController(JobOperator jobOperator, JobExplorer jobExplorer) {
this.jobOperator = jobOperator;
this.jobExplorer = jobExplorer;
}
// 查询所有正在运行的 Job
@GetMapping("/running")
public Set<Long> getRunningJobs() {
return jobOperator.getRunningExecutions("anyJob");
}
// 停止作业
@PostMapping("/stop/{executionId}")
public String stop(@PathVariable long executionId) {
jobOperator.stop(executionId);
return "Stop signal sent to execution #" + executionId;
}
// 重启失败作业
@PostMapping("/restart/{executionId}")
public Long restart(@PathVariable long executionId) {
return jobOperator.restart(executionId);
}
// 作业摘要
@GetMapping("/summary/{executionId}")
public String getSummary(@PathVariable long executionId) {
return jobOperator.getSummary(executionId);
}
}
应用健康检查
@Component
public class BatchHealthIndicator implements HealthIndicator {
private final JobExplorer jobExplorer;
public BatchHealthIndicator(JobExplorer jobExplorer) {
this.jobExplorer = jobExplorer;
}
@Override
public Health health() {
// 检查最近 1 小时是否有失败的 Job
LocalDateTime oneHourAgo = LocalDateTime.now().minusHours(1);
List<JobInstance> failedJobs = jobExplorer.findJobInstancesByJobName(
"orderProcessingJob", 0, 100).stream()
.flatMap(ji -> jobExplorer.getJobExecutions(ji).stream())
.filter(job -> job.getStatus() == BatchStatus.FAILED
&& job.getEndTime() != null
&& job.getEndTime().isAfter(Date.from(
oneHourAgo.atZone(ZoneId.systemDefault()).toInstant())))
.map(JobExecution::getJobInstance)
.toList();
return failedJobs.isEmpty()
? Health.up().withDetail("recentFailures", 0).build()
: Health.down().withDetail("recentFailures", failedJobs.size()).build();
}
}
易错场景与避坑
反例一:不清理历史数据导致元数据表膨胀
-- 100 个 Job × 每天 24 次 × 365 天 × 5 年 = 4,380,000 行
-- BATCH_STEP_EXECUTION 表可能到千万行 → 查询缓慢
正确做法:定期清理(保留 30-90 天):
DELETE FROM BATCH_STEP_EXECUTION_CONTEXT
WHERE STEP_EXECUTION_ID IN (
SELECT STEP_EXECUTION_ID FROM BATCH_STEP_EXECUTION
WHERE JOB_EXECUTION_ID IN (
SELECT JOB_EXECUTION_ID FROM BATCH_JOB_EXECUTION
WHERE END_TIME < DATE_SUB(NOW(), INTERVAL 90 DAY)
)
);
反例二:Web 应用中忘记关掉自动执行
// ❌ 应用启动时自动跑批处理 → 启动极慢、可能数据冲突
// spring.batch.job.enabled 默认 true
面试高频考点
Q1:如何实现 Job 执行失败后的告警?
两种方式:①监听器方式——
JobExecutionListener.afterJob()中检查STATUS=FAILED,发送邮件/钉钉/企微。②轮询方式——定时查询BATCH_JOB_EXECUTION表,查找近期失败记录并告警。
Q2:stop() 和 abandon() 的区别?
stop()发送停止信号(设置STOPPING状态),Job 在当前 Chunk 完成后优雅停止,状态变为STOPPED,可以重启。abandon()标记为ABANDONED,放弃该次执行,不可重启。
全文完 | Spring Batch 独立教程 14 章