Spring-boot 定时任务的实现
概述
通过自动化操作和任务的规模化管理,提高效率、可靠性和工作质量。减少手动操作,避免疏忽和错误,节省时间和人力资源的投入。
优点
简单易用:使用注解驱动的方式,简化定时任务的配置和管理。通过添加
@Scheduled
注解将普通方法标记为定时任务,而不需要手动编写定时任务调度的代码。内置任务调度器:Springboot 内置了轻量级的热舞调度器,可以方便的执行定时任务。提供了线程池、任务管理和并发处理等功能,可以高效的管理和执行任务。
灵活的配置选项:@Scheduled 注解支持各种配置选项
cron 表达式
fixedDelay
fixedRate
通过这些参数可以灵活的定义任务的触发时间和频率。
实现方法
1. 导入依赖
Spring boot 自带
2. 在启动类中开启定时任务
使用 @EnableScheduling 注解开启定时任务
@SpringBootApplication
@MapperScan("cn.littleprince.mapper") // 扫描 Mapper 包
@EnableScheduling // 开启定时任务
public class BackendApplication {
public static void main(String[] args) {
SpringApplication.run(BackendApplication.class, args);
}
}
3. 在 job 包下定义要执行的定时任务
package cn.littleprince.job;
import cn.littleprince.mapper.UserMapper;
import cn.littleprince.model.domain.User;
import cn.littleprince.service.UserService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 缓存预热任务
*
* @author 349807102
*/
@Component
@Slf4j
public class PreCacheJob {
@Resource
private RedisTemplate redisTemplate;
@Resource
private UserService userService;
/**
* 重点用户
*/
private List<Long> mainUserList = Arrays.asList(1L);
/**
* 每天执行 -> 每天 0:4 执行一次 // https://cron.qqe2.com/
* 为了确保定时任务同一时间只能有一个服务器执行
* 1. 将定时任务和主程序拆开:只在一个服务器上部署定时任务【成本很大】
* 2. 根据 ip 触发定时任务【成本低,但是 ip 可能不是固定的】
* 3. 动态配置:配置可以很方便的更新【存储在数据库中、redis 中】 *******
* 4. 配置中心【Nacos、Spring Cloud Config】
* 5. 分布式锁【此处实践】:只有抢到锁的服务器才能执行业务逻辑
* 缺点: 增加成本【但不多】
* 有点:不用手动配置
* <p>
* 锁:同一时间只有某些线程能访问到资源
* <p>
* 抢锁的机制:先来的人将数据改成自己的标识,后来的人发现标识已经存在,就抢锁失败,继续等待
* redis 实现【快】
* set key value NX EXIPX time: set if no exists
* 用完要释放锁
* 锁一定要加过期时间
* <p>
* mysql 数据库 行级锁 select for update
* Zookeeper 实现
*/
@Scheduled(cron = "0 4 0 * * *")
public void doCacheRecommendUser() {
for (Long userId : mainUserList) {
// 无缓存 -> 查询数据库
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
Page<User> userPage = userService.page(new Page<>(1, 20), queryWrapper);
// 创建 redis 操作对象
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
// 如果已经有了缓存 -> 直接读
// 获取当前用户
String redisKey = String.format("yupao:user:recommend:%s", userId);
// 写缓存
try {
// 设置缓存过期时间
valueOperations.set(redisKey, userPage, 30000, TimeUnit.MILLISECONDS);
} catch (Exception e) {
log.error("redis set key error: ", e);
}
}
}
}
cron表达式
时间范围
Cron 表达式由空格分隔的6个或7个字段组成,每个字段代表一个时间单位。字段的取值范围如下:
参数 | 是否必须 | 范围 | 支持的特殊字符 |
---|---|---|---|
秒(Seconds) | 是 | 0 ~ 59 | *,- / |
分(Minutes) | 是 | 0 ~ 59 | *,- / |
时(Hours) | 是 | 0 ~ 23 | *,- / |
日(DayofMonth) | 是 | 1 ~ 31 | *,- /? L W |
月(Month) | 是 | 1 ~ 12 | *,- / |
星期(DayofWeek) | 是 | 1 ~ 7 | *,- /? L # |
年(Year) | 否 | 1970 ~ 2099 | - * / |
特殊字符
-
(星号):通配符,表示每个时间单位。例如 * * * * ?表示每一秒执行一次。
-
?(问号):该字符用于在日期和星期字段中指定"不指定值"。一般情况下,日期和星期两个字段只能指定一个值,另一个字段要使用问号进行占位。例如0 0 12 ? * MON-FRI表示在每个星期一至星期五的12点执行。
-
-(连字符):范围符号,用于指定一个时间单位的取值范围。例如10-30 * * * * ?表示在每分钟的第10秒到第30秒之间执行。
-
,(逗号):枚举符号,用于指定多个时间单位取值的列表。例如1,3,5 * * * * ?表示在每小时的第1分、第3分和第5分执行。
-
/(斜杠):间隔符号,用于指定时间单位的间隔。例如0/5 * * * * ?表示每5秒执行一次。
-
L(最后的):特殊字符,用于指定某个时间单位的最后一个值。例如0 0 12 L * ?表示在每个月的最后一天的12点执行。
-
W(工作日):特殊字符,用于指定距离给定日期最近的工作日(周一至周五)。例如15W * * * ?表示在每个月的第15个工作日当天执行。
-
#(井号):特殊字符,用于指定某个月份的第几个星期几。例如0 0 12 ? * 6#3表示在每个月的第三个星期五的12点执行。