【项目】秒杀实战

本文详细介绍了如何设计并实现一个高并发的秒杀系统,包括数据库设计、秒杀流程、库存预减、使用RabbitMQ进行消息队列处理以及幂等性保障。通过缓存、数据库和消息队列的结合,确保了秒杀过程的稳定性和效率。
摘要由CSDN通过智能技术生成

秒杀的实现

一、数据库
秒杀商品表:
在这里插入图片描述
秒杀时间表:
在这里插入图片描述
二、流程:
1、添加秒杀商品:先查看是否有该时间段,没有就添加一个时间段,并把商品信息添加到秒杀商品表里。
2、获取当前时间及往后7个时间段, 总共8个。
3、根据时间段id获取商品
4、根据秒杀id获取商品详细信息

1 从缓存中查询
2. 缓存没有,再从数据库中获取,添加到缓存
3. 将库存单独存入一个key中
4. 返回商品
5、秒杀开始
1 判断是否卖完
2. 判断是否秒杀开始,虽然前端在没开始之前不会打开抢购按钮,但为了防止路径暴露被刷
3. 判断是否已经秒杀到了,避免一个账户秒杀多个商品
4. 预减库存:从缓存中减去库存、减少访问数据库次数
5. 判断是否已经卖完,设置内存标记
6. 使用RabbitMQ异步传输
6、 使用RabbitMQ接收信息进行处理
1 判断是否消费过了
2. 将消费信息存入redis
3. 添加订单,将用户信息存入redis中的已消费集合中。
4. 如果发生错误,删除消费信息、将消息放入队列尾部,尝试再次消费
三、代码

秒杀代码

 @Transactional
    public void seckillProduct(String seckillId, Integer userId) {
        if (localOverMap.get(seckillId) != null && localOverMap.get(seckillId)) {
            // 售空
            throw new XmException(ExceptionEnum.GET_SECKILL_IS_OVER);
        }
        // 判断秒杀是否开始, 防止路径暴露被刷
        Map m = redisTemplate.opsForHash().entries(RedisKey.SECKILL_PRODUCT + seckillId);
        if (!m.isEmpty()) {
            SeckillProductVo seckillProductVo = null;
            try {
                seckillProductVo = BeanUtil.map2bean(m, SeckillProductVo.class);
            } catch (Exception e) {
                e.printStackTrace();
            }
            // 秒杀开始时间
            Long startTime = seckillProductVo.getStartTime();

            if (startTime >System.currentTimeMillis()) {
                throw new XmException(ExceptionEnum.GET_SECKILL_IS_NOT_START);
            }
        }

        // 判断是否已经秒杀到了,避免一个账户秒杀多个商品
        List<String> list = redisTemplate.opsForList().range(RedisKey.SECKILL_PRODUCT_USER_LIST + seckillId, 0, -1);
        if (list.contains(String.valueOf(userId))) {
            throw new XmException(ExceptionEnum.GET_SECKILL_IS_REUSE);
        }

        // 预减库存:从缓存中减去库存
        // 利用redis中的方法,减去库存,返回值为减去1之后的值
        if (stringRedisTemplate.opsForValue().decrement(RedisKey.SECKILL_PRODUCT_STOCK + seckillId) < 0) {
            // 设置内存标记
            localOverMap.put(seckillId, true);
            // 秒杀完成,库存为空
            throw new XmException(ExceptionEnum.GET_SECKILL_IS_OVER);
        }
        // 使用RabbitMQ异步传输
        mqSend(seckillId, userId);

    }

发送消息

  private void mqSend(String seckillId, Integer userId) {
        HashMap<String, String> map = new HashMap<>();
        map.put("seckillId", seckillId);
        map.put("userId", userId.toString());
        // 设置ID,保证消息队列幂等性
        CorrelationData correlationData = new CorrelationData();
        correlationData.setId(seckillId + ":" + userId);
        try {
            rabbitTemplate.convertAndSend("seckill_order", map, correlationData);
        } catch (AmqpException e) {
            // 发送消息失败
            e.printStackTrace();
            stringRedisTemplate.opsForValue().increment(RedisKey.SECKILL_PRODUCT_STOCK + seckillId);
        }
    }

接收消息

 @RabbitListener(queues = "seckill_order")
    public void insertOrder(Map map, Channel channel, Message message){
        String seckillId = (String) map.get("seckillId");
        String userId = (String) map.get("userId");
        // 查看id,保证幂等性
        String correlationId = seckillId + ":" + userId;
        if (stringRedisTemplate.hasKey(RedisKey.SECKILL_RABBITMQ_ID + correlationId)) {
            System.out.println(stringRedisTemplate.hasKey(RedisKey.SECKILL_RABBITMQ_ID + correlationId));
            // redis中存在,表明此条消息已消费,请勿重复消费
            return;
        }

        // 存入redis,因为只需要判断是否存在,因此value为多少无所谓
        stringRedisTemplate.opsForValue().set(RedisKey.SECKILL_RABBITMQ_ID + correlationId, "1");
        Long seckillEndTime = seckillProductService.getEndTime(seckillId);
        stringRedisTemplate.expire(RedisKey.SECKILL_RABBITMQ_ID + correlationId, seckillEndTime - new Date().getTime(), TimeUnit.SECONDS); // 设置过期时间

        try {
        //添加订单
            orderService.addSeckillOrder(seckillId, userId);
        } catch (Exception e) {
            e.printStackTrace();
            try {
                stringRedisTemplate.delete(RedisKey.SECKILL_RABBITMQ_ID + correlationId);
                // 将该消息放入队列尾部,尝试再次消费
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值