zoukankan      html  css  js  c++  java
  • 基于SpringBoot、Redis和RabbitMq的商品秒杀处理

    一、商品秒杀存在的问题

      1、商品肯能会超卖,因为并发。

      2、数据库承受巨大的压力,每秒大量的访问可能让数据库宕机。

      3、用户体验极差,我的电脑,2核,16G,500并发,大概是4s

    二、解决的方案

      1、使用Redis的decr的方法,防止商品超卖,先减再判断是不是小于0,而不是先查再判断。

      2、使用RabbitMq,先入队列,然后消费者慢慢的进行消费,来进行流量的削峰,当然,限流也是可以的。

      3、使用RabbitMq实现异步,直接返回给用户信息,而不是让用户不同的转圈,增强体验

    三、主要代码

    controller层:

    package com.example.seckill.controller;
    
    
    import com.example.seckill.entity.GoodsRecord;
    import com.example.seckill.service.IGoodsRecordService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    import org.springframework.stereotype.Controller;
    
    import java.util.List;
    
    /**
     * <p>
     * 前端控制器
     * </p>
     *
     * @author caesar
     * @since 2020-12-08
     */
    @RestController
    @RequestMapping("/goodsRecord")
    public class GoodsRecordController {
        @Autowired
        private IGoodsRecordService iGoodsRecordService;
        /**
         *  @Author: caesar
         *  @Date:2020年12月08日 19:12:55
         *  @Description: 秒杀接口,不限次数,直接秒杀
         */
        @PostMapping("/seckillGoods")
        public String seckillGoods(@RequestBody GoodsRecord goodsRecord){
            return iGoodsRecordService.seckillGoods(goodsRecord);
        }
    
    
        /**
         *  @Author: caesar
         *  @Date:2020年12月08日 19:12:48
         *  @Description: 查询结果
         */
        @GetMapping("/queryGoodsRecordList")
        public List<GoodsRecord> queryGoodsRecordList(@RequestParam("userId") Integer userId){
            return iGoodsRecordService.queryGoodsRecordList(userId);
        }
    }

    sevice层:

    package com.example.seckill.service.impl;
    
    import com.example.seckill.activemq.MQSender;
    import com.example.seckill.dao.GoodsMapper;
    import com.example.seckill.entity.GoodsRecord;
    import com.example.seckill.dao.GoodsRecordMapper;
    import com.example.seckill.service.IGoodsRecordService;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.example.seckill.utils.RedisUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    /**
     * <p>
     *  服务实现类
     * </p>
     *
     * @author caesar
     * @since 2020-12-08
     */
    @Service
    public class GoodsRecordServiceImpl extends ServiceImpl<GoodsRecordMapper, GoodsRecord> implements IGoodsRecordService {
        @Autowired
        private RedisUtil redisUtil;
        @Autowired
        private GoodsRecordMapper goodsRecordMapper;
        @Autowired
        private GoodsMapper goodsMapper;
        @Autowired
        private MQSender mqSender;
        @Override
        public String seckillGoods(GoodsRecord goodsRecord) {
            Integer goodsId = goodsRecord.getGoodsId();
            Integer number  = goodsRecord.getNumber();
            // 判断库存是否充足
            long surplusNumber = redisUtil.decr(goodsId.toString(),number);
            if(surplusNumber < 0){
                // 如果不够了,重新加回
                if((surplusNumber + number) > 0){
                    redisUtil.incr(goodsId.toString(),number);
                }
                return "数量不足,秒杀失败!!!";
            }
            // 信息入消息队列
            mqSender.send(goodsRecord);
            //goodsRecordMapper.insertGoodsRecord(goodsRecord);
            //goodsMapper.updateGoods(goodsRecord);
            return "已参与抢购,请稍后。。。。。。";
        }
    
        /**
         *  @Author: caesar
         *  @Date:2020年12月09日 14:12:55
         *  @Description: 查询列表
         */
        @Override
        public List<GoodsRecord> queryGoodsRecordList(Integer userId) {
            return goodsRecordMapper.queryGoodsRecordList(userId);
        }
    
        /**
         *  @Author: caesar
         *  @Date:2020年12月09日 14:12:08
         *  @Description: 插入数据
         */
        @Override
        public void insertGoodsRecord(GoodsRecord goodsRecord) {
            goodsRecordMapper.insertGoodsRecord(goodsRecord);
        }
    }
    package com.example.seckill.service.impl;
    
    import com.example.seckill.entity.Goods;
    import com.example.seckill.dao.GoodsMapper;
    import com.example.seckill.service.IGoodsService;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    /**
     * <p>
     *  服务实现类
     * </p>
     *
     * @author caesar
     * @since 2020-12-08
     */
    @Service
    public class GoodsServiceImpl extends ServiceImpl<GoodsMapper, Goods> implements IGoodsService {
        @Autowired
        private GoodsMapper goodsMapper;
        @Override
        public List<Goods> queryGoodsList() {
            return goodsMapper.queryGoodsList();
        }
    }

    dao层:

    package com.example.seckill.dao;
    
    import com.example.seckill.entity.Goods;
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.example.seckill.entity.GoodsRecord;
    import org.apache.ibatis.annotations.Mapper;
    
    import java.util.List;
    
    /**
     * <p>
     *  Mapper 接口
     * </p>
     *
     * @author caesar
     * @since 2020-12-08
     */
    @Mapper
    public interface GoodsMapper extends BaseMapper<Goods> {
        public List<Goods> queryGoodsList();
    
        /**
         *  @Author: caesar
         *  @Date:2020年12月09日 14:12:19
         *  @Description: 更新库存
         */
        public void updateGoods(GoodsRecord goodsRecord);
    }
    package com.example.seckill.dao;
    
    import com.example.seckill.entity.GoodsRecord;
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    import org.springframework.web.bind.annotation.RequestBody;
    
    import java.util.List;
    
    /**
     * <p>
     *  Mapper 接口
     * </p>
     *
     * @author caesar
     * @since 2020-12-08
     */
    @Mapper
    public interface GoodsRecordMapper extends BaseMapper<GoodsRecord> {
    
        /**
         *  @Author: caesar
         *  @Date:2020年12月09日 14:12:19
         *  @Description: 秒杀记录入库
         */
        public void insertGoodsRecord(GoodsRecord goodsRecord);
    
        /**
         *  @Author: caesar
         *  @Date:2020年12月09日 14:12:35
         *  @Description: 查询列表
         */
        public List<GoodsRecord> queryGoodsRecordList(@Param("userId") Integer userId);
    
    }

    xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.example.seckill.dao.GoodsMapper">
    <select id="queryGoodsList" resultType="com.example.seckill.entity.Goods">
        select * from goods
    </select>
    <!--    更新库存-->
        <update id="updateGoods" parameterType="com.example.seckill.entity.GoodsRecord">
            update goods set goods.number = goods.number - #{number} where goods_id = #{goodsId}
        </update>
    </mapper>
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.example.seckill.dao.GoodsRecordMapper">
    <!--    秒杀-->
    <insert id="insertGoodsRecord" parameterType="com.example.seckill.entity.GoodsRecord">
        insert into goods_record (goods_id, user_id, goods_record.number) values (#{goodsId}, #{userId}, #{number})
    </insert>
    <!--    查询成功列表-->
        <select id="queryGoodsRecordList" resultType="com.example.seckill.entity.GoodsRecord">
            select * from goods_record where user_id = #{userId}
        </select>
    </mapper>

    activeMq配置类:

    package com.example.seckill.activemq;
    
    import com.example.seckill.dao.GoodsMapper;
    import com.example.seckill.dao.GoodsRecordMapper;
    import com.example.seckill.entity.GoodsRecord;
    import com.example.seckill.service.IGoodsRecordService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    /**
     *  @Author: caesar
     *  @Date:2020年12月08日 20:12:44
     *  @Description: 消费者
     */
    @Service
    public class MQReceiver {
        private static final Logger logger = LoggerFactory.getLogger(MQReceiver.class);
        @Autowired
        private GoodsRecordMapper goodsRecordMapper;
        @Autowired
        private GoodsMapper goodsMapper;
        private static final long now = System.currentTimeMillis();
        @RabbitListener(queues= "GOODS_QUEUE")//指明监听的是哪一个queue
        public void receive(GoodsRecord goodsRecord){
            logger.info("正在接收消息。。。。+入库时间为"+(System.currentTimeMillis()-now));
            goodsRecordMapper.insertGoodsRecord(goodsRecord);
            // 更新库存
            goodsMapper.updateGoods(goodsRecord);
        }
    }
    package com.example.seckill.activemq;
    
    import com.example.seckill.entity.GoodsRecord;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.amqp.core.AmqpTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    /**
     *  @Author: caesar
     *  @Date:2020年12月08日 19:12:37
     *  @Description: 消息发送者
     */
    @Service
    public class MQSender {
        private static final Logger logger = LoggerFactory.getLogger(MQSender.class);
        @Autowired
        private AmqpTemplate amqpTemplate;
        //Direct模式
        public void send(GoodsRecord goodsRecord) {
            logger.info("正在发送消息。。。。");
            //第一个参数队列的名字,第二个参数发出的信息
            amqpTemplate.convertAndSend("GOODS_QUEUE", goodsRecord);
        }
    }

    项目启动初始化类:

    package com.example.seckill.init;
    import com.example.seckill.entity.Goods;
    import com.example.seckill.service.IGoodsService;
    import com.example.seckill.utils.RedisUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    import java.util.List;
    
    /**
     *  @Author: caesar
     *  @Date:2020年12月08日 18:12:56
     *  @Description: 项目启动初始化类
     */
    @Component
    @Order(1)
    public class OrderRunnerFirst implements CommandLineRunner {
        @Autowired
        private IGoodsService iGoodsService;
        @Autowired
        private RedisUtil redisUtil;
        /**
         *  @Author: caesar
         *  @Date:2020年12月08日 18:12:47
         *  @Description: 执行的方法
         */
        @Override
        public void run(String... args) throws Exception {
            // 获取商品列表
            List<Goods> goodsList = iGoodsService.queryGoodsList();
            // 入缓存
            goodsList.forEach(x -> {
                redisUtil.set(x.getGoodsId().toString(),x.getNumber());
            });
        }
    }

    如果需要详细代码,我已上传到码云:https://gitee.com/gitee__wsq/seckill

    顺便提一下,本人使用的压测工具为JMeter

    欢迎大佬批评指正,谢谢啦!

  • 相关阅读:
    (转)sqlite3生成lib遇到的问题
    C++中的static
    static int和static final int的区别
    用static关键字修饰类
    Shell脚本的编写
    linux定时任务的设置 crontab 配置指南
    s3cmd的安装与配置
    s3cmd的安装与使用
    -Dmaven.multiModuleProjectDirectory system propery is not set.
    maven 环境搭建 Myeclipse配置
  • 原文地址:https://www.cnblogs.com/mcjhcnblogs/p/14109506.html
Copyright © 2011-2022 走看看