基于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

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

 

posted @ 2020-12-09 16:58  码在江湖  阅读(979)  评论(4编辑  收藏  举报