业务背景:正常开发情况下,spring的定时任务只有当前任务线程执行完毕后才会扫描下一次定时任务,即为默认的同步任务。
譬如定时表达式@Scheduled(cron = "0 0/1 * * * ?")
意味当前定时任务理想情况下需要1分钟跑一次,但是当遇到有时间要求的定时任务时,同时又有新的增量任务进来时,则需要启动异步定时任务。
如果采用的是spring自带的Scheduled注解实现的话
第一步在springboot启动类上添加@EnableAsync注解启动异步任务
第二步在实际的定时任务方法上添加@Async()注解
这样的话当当前任务正在执行时,即还未执行完毕时,当下次1分钟的时候也会再次扫描该定时任务。
示例代码:
@SpringBootApplication
@EnableAsync
@EnableFeignClients
@EnableScheduling
public class EpaiMarketApplication {
public static void main(String[] args) {
SpringApplication.run(EpaiMarketApplication.class, args);
}
}
@Component
@Slf4j
public class AutoIncrNewsJob {
@Resource
private NewsService newsService;
@Scheduled(cron = "0 0/1 * * * ?")
private void incrNewsViewCount() {
newsService.incrNewsViewCount();
}
}
@Async()
public void incrNewsViewCount() {
try{
List needIncrViewCountNewsList=baseNewsInfoMapper.selectNeedIncrViewCountList();
if(CollectionUtils.isEmpty(needIncrViewCountNewsList)){
return;
}
//使用parallel并发进程执行
needIncrViewCountNewsList.stream().parallel().forEach(news->{
Boolean flag = redisTemplate.opsForValue().setIfAbsent("news_count_incr_switch_"+news.getNewsId(), "lock",60*30, TimeUnit.SECONDS);
if(flag){
try {
Integer targetViewCount = news.getTargetViewCount();
Integer initMinute = news.getInitMinute();
Integer totalViewCount = news.getTotalViewCount();
Integer diffViewCount=targetViewCount-totalViewCount;
//取出redis值进行+1操作
Integer redisViewCount=0;
if(redisClient.get(NewsConstant.REDIS_COUNT_PREX+ news.getNewsId())!=null){
redisViewCount = Ints.tryParse(redisClient.get(NewsConstant.REDIS_COUNT_PREX + news.getNewsId()).toString());
}
int step=diffViewCount%(initMinute*60)==0?diffViewCount/(initMinute*60):diffViewCount/(initMinute*60)+1;
DecimalFormat dF = new DecimalFormat("0.00");
//(float) successCount / (successCount + failCount)) * 100
double times=Double.valueOf(dF.format((float)step*initMinute*60/diffViewCount==0?1:(float)step*initMinute*60/diffViewCount));
log.info("redis 新增步长:{},资讯id:{},间隔秒数:{}",step,news.getNewsId(),times);
for(int i=1;i<=initMinute*60;i++){
try {
if(redisClient.get(NewsConstant.REDIS_COUNT_PREX+ news.getNewsId())!=null){
redisViewCount = Ints.tryParse(redisClient.get(NewsConstant.REDIS_COUNT_PREX + news.getNewsId()).toString());
}
if(redisViewCount>=targetViewCount){
break;
}
Thread.sleep((long) (1000*times));
log.info("redis 新增步长:{},资讯id:{}",step,news.getNewsId());
redisClient.incr(NewsConstant.REDIS_COUNT_PREX + news.getNewsId(),step);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
baseNewsInfo baseNewsInfo=new baseNewsInfo();
baseNewsInfo.setNewsId(news.getNewsId());
baseNewsInfo.setTotalViewCount(news.getTotalViewCount()+redisViewCount);
baseNewsInfoMapper.updateByPrimaryKeySelective(baseNewsInfo);
}catch(Exception e){
log.error("定时增加资讯初始浏览量异常,错误信息:{},资讯id:{}",e.toString(),news.getNewsId());
}finally {
//释放锁资源
redisTemplate.delete("news_count_incr_switch_"+news.getNewsId());
}
}
});
}catch(Exception e){
log.error("定时增加资讯初始浏览量异常,错误信息:{}",e.toString());
}
}
通过两次注解即可实现增量的任务加入到扫描队列中,同时在定时任务执行过程中要注意索住执行对象。



