使用场景
在集群环境的时候,一个服务可能存在多个实例,定时任务@Scheduled会被多次执行,
在一些业务场景的时候我们需要它只执行 一次,这时候可以使用@SchedulerLock加锁,来达到场景需求
引入依赖
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>2.2.1</version>
</dependency>
注:ShedLock需要使用外部存储,我这里使用的是mysql8.0
创建数据库表
CREATE TABLE `shedlock` (
`NAME` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`lock_until` timestamp(3) NULL DEFAULT NULL,
`locked_at` timestamp(3) NULL DEFAULT NULL,
`locked_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='锁 shedlock';
配置类
package com.sqx.config;
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import net.javacrumbs.shedlock.spring.ScheduledLockConfiguration;
import net.javacrumbs.shedlock.spring.ScheduledLockConfigurationBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.time.Duration;
/**
* @description:TODO
* @author: qiao
* @create: 2024/4/15 9:41
*/
@Component
public class ShedLockConfig {
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(dataSource);
}
@Bean
public ScheduledLockConfiguration scheduledLockConfiguration(LockProvider lockProvider) {
return ScheduledLockConfigurationBuilder.withLockProvider(lockProvider)
.withPoolSize(10)
.withDefaultLockAtMostFor(Duration.ofMinutes(10))
.build();
}
}
定时任务
package com.sqx.modules.timedtask;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.sqx.common.eum.BusinessEnum;
import com.sqx.common.utils.DateUtils;
import com.sqx.modules.app.entity.UserEntity;
import com.sqx.modules.app.entity.UserFollow;
import com.sqx.modules.app.entity.UserMoney;
import com.sqx.modules.app.entity.UserMoneyDetails;
import com.sqx.modules.app.service.UserFollowService;
import com.sqx.modules.app.service.UserMoneyDetailsService;
import com.sqx.modules.app.service.UserMoneyService;
import com.sqx.modules.app.service.UserService;
import com.sqx.modules.autoTask.entity.AutoTask;
import com.sqx.modules.autoTask.service.AutoTaskService;
import com.sqx.modules.common.service.CommonInfoService;
import com.sqx.modules.externalGroup.entity.GroupInfo;
import com.sqx.modules.externalGroup.service.GroupInfoService;
import com.sqx.modules.sys.entity.SysUserEntity;
import com.sqx.modules.talk.entity.dto.TalkDTO;
import com.sqx.modules.talk.service.CommentService;
import com.sqx.modules.talk.service.TalkService;
import com.sqx.modules.talk.utils.DateUtil;
import com.sqx.modules.tickets.entity.UserTickets;
import com.sqx.modules.tickets.entity.UserTicketsRecord;
import com.sqx.modules.tickets.eum.SignStrEnum;
import com.sqx.modules.tickets.service.UserTicketsRecordService;
import com.sqx.modules.tickets.service.UserTicketsService;
import com.sqx.modules.utils.baiduAi.BaiDuAiUtils;
import lombok.extern.slf4j.Slf4j;
import net.javacrumbs.shedlock.core.SchedulerLock;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
@Component
@Slf4j
public class TimedTask {
@Autowired
private UserService userService;
@Resource
private UserTicketsService userTicketsService;
@Resource
private UserMoneyService userMoneyService;
@Resource
private UserMoneyDetailsService userMoneyDetailsService;
@Resource
private CommonInfoService commonInfoService;
@Resource
private GroupInfoService groupInfoService;
@Autowired
private UserTicketsRecordService userTicketsRecordService;
@Resource
private AutoTaskService autoTaskService;
@Resource
private TalkService talkService;
@Resource
private CommentService commentService;
@Resource
private UserFollowService userFollowService;
// @Scheduled(cron = "0/10 * * * * ? ", zone = "Asia/Shanghai") 10秒
/*
* 每10分钟执行一次
* lockAtMostFor 任务口节点锁的最大占用时间
* lockAtLeastFor 任务节点锁的最短占用时间
* */
@Scheduled(cron = "0 0/10 * * * ? ", zone = "Asia/Shanghai")
@SchedulerLock(name = "sendCommentTask", lockAtMostFor = 10000, lockAtLeastFor = 5 * 1000)
public void sendTalkCommont(){
try {
//获取评论任务信息
AutoTask autoTask = autoTaskService.queryByType(BusinessEnum.TASK_TYPE_COMMONET.getCode());
if (ObjectUtil.isNotEmpty(autoTask)){
//获取任务状态 是否为执行状态
Integer executeStatus = autoTask.getExecuteStatus();
if (executeStatus.equals(BusinessEnum.EXECUTE_STATUS_Y.getCode())){
//执行状态则开始查询autoTask.getTimeInterval()分钟前的动态 然后调用百度AI接口进行评论
//获取现在的时间
LocalDateTime now = LocalDateTime.now();
//获取指定时间X分钟前的时间为结束时间
LocalDateTime endTime = now.minusMinutes(autoTask.getTimeInterval());
//结束时间的前X分钟为开始时间
LocalDateTime startTime = endTime.minusMinutes(autoTask.getTimeInterval());
String endTimeStr = endTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
String startTimeStr = startTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
//查询时间内发表的动态
List<Map<String, Object>> talkDTOS = talkService.queryList(startTimeStr, endTimeStr);
if (ObjectUtil.isNotEmpty(talkDTOS)){
for (Map<String, Object> talkDTO : talkDTOS) {
//获取动态id 动态文案内容 动态是否被自动评论
String content = (String) talkDTO.get("content");
Integer talkId = (Integer) talkDTO.get("talkId");
Integer isAutoCommont = (Integer) talkDTO.get("isAutoCommont");
if (ObjectUtil.isNotEmpty(isAutoCommont) && isAutoCommont.equals(0)){
//自动评论为空 状态为未评论 调用百度接口
if (StringUtils.isNotEmpty(content)){
String result = BaiDuAiUtils.getResult(content);
if (ObjectUtil.isNotEmpty(result)){
//保存评论及消息通知
commentService.autoTaskSaveComment(talkId,result);
}
}
}
}
}
}
}
}catch (Exception e){
e.printStackTrace();
log.error("自动评论出错啦~"+e.getMessage());
}
}
}
@SchedulerLock(name = “sendCommentTask”, lockAtMostFor = 10000, lockAtLeastFor = 5 * 1000)
name 任务名称
lockAtMostFor 任务口节点锁的最大占用时间
lockAtLeastFor 任务节点锁的最短占用时间
***注意:lockAtLeastFor设置的时间大于cron表达式中的时间时,执行时间会按照lockAtLeastFor设置的来执行***
启动类增加注解
@EnableScheduling 开启定时任务
@EnableSchedulerLock(defaultLockAtMostFor = “PT50S”) 开启定时任务锁,
defaultLockAtMostFor——属性表示默认最大锁持有时间,PT50S表示50秒
package com.sqx;
import lombok.extern.slf4j.Slf4j;
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@Slf4j
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT50S")
@SpringBootApplication
public class SqxApplication {
public static void main(String[] args) {
SpringApplication.run(SqxApplication.class, args);
}
}