1. spring boot 相关配置
<dependency>
            <groupId>org.jobrunr</groupId>
            <artifactId>jobrunr-spring-boot-3-starter</artifactId>
            <version>7.2.0</version>
        </dependency>
org:
  jobrunr:
    background-job-server:
      enabled: true
    dashboard:
      enabled: true
      port: 28000
    jobs:
      default-number-of-retries: 10
      retry-back-off-time-seed: 3
  1. 定制Job的覆盖逻辑:针对特定对象后面触发的job自动覆盖前面的job,重置重试间隔

前提:使用上面的starter可以自动注入Jobrunr的接口类:

private final JobScheduler jobScheduler;
    private final StorageProvider storageProvider;

关键代码如下(分析了JobRunr的源码、数据库后得出的结论):

a. 首先需要为你的job生成合适的id,与目标对象(`targetIdentifier`)唯一对应

UUID uuid = UUID.nameUUIDFromBytes(targetIdentifier.getBytes(StandardCharsets.ISO_8859_1));

b. 关键的处理逻辑:如果存在则更新并enqueue(通过storageProvider.save实现),不存在才新建

try {
                // see requeue source code in  JobRunrApiHandler#requeueJobById
                var staleJob = storageProvider.getJobById(uuid); // 已存在job # https://github.com/jobrunr/jobrunr/discussions/759
                var job = new Job(staleJob.getId(), staleJob.getVersion(),
                        staleJob.getJobDetails(), List.of(new EnqueuedState()), new ConcurrentHashMap<>());
                // org.jobrunr.jobs.states.IllegalJobStateChangeException: A job cannot change state from ENQUEUED to ENQUEUED.
                storageProvider.save(job);
            } catch (JobNotFoundException e) {
                jobScheduler.enqueue(uuid, () -> doYourTask(targetIdentifier)); //
            }

单纯的jobScheduler.enqueue在遇到同样的JobId时不会更新数据库,会导致重试间隔继续增长,同时日志会打印:

Skipped Job with id {} as it already exists
  1. 在免费版中如何注册 JobFilter以监听Job状态变化事件,比如重试全部耗尽最终失败的事件,用于内部通知
/**
 * 参考 https://springbuilders.dev/donatavict/getting-started-with-jobrunr-use-case-example-pt-ii-4hml
 */
public class JobRunrConfig {
    @Component
    @Slf4j
    public static class GlobalTasksFilter implements JobServerFilter {
        @Override
        public void onFailedAfterRetries(Job job) {
            log.error("All retries failed for Job {}", job.getJobName());
        }
    }

    @Component
    @RequiredArgsConstructor
    public static class BackgroundJobServerBeanPostProcessor implements BeanPostProcessor {
        private final GlobalTasksFilter globalTasksFilter;

        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if(bean instanceof BackgroundJobServer backgroundJobServer) {
                backgroundJobServer.setJobFilters(Collections.singletonList(globalTasksFilter));
            }
            return bean;
        }
    }
}