
执行定时任务需要给一个时间计划,这个时间计划可以用 Cron 表达式来编写
官方文档 Cron 表达式是一个字符串,是用空格分割的六到七个属性。
语法:秒 分 时 日 月 周 年(可忽略年,Spring 不支持年)
定时任务只能精确到秒
Seconds:0-59,举例:0 就是整秒执行,1 就是在第1秒的时候执行
Day of week:值可以写 1-7,也可以写 SUN-SAT,1 就是周日,7 就是周六
①特殊字符,:枚举
- (cron="7,9,23 * * * * ?"):代表任意时刻的7,9,23秒启动这个任务;
-:范围
- (cron="7-20 * * * * ?"):任意时刻的 7-20 秒之间,每秒启动一次
*:任意
- 指定位置的任意时刻都可以
/:步长
- (cron="7/5 * * * * ?"):第 7 秒启动,每 5 秒一次;
- (cron="*/5 * * * * ?"):任意时间启动之后,每 5 秒一次;
?`:(出现在日和周几的位置)为了防止日和周冲突,如果1个精确了,另一个就得写`?
- (cron="* * * 1 * ?"):每月的 1 号,启动这个任务,如果两个都写精确值的话,可能会导致冲突,所以其中一个要使用?
L:(出现在日和周的位置)”,last:最后一个
- (cron="* * * ? * 3L"):每月的最后一个周二
W: Work Day:工作日
- (cron="* * * W * ?"):每个月的工作日触发
- (cron="* * * LW * ?"):每个月的最后一个工作日触发
#: 第几个
- (cron="* * * ? * 5#2"):5 代表周 4,#2 代表第 2 个,合起来就是每个月的第 2 个周 4
②示例
二、Spring Boot整合定时任务 1、与Quarts的区别
自动配置类参考 TaskSchedulingAutoConfiguration
@Slf4j
@Component
@EnableScheduling // 开启定时功能
public class HelloSchedule {
@Scheduled(cron = "*/5 * * ? * 1") // 开启定时任务
public void hello(){
log.info("hello");
}
}
2、定时任务默认是阻塞的
@Scheduled(cron = "* * * ? * 1")
public void block() throws InterruptedException {
log.info("hello......");
Thread.sleep(3000);
}
3、解决定时任务阻塞的方法
-
可以使用异步任务的方式,CompletableFuture.runAsync(),自己提交到线程池
-
修改配置文件,spring.task.scheduling.pool.size=5
-
让定时任务异步执行
- 首先在类上面标注@EnableAsync,开启异步任务功能
- 然后在方法上标注@Async,执行异步任务
- 这个异步任务不是只能搭配定时任务,它可以替代CompletableFuture
- 自动配置类参考 TaskExecutionAutoConfiguration
- 它在配置文件中的线程池属性是:spring.task.execution.pool.xxx
使用异步+定时任务来实现定时任务不阻塞
三、定时上架秒杀商品 1、简介
每天凌晨3点,上架最近3天所需要秒杀的商品
因为这个时间段服务器压力较小,并且比较空闲,
上架最近3天的商品,可以给用户一个预告的功能,让用户提前知道哪个商品什么时间将要开启秒杀
2、随机码
为了防止有用户在得知秒杀请求时,发送大量请求对商品进行秒杀,我们采取了随机码的方式,即每个要参加秒杀的商品,都有一个随机码,只有通过正常提交请求的流程才可以获取,否则谁都无法得知随机码是多少,避免了恶意秒杀
3、商品的分布式信号量
信号量保存了当前秒杀商品的库存信息
我们的库存秒杀不应该是实时去数据库扣库存,因为几百万请求进来的时候,如果都去扣,那会直接把数据库压垮。
所以现在秒杀最大的问题就是,如何应对这些高并发的流量
首先,这么大的流量进到服务器的话,肯定有一些流量是无效的,比如秒杀不成功,假设我们现在就一百个商品要被秒杀,哪怕放进来一百万请求,最终也只有一百个请求,能成功的去数据库扣掉库存。
所以我们可以提前在 redis 里边设置一个信号量,这个信号量可以认为是一个自增量,假设这个信号量叫 count,它专门用来计数,它的初始值是 100,每进来一个请求,我们就让这个值减一,如果有用户想要秒杀这个商品,我们先去 redis 里边获取一个信号量,也就是给这一百的库存减一,然后这个值就变成九十九,如果能减成功了,那就把这个请求放行,然后再做后边的处理数据库。如果不能减,那就不用进行后续的 *** 作了,我们只会阻塞很短的时间,就会释放这个请求,我们只有每一个请求都能很快的释放,能很快的做完,我们才能拥有处理大并发的能力。
这块有一个注意点,由于每一个请求进来减这个信号量的值,就是当前商品的库存信息,只有请求里携带了我们给秒杀商品设计的随机码,才可以来减信号量,如果不带随机码的话,直接减信号量的话,就会出现问题,可能秒杀还没开始,有一些恶意请求,就把信号量就减了了。
所以上面说的随机码是一种保护机制。
4、代码
- 创建秒杀项目模块
-
引入依赖
-
com.achang.achangmall.coupon.controller.SeckillSessionController
@GetMapping(value = "/Lates3DaySession")
public R getLates3DaySession() {
List seckillSessionEntities = seckillSessionService.getLates3DaySession();
return R.ok().setData(seckillSessionEntities);
}
- com.achang.achangmall.coupon.service.impl.SeckillSessionServiceImpl
@Service("seckillSessionService")
public class SeckillSessionServiceImpl extends ServiceImpl implements SeckillSessionService {
@Autowired
private SeckillSkuRelationService seckillSkuRelationService;
@Override
public List getLates3DaySession() {
//计算最近三天
//查出这三天参与秒杀活动的商品
List list = this.baseMapper.selectList(new QueryWrapper()
.between("start_time", startTime(), endTime()));
if (list != null && list.size() > 0) {
List collect = list.stream().map(session -> {
Long id = session.getId();
//查出sms_seckill_sku_relation表中关联的skuId
List relationSkus = seckillSkuRelationService.list(new QueryWrapper()
.eq("promotion_session_id", id));
session.setRelationSkus(relationSkus);
return session;
}).collect(Collectors.toList());
return collect;
}
return null;
}
private String startTime() {
LocalDate now = LocalDate.now();
LocalTime min = LocalTime.MIN;
LocalDateTime start = LocalDateTime.of(now, min);
//格式化时间
String startFormat = start.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return startFormat;
}
private String endTime() {
LocalDate now = LocalDate.now();
LocalDate plus = now.plusDays(2);
LocalTime max = LocalTime.MAX;
LocalDateTime end = LocalDateTime.of(plus, max);
//格式化时间
String endFormat = end.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return endFormat;
}
}
- com.achang.achangmall.coupon.entity.SeckillSessionEntity
@TableField(exist = false) private ListrelationSkus;
- com.achang.achangmall.seckill.vo.SeckillSkuVo
@Data
public class SeckillSkuVo {
private Long id;
private Long promotionId;
private Long promotionSessionId;
private Long skuId;
private BigDecimal seckillPrice;
private Integer seckillCount;
private Integer seckillLimit;
private Integer seckillSort;
}
- com.achang.achangmall.seckill.config.ScheduledConfig
@Configuration
@EnableAsync
@EnableScheduling
public class ScheduledConfig {}
- com.achang.achangmall.seckill.feign.CouponFeignService
@FeignClient("achangmall-coupon")
public interface CouponFeignService {
@GetMapping(value = "/coupon/seckillsession/Lates3DaySession")
R getLates3DaySession();
}
- com.achang.achangmall.seckill.to.SeckillSkuRedisTo
@Data
public class SeckillSkuRedisTo {
private Long promotionId;
private Long promotionSessionId;
private Long skuId;
private BigDecimal seckillPrice;
private Integer seckillCount;
private Integer seckillLimit;
private Integer seckillSort;
//sku的详细信息
private SkuInfoVo skuInfo;
//当前商品秒杀的开始时间
private Long startTime;
//当前商品秒杀的结束时间
private Long endTime;
//当前商品秒杀的随机码
private String randomCode;
}
- com.achang.achangmall.seckill.vo.SeckillSessionWithSkusVo
@Data
public class SeckillSessionWithSkusVo {
private Long id;
private String name;
private Date startTime;
private Date endTime;
private Integer status;
private Date createTime;
private List relationSkus;
}
- com.achang.achangmall.seckill.service.impl.SeckillServiceImpl
明天继续!!!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)