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 分钟一次。
我有一个服务必须 运行 一个作业来从另一个服务获取和刷新它的数据。作业必须在启动时 运行 并且每对 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 分钟一次。