Spring中的调度:@Schedule和@EnableScheduling导致多次调用方法

Scheduling in Spring: @Schedule and @EnableScheduling lead to multiple calls of method

我有一个服务必须 运行 一个作业来从另一个服务获取和刷新它的数据。作业必须在启动时 运行 并且每对 hours/days。我正在调查预定作业的行为,根据日志(见下文),它似乎被连续调用了两次。

@Service
public class ServiceImpl implements ServiceInterface {

@Autowired
private FetchService fetchService;

private int timesCalled = 0;
private Data data;

@PostConstruct
private void initialize() {
    data = fetchService.getAndUpdate();
}

@Scheduled(cron = "* */5 * * * *")
private void refresh() {
    LOG.info(appContext.getId());
    LOG.info("This object: " + System.identityHashCode(this));
    LOG.info("Times called: " +  timesCalled);
    timesCalled++;
    data = fetchService.getAndUpdate();
}
...

这里还有每 5 分钟调用一次刷新方法的日志,可以看出它被调用了两次:

2020-07-02 17:30:00.006  INFO 30416 --- [   scheduling-1] c.d.p.d.service.ServiceImpl  : org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6db9cae5
2020-07-02 17:30:00.006  INFO 30416 --- [   scheduling-1] c.d.p.d.serice.ServiceImpl  : This object: 357813323
2020-07-02 17:30:00.006  INFO 30416 --- [   scheduling-1] c.d.p.d.service.ServiceImpl  : Times called: 1
....
2020-07-02 17:30:32.001  INFO 30416 --- [   scheduling-1] c.d.p.d.service.ServiceImpl  : org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@6db9cae5
2020-07-02 17:30:32.001  INFO 30416 --- [   scheduling-1] c.d.p.d.service.ServiceImpl  : This object: 357813323
2020-07-02 17:30:32.001  INFO 30416 --- [   scheduling-1] c.d.p.d.service.ServiceImpl  : Times called: 2

我没有 web.xml,我只使用默认设置。我在根级别使用了@EnableScheduling 标记:

@EnableScheduling
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

我调查了类似的问题,但找不到任何可以帮助我找到此错误来源的内容。感谢您的帮助:)

您描述的行为是正常且有意为之的。

如您所写,您的 cron 表达式是 * */5 * * * *,这意味着,根据 the Spring guide,它会 运行 每 5 分钟的每一秒:

A cron-like expression, extending the usual UN*X definition to include triggers on the second, minute, hour, day of month, month, and day of week.

For example, {@code "0 * * * * MON-FRI"} means once per minute on weekdays (at the top of the minute - the 0th second). The fields read from left to right are interpreted as follows.

  • second
  • minute
  • hour
  • day of month
  • month
  • day of week

可以通过您的代码和 cron 表达式轻松重现:

@Scheduled(cron = "* */5 * * * *")
private void refresh() {
    log.info("Times called: " +  timesCalled);
    timesCalled++;
}

并观察其行为:

14:20:13.001  INFO 8980 --- [   sch-1] com.example.demo.Sched   : Times called: 13
14:20:14.001  INFO 8980 --- [   sch-1] com.example.demo.Sched   : Times called: 14
14:20:15.003  INFO 8980 --- [   sch-1] com.example.demo.Sched   : Times called: 15
14:20:59.002  INFO 8980 --- [   sch-1] com.example.demo.Sched   : Times called: 59
14:25:00.000  INFO 8980 --- [   sch-1] com.example.demo.Sched   : Times called: 60
14:25:01.000  INFO 8980 --- [   sch-1] com.example.demo.Sched   : Times called: 61
14:25:02.001  INFO 8980 --- [   sch-1] com.example.demo.Sched   : Times called: 62
14:25:03.002  INFO 8980 --- [   sch-1] com.example.demo.Sched   : Times called: 63

如您所见,每秒 运行 秒,直到分钟结束。比等到 14:25 和 运行 每秒再次等待。

但为什么它在问题案例中只 运行 2 次?

这很简单:data = fetchService.getAndUpdate(); 大约需要 30 秒,并且因为您只有一个线程用于调度,所以它必须等待最后一次迭代完成,直到它可以重新开始。


解决方案

要解决这个问题,只需将第一个通配符替换为 0:

0 */5 * * * *

您的工作将 运行 每 5 分钟一次。