执行定时任务需要给一个时间计划,这个时间计划可以用 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() { ListseckillSessionEntities = seckillSessionService.getLates3DaySession(); return R.ok().setData(seckillSessionEntities); }
- com.achang.achangmall.coupon.service.impl.SeckillSessionServiceImpl
@Service("seckillSessionService") public class SeckillSessionServiceImpl extends ServiceImplimplements 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 ListrelationSkus; }
- com.achang.achangmall.seckill.service.impl.SeckillServiceImpl
明天继续!!!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)