一、秒杀会给系统带来的问题
1、商品库存减成负数
2、不停的访问数据库,导致数据库宕机,对数据库的压力很大
3、用户体验极差,500并发,在我的电脑上,当然是windows,2核,16G,4s,加载4s。。。。好吧
二、解决方案
1、将商品的库存,存到redis中,这样不用从mysql中,一直获取数据库,提高性能,使用redis的decr方法,再判断的方法,而不是先查询再判断的方法,防止卖超。
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); } }
service层:
package com.example.seckill.service; import com.example.seckill.entity.GoodsRecord; import com.baomidou.mybatisplus.extension.service.IService; import com.example.seckill.utils.RedisUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import java.util.List; /** * <p> * 服务类 * </p> * * @author caesar * @since 2020-12-08 */ public interface IGoodsRecordService extends IService<GoodsRecord> { /** * @Author: caesar * @Date:2020年12月09日 14:12:21 * @Description: 秒杀逻辑 */ public String seckillGoods(GoodsRecord goodsRecord); /** * @Author: caesar * @Date:2020年12月09日 14:12:29 * @Description: 查询秒杀记录 */ public List<GoodsRecord> queryGoodsRecordList(Integer userId); /** * @Author: caesar * @Date:2020年12月09日 14:12:39 * @Description: 商品入库 */ public void insertGoodsRecord(GoodsRecord goodsRecord); }
package com.example.seckill.service; import com.example.seckill.entity.Goods; import com.baomidou.mybatisplus.extension.service.IService; import java.util.List; /** * <p> * 服务类 * </p> * * @author caesar * @since 2020-12-08 */ public interface IGoodsService extends IService<Goods> { public List<Goods> queryGoodsList(); }
service实现层:
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.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>
<?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>
队列配置:
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.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.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()); }); } }
主要的类就是这些,大家如果需要完整代码可以去码云上面获取:
欢迎大佬批评指正!!!,谢谢啦!