商场秒杀服务实战

秒杀服务

后台添加秒杀商品

在这里插入图片描述

添加场次,添加场次中的商品

在这里插入图片描述
单独创建一个秒杀服务,这样不影响正常交易的进行
在这里插入图片描述
在这里插入图片描述

定时任务&Cron表达式

在线Cron表达式生成器

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

SpringBoot整合定时任务与异步任务

/**
 * 定时任务
 * 1、 @EnableScheduling 开启定时任务
 * 2、 开启一个定时任务
 *
 * 异步任务
 *   1、@EnableAsync 类上注解,开启定时任务
 *   2、@Async 开启一个定时任务
 */
@Slf4j
@Component
@EnableAsync
@EnableScheduling
public class HelloSchedule {

    /**
     * 1、 Spring中 6位组成,不允许第7位第年
     * 2、 在周几第位置,1-7代表周一到周日: 也可以写英文缩写
     * 3、 定时任务不应该阻塞。默认是阻塞的
     *      1)可以让业务运行以异步的方式,自己提交到线程池
     *          CompletableFuture.runAsync(()->{
     *              xxxService.hello();
     *          },executor);
     *
     *      2)支持任务定时任务线程池:设置 spring.task.scheduling.pool.size=5  不好使
     *
     *      3)让定时任务异步执行
     *          异步任务:
     *              1、@EnableAsync 类上注解,开启定时任务
     *              2、@Async 开启一个定时任务
     *          解决:使用异步+定时任务来完成定时任务不阻塞的功能
     */
    @Async
    @Scheduled(cron = "* * * ? * *")
    public void hello() throws InterruptedException {
        log.info("hello");
        Thread.sleep(5000);
    }
}

时间日期处理

定时任务这边,远程调用优惠服务,计算最近三天内需要上架的秒杀商品

秒杀商品上架

将远程查询返回的需要上架的商品存入redis

    //活动信息
    private final String SESSION_CACHE_PREFIX = "seckill:sessions:";
    //sku信息
    private final String SKUKILL_CACHE_PREFIX = "seckill:skus";//多了个:
    //高并发 信号量
    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:";//+随机码

设置随机码
抢商品的必须带上随机码才能减到库存

设置商品的 分布式 redis 信号量,商品可以秒杀的数量作为信号,信号量值为商品的库存
1、引入redisson依赖,配置参数

@Configuration
public class MyRedissonConfig {
    /**
     * 所有对redis的使用通过RedissonClient
     * @return
     * @throws IOException
     */
    @Bean(destroyMethod="shutdown")
    public RedissonClient redisson() throws IOException {
        Config config = new Config();
        //1、创建单节点模式
        config.useSingleServer().setAddress("redis://192.168.33.10:6379");

        //多节点模式
//        config.useClusterServers()
//                .addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");

        //2、创建实例
        return Redisson.create(config);
    }
}

幂等性保证

定时任务分布式下的问题
在这里插入图片描述
在这里插入图片描述

查询秒杀商品

查询时判断 当前时间商品秒杀是否开始,没有开始就设置随机码为空,不发放随机码

秒杀系统设计

在这里插入图片描述

在这里插入图片描述
网关层拦截恶意请求

我们做到了
1、秒杀单独抽取出来了
2、链接加密,只有秒杀时间到了,才能获取到随机码
3、库存预热,定时任务上架上要秒杀到商品到redis,商品的库存是redis的信号量控制,原子减量
4、动静分离

登陆检查

前端限流,前端判断当前商品是否是秒杀商品,若是秒杀商品,先判断登陆。

后端判断登陆,引入 session 依赖

<!--        整合Spring Session完成session共享问题 微服务自治,就不放在common里了-->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

session配置类

@EnableRedisHttpSession
@Configuration
public class GulimallSessionConfig {
    /**
     * 自定义session作用域:整个网站
     * 使用一样的session配置,能保证全网站共享一样的session
     */
    @Bean
    public CookieSerializer cookieSerializer() {

        DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer();

        defaultCookieSerializer.setDomainName("gulimall.com");
        defaultCookieSerializer.setCookieName("GULISESSION");

        return defaultCookieSerializer;
    }
    /**
     * 序列化机制
     */
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer(){

        return new GenericJackson2JsonRedisSerializer();
    }

}

拦截器

@Component
public class LoginUserInterceptor implements HandlerInterceptor {

    public static ThreadLocal<MemberRespVo> loginUser = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String uri = request.getRequestURI();
        boolean match = new AntPathMatcher().match("/kill", uri);

        if (match ) {
            return true;
        }

        HttpSession session = request.getSession();
        MemberRespVo attribute = (MemberRespVo) session.getAttribute(AuthServerConstant.LOGIN_USER);
        if (attribute != null) {

            loginUser.set(attribute);
            return true;
        } else {

            request.getSession().setAttribute("msg", "请先登录");
            response.sendRedirect("http://auth.gulimall.com/login.html");
            return false;
        }
    }

}

将拦截器注册

@Configuration
public class SecKillWebConfiguration implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginUserInterceptor()).addPathPatterns("/**");
    }
}

秒杀流程

选择这套流程,用到消息队列,做到流量削峰
在这里插入图片描述
先判断这人是否参与过秒杀,通过设置redis key 占位,占位设置成功说明没有参与过,反之

//4、验证这个人是否买过,幂等性;如果只要秒杀成功,就去占位,好让。  userId_SessionId_skuId
String redisKey = respVo.getId() + "_" + redis.getPromotionSessionId() + "_" + redis.getSkuId();
//自动过期
Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);

前置判断成功后,获取redis信号量

RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
//semaphore.acquire(num);//这是个阻塞方式获取信号量,获取不到就不走了,不好
//semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);//尝试100毫妙获取信号量
semaphore.tryAcquire(num);//尝试获取一次,获取不到就 返回false

获取到

秒杀效果完成

MQ的设计
在这里插入图片描述
1、引入 RabbitMQ依赖,添加配置和转换器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
spring.rabbitmq.virtual-host=/
spring.rabbitmq.host=192.168.33.10
@Configuration
public class MyRabbitConfig {
    @Autowired
    RabbitTemplate rabbitTemplate;
    
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
}

2、创建队列,并指定 交换机的绑定

@Bean
public Queue orderSeckillOrderQueue() {

    //是否持久化的 是否排他的(大家都能监听,谁抢到算谁的) 是否自动删除
    return new Queue("order.seckill.order.queue", true, false, false);
}

@Bean
public Binding orderSeckillOrderQueueBinding() {

    return new Binding("order.seckill.order.queue",
            Binding.DestinationType.QUEUE,
            "order-event-exchange",
            "order.seckill.order",
            null);
}

3、创建监听类

@RabbitListener(queues = "order.seckill.order.queue")
@Component
public class OrderSecKillListener {

    @Autowired
    OrderService orderService;

    @RabbitHandler
    public void listener(SecKillOrderTo secKillOrder, Channel channel, Message message) throws IOException {
        System.out.println("准备创建秒杀单单详细信息"+secKillOrder.getOrderSn());
        try {
            orderService.createSecKillOrder(secKillOrder);
            
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }catch (IOException e){
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
        }

    }
}

监听到秒杀订单队列的队列,然后创建待支付订单

测试完成秒杀下单 时间为80毫秒

页面美化

end

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值