spring自带的定时任务功能@EnableScheduling
1 demo
package
com.test.domi.config;
import
org.springframework.beans.factory.annotation.Configurable;
import
org.springframework.scheduling.annotation.EnableScheduling;
import
org.springframework.scheduling.annotation.Scheduled;
import
org.springframework.stereotype.Component;
import
java.text.SimpleDateFormat;
import
java.util.Date;
@Component
@Configurable
@EnableScheduling
public
class
ScheduledTasks {
//每30秒执行一次
@Scheduled
(fixedRate =
1000
*
30
)
public
void
reportCurrentTime(){
System.out.println (
"Scheduling Tasks Examples: The time is now "
+ dateFormat ().format (
new
Date ()));
}
//在固定时间执行
@Scheduled
(cron =
"0 */1 * * * * "
)
public
void
reportCurrentByCron(){
System.out.println (
"Scheduling Tasks Examples By Cron: The time is now "
+ dateFormat ().format (
new
Date()));
}
private
SimpleDateFormat dateFormat(){
return
new
SimpleDateFormat (
"HH:mm:ss"
);
}
Scheduling Tasks Examples: The time is now 11:55:54 Scheduling Tasks Examples By Cron: The time is now 11:56:00 Scheduling Tasks Examples: The time is now 11:56:24 Scheduling Tasks Examples: The time is now 11:56:54 Scheduling Tasks Examples By Cron: The time is now 11:57:00
2 详解
http://tramp.cincout.cn/2017/08/18/spring-task-2017-08-18-spring-boot-enablescheduling-analysis/
cron表达式:https://www.zhyd.me/article/43
1.cron是设置定时执行的表达式,如 0 0/5 * * * ?每隔五分钟执行一次
2.zone表示执行时间的时区
3.fixedDelay 和fixedDelayString 一个固定延迟时间执行,上个任务完成后,延迟多久执行
4.fixedRate 和fixedRateString一个固定频率执行,上个任务开始后多长时间后开始执行
5.initialDelay 和initialDelayString表示一个初始延迟时间,第一次被调用前延迟的时间
3 总结常见问题
a: 单线程任务丢失,转为异步线程池
默认的 ConcurrentTaskScheduler 计划执行器采用Executors.newSingleThreadScheduledExecutor() 实现单线程的执行器。因此,对同一个调度任务的执行总是同一个线程。如果任务的执行时间超过该任务的下一次执行时间,则会出现任务丢失,跳过该段时间的任务。上述问题有以下解决办法:
采用异步的方式执行调度任务,配置 Spring 的 @EnableAsync,在执行定时任务的方法上标注 @Async配置任务执行池,线程池大小 n 的数量为 单个任务执行所需时间 / 任务执行的间隔时间。如下:
//每30秒执行一次
@Async
(
"taskExecutor"
)
@Scheduled
(fixedRate =
1000
*
3
)
public
void
reportCurrentTime(){
System.out.println (
"线程"
+ Thread.currentThread().getName() +
"开始执行定时任务===&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&7&&&====》"
+
new
SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss"
).format(
new
Date()));
long
start = System.currentTimeMillis();
Future<Boolean> isOk1;
Future<Boolean> isOk2;
。 。。。。。。。。。省略。。。。。。。
b: 关于分布式情况下,重复执行的问题(两种方案)
1:可以使用redis的分布式锁保证spring schedule集群只执行一次。 redis分布式锁是通过setnx命令实现的。该命令的作用是,当往redis中存入一个值时,会先判断该值对应的key是否存在,如果存在则返回0,如果不存在,则将该值存入redis并返回1。(但是在分布式跨时区部署的时候,依然无法避免重复执行)
@Component
@Configuration
@EnableScheduling
public
class
AutoConvertTask {
private
static
final
Logger logger = LoggerFactory.getLogger(AutoConvertTask.
class
);
@Autowired
private
RedisTemplate redisTemplate;
private
static
final
String LOCK =
"task-job-lock"
;
private
static
final
String KEY =
"tasklock"
;
@Scheduled
(cron =
"0 0 0 * * ? "
)
public
void
autoConvertJob() {
boolean
lock =
false
;
try
{
lock = redisTemplate.opsForValue().setIfAbsent(KEY, LOCK);
logger.info(
"是否获取到锁:"
+ lock);
if
(lock) {
List<GameHistory> historyList = historyService.findTenDaysAgoUntreated();
for
(GameHistory history : historyList) {
update(history);
}
}
else
{
logger.info(
"没有获取到锁,不执行任务!"
);
return
;
}
}
finally
{
if
(lock) {
redisTemplate.delete(KEY);
logger.info(
"任务结束,释放锁!"
);
}
else
{
logger.info(
"没有获取到锁,无需释放锁!"
);
}
}
}
}
2:可以通过使用shedlock将spring schedule上锁。详细见:https://segmentfault.com/a/1190000011975027
c: 服务器宕机之后,丢失的任务如何补偿?
可以将每次的任务执行时间缓在redis里,下次执行任务的时候都取出该时间,判断是否为上一个周期,如果不是,可以计算出中间丢失的周期数,然后做响应的补偿操作。如果怕redis宕机,可以将“执行时间”持久化到表中。