Day437.秒杀服务 -谷粒商城

Day437.秒杀服务 -谷粒商城,第1张

Day437.秒杀服务 -谷粒商城 秒杀服务 一、定时任务-Quartz Cron表达式

执行定时任务需要给一个时间计划,这个时间计划可以用 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

②示例 表达式意义0 0 12 ?每天的12点执行一次0 15 10 ?每天的10:15执行0 15 10 ?每天的10:15执行0 15 10 ? *每天的10:15执行0 15 10 ? 20052005年每天10:15执行都执行0 14 * ?每天14点启动,每隔1分钟执行一次0 0/5 14 ?每天14点启动,每隔5分钟执行一次0 0/5 14,18 ?每天的14点、18点启动,每隔5分钟执行一次0 0-5 14 ?14:00-14:05执行,每分钟执行一次0 10,44 14 ? 3 WED3月的每个星期三的14:10 、14:44启动这个任务,每分钟执行一次0 15 10 ? * MON-FRI每月的周一到周五,10:15执行0 15 10 ? * 6L每月的最后一个周五,10:15执行0 15 10 ? * 6#3每月的第3个周5,10:15执行
二、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、解决定时任务阻塞的方法
  1. 可以使用异步任务的方式,CompletableFuture.runAsync(),自己提交到线程池

  2. 修改配置文件,spring.task.scheduling.pool.size=5

  3. 让定时任务异步执行

①异步任务
  1. 首先在类上面标注@EnableAsync,开启异步任务功能
  2. 然后在方法上标注@Async,执行异步任务
  3. 这个异步任务不是只能搭配定时任务,它可以替代CompletableFuture
  4. 自动配置类参考 TaskExecutionAutoConfiguration
  5. 它在配置文件中的线程池属性是: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 List relationSkus;
  • 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
明天继续!!!

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/4968470.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-14
下一篇 2022-11-13

发表评论

登录后才能评论

评论列表(0条)

保存