秒杀知识点

业务流程图:

秒杀知识点_第1张图片秒杀知识点_第2张图片

定时任务:

秒杀商品定时上架。

1、cron 表达式

Cron Trigger Tutorial

秒杀知识点_第3张图片

特殊字符:

:枚举; (cron="7,9,23 * * * * ?"):任意时刻的 7,9,23 秒启动这个任务;

-:范围: (cron="7-20 * * * * ?"):任意时刻的 7-20 秒之间,每秒启动一次

*:任意; 指定位置的任意时刻都可以

/:步长; (cron="7/5 * * * * ?"):第 7 秒启动,每 5 秒一次;

(cron="*/5 * * * * ?"):任意秒启动,每 5 秒一次;

?:(出现在日和周几的位置):为了防止日和周冲突,在周和日上如果要写通配符使 用? (cron="* * * 1 * ?"):每月的 1 号,启动这个任务;

L:(出现在日和周的位置)”, last:最后一个 (cron="* * * ? * 3L"):每月的最后一个周二

W: Work Day:工作日 (cron="* * * W * ?"):每个月的工作日触发 (cron="* * * LW * ?"):每个月的最后一个工作日触发

#:第几个 (cron="* * * ? * 5#2"):每个月的第 2 个周

/**
 * 定时任务
 *      1、@EnableScheduling 开启定时任务
 *      2、@Scheduled开启一个定时任务
 *
 * 异步任务
 *      1、@EnableAsync:开启异步任务
 *      2、@Async:给希望异步执行的方法标注
 */

@Slf4j
@Component
@EnableAsync
@EnableScheduling
public class HelloScheduled {

    /**
     * 1、在Spring中表达式是6位组成,不允许第七位的年份
     * 2、在周几的的位置,1-7代表周一到周日
     * 3、定时任务不该阻塞。默认是阻塞的
     *      1)、可以让业务以异步的方式,自己提交到线程池
     *              CompletableFuture.runAsync(() -> {
     *         },execute);
     *
     *      2)、支持定时任务线程池;设置 TaskSchedulingProperties
     *        spring.task.scheduling.pool.size: 5
     *
     *      3)、让定时任务异步执行
     *          异步任务
     *
     *      解决:使用异步任务 + 定时任务来完成定时任务不阻塞的功能
     *
     */
    @Async
    @Scheduled(cron = "*/5 * * ? * 4")
    public void hello() {
        log.info("hello...");
        try { TimeUnit.SECONDS.sleep(3); } 
        catch (InterruptedException e) {             
            e.printStackTrace(); }
    
    }

}

将商品信息保存到redis中

保存商品的秒杀场次、sku基本信息、活动信息等;

为了避免恶意秒杀,添加一个商品秒杀随机码。

引入分布式信号量;(限流)

保证幂等性(Redisson)

避免重复上架

秒杀知识点_第4张图片

秒杀知识点_第5张图片

秒杀合法性校验:

校验登陆合法性,时间合法性,购物数量,幂等性(使用占位,如果秒杀成功,则占位)

userid_session_skuid

并解决自动过期问题,

redisTemplate.setIfAbsent()

tryAcquire 预减库存

下单,mq发送消息;

public String kill(String killId, String key, Integer num) throws InterruptedException {

        long s1 = System.currentTimeMillis();
        //获取当前用户的信息
        MemberResponseVo user = LoginUserInterceptor.loginUser.get();

        //1、获取当前秒杀商品的详细信息从Redis中获取
        BoundHashOperations hashOps = redisTemplate.boundHashOps(SECKILL_CHARE_PREFIX);
        String skuInfoValue = hashOps.get(killId);
        if (StringUtils.isEmpty(skuInfoValue)) {
            return null;
        }
        //(合法性效验)
        SeckillSkuRedisTo redisTo = JSON.parseObject(skuInfoValue, SeckillSkuRedisTo.class);
        Long startTime = redisTo.getStartTime();
        Long endTime = redisTo.getEndTime();
        long currentTime = System.currentTimeMillis();
        //判断当前这个秒杀请求是否在活动时间区间内(效验时间的合法性)
        if (currentTime >= startTime && currentTime <= endTime) {

            //2、效验随机码和商品id
            String randomCode = redisTo.getRandomCode();
            String skuId = redisTo.getPromotionSessionId() + "-" +redisTo.getSkuId();
            if (randomCode.equals(key) && killId.equals(skuId)) {
                //3、验证购物数量是否合理和库存量是否充足
                Integer seckillLimit = redisTo.getSeckillLimit();

                //获取信号量
                String seckillCount = redisTemplate.opsForValue().get(SKU_STOCK_SEMAPHORE + randomCode);
                Integer count = Integer.valueOf(seckillCount);
                //判断信号量是否大于0,并且买的数量不能超过库存
                if (count > 0 && num <= seckillLimit && count > num ) {
                    //4、验证这个人是否已经买过了(幂等性处理),如果秒杀成功,就去占位。userId-sessionId-skuId
                    //SETNX 原子性处理
                    String redisKey = user.getId() + "-" + skuId;
                    //设置自动过期(活动结束时间-当前时间)
                    Long ttl = endTime - currentTime;
                    Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
                    if (aBoolean) {
                        //占位成功说明从来没有买过,分布式锁(获取信号量-1)
                        RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
                        //TODO 秒杀成功,快速下单
                        boolean semaphoreCount = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
                        //保证Redis中还有商品库存
                        if (semaphoreCount) {
                            //创建订单号和订单信息发送给MQ
                            // 秒杀成功 快速下单 发送消息到 MQ 整个操作时间在 10ms 左右
                            String timeId = IdWorker.getTimeId();
                            SeckillOrderTo orderTo = new SeckillOrderTo();
                            orderTo.setOrderSn(timeId);
                            orderTo.setMemberId(user.getId());
                            orderTo.setNum(num);
                            orderTo.setPromotionSessionId(redisTo.getPromotionSessionId());
                            orderTo.setSkuId(redisTo.getSkuId());
                            orderTo.setSeckillPrice(redisTo.getSeckillPrice());
                            rabbitTemplate.convertAndSend("order-event-exchange","order.seckill.order",orderTo);
                            long s2 = System.currentTimeMillis();
                            log.info("耗时..." + (s2 - s1));
                            return timeId;
                        }
                    }
                }
            }
        }
        long s3 = System.currentTimeMillis();
        log.info("耗时..." + (s3 - s1));
        return null;
    }

你可能感兴趣的:(数据库,服务器,运维)