一、秒殺會給系統帶來的問題
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()); }); } }
主要的類就是這些,大家如果需要完整代碼可以去碼雲上面獲取:
歡迎大佬批評指正!!!,謝謝啦!