一、商品秒杀存在的问题
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
欢迎大佬批评指正,谢谢啦!