“This is the 20th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”


Hello, everyone! I am the most important person in the area! Optimized the second kill interface


1. Interface optimization

The point is that we want to == reduce access to the database ==

  1. When the system is initialized, the inventory of kill items is loaded into Redis
  2. After receiving the request, pre-reduce the inventory in Redis. If the inventory is insufficient, the seconds kill failure will be returned directly
  3. Seconds kill, push the order into the message queue, return the front end message “queue” (like 12306 ticket)
  4. Message out of line, generate orders, reduce inventory
  5. During the preceding process, the client always polls for the success of seckilling

2. Clear block diagram analysis


3. How do we implement it in the code

3.1 Inventory is preloaded into Redis

We did this by implementing the InitialzingBean interface and overriding the afterProperties method

public class MiaoshaController implements InitializingBean {
	    @Override
    public void afterPropertiesSet(a) throws Exception {
        // When the system starts, the data is stored in Redis

        // Load all seckill items
        List<GoodsVo> goodsVos = goodsService.listGoodsVo();
        if(goodsVos == null)
            return;
        // Store in Redis, kill the quantity of goods per second
        for (GoodsVo good : goodsVos){
            redisService.set(GoodsKey.miaoshaGoodsStockPrefix,""+good.getId(),good.getStockCount());
            map.put(good.getId(),false); }}... }Copy the code
  1. We read the information from the database and load it into the cache one by one
  2. Note that there is a map that adds a key-value pair for id-false to indicate that the item has not been killed in seconds. This is used in the following context to prevent the item from accessing the Redis service when it has been killed in seconds.

3.2 Start second kill, pre-reduce inventory

        //user can not be empty, empty to login
        if(user == null) {return Result.error(CodeMsg.SESSION_ERROR);
        }

        // Use HashMap to reduce Redis access time
        boolean over = map.get(goodsId);
        if(over)
            return Result.error(CodeMsg.MIAO_SHA_OVER);

        // Request received, pre-inventory reduction
        Long count = redisService.decr(GoodsKey.miaoshaGoodsStockPrefix, "" + goodsId);
        if(count <= 0){
            map.put(goodsId,true);
            return Result.error(CodeMsg.MIAO_SHA_OVER);
        }
Copy the code
  1. First of all, the user cannot be null
  2. Here we see the map again, which is written in front of the Redis service to prevent it from accessing the Redis service when the item is finished
  3. Pre-decrement inventory, return the second kill failure when the inventory is less than 0

3.3 Joining a Message Queue (Direct Exchange)

        // Check whether the seconds have already killed
        MiaoshaOrder miaoshaOrder = orderService.selectMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
        if(miaoshaOrder ! =null)
            return Result.error(CodeMsg.REPEATE_MIAOSHA);

        // Joins the message queue
        MiaoshaMessage miaoshaMessage = new MiaoshaMessage();
        miaoshaMessage.setGoodsId(goodsId);
        miaoshaMessage.setMiaoShaUser(user);
        mqSender.sendMiaoshaMessage(miaoshaMessage);
Copy the code
  1. This is unnecessary because we have already created a unique index in the database. The userId and GoodsId are bound together, so no duplicate orders will be generated
  2. Customize the MiaoshaMessage class, create an object, add the user and goodsId information we want, and send the message

3.4 Message Sending Process

    @Autowired
    AmqpTemplate amqpTemplate;


    public void sendMiaoshaMessage(MiaoshaMessage miaoshaMessage){
        String msg = RedisService.beanToString(miaoshaMessage);
        log.info("miaosha send msg:" + msg);
        amqpTemplate.convertAndSend(MQConfig.MIAOSHA_QUEUE,msg);
    }
Copy the code
  • Use the AmqpTemlplate instance provided by the SpringBoot framework to send messages to our kill queue

3.5 Messages are processed by teams

    @RabbitListener(queues = MQConfig.MIAOSHA_QUEUE)
    public void receiveMiaoshaMsg(String miaoshaMessage){
        log.info("miaosha receive msg:" + miaoshaMessage);
        MiaoshaMessage msg = RedisService.stringToBean(miaoshaMessage, MiaoshaMessage.class);

        long goodsId = msg.getGoodsId();
        MiaoShaUser miaoShaUser = msg.getMiaoShaUser();
        GoodsVo goodsVo = goodsService.getGoodsVoByGoodsId(goodsId);

        // Check inventory
        int stock = goodsVo.getStockCount();
        if(stock < 0)
            return;

        // There is inventory and no seconds kill, start seconds kill
        miaoshaService.miaosha(miaoShaUser,goodsVo);
    }
Copy the code
  • Determine whether the inventory is still available, if so, execute the second kill down

3.5.1 Second kill method

    @Transactional
    public OrderInfo miaosha(MiaoShaUser user, GoodsVo goods) {
        // Inventory is reduced by 1
        boolean success = goodsService.reduceStock(goods);

        if(success)
            / / order
            return orderService.createOrder(user,goods);
        else{
            setGoodsOver(goods.getId());
            return null; }}Copy the code
  • This method is marked with the @Transactional annotation to ensure that destocking and placing orders are successful
  • Note that there is a setGoodsOver() method that saves a flag in Redis when the item is not in stock, which we will look at next

3.6 Seconds kill Result of Interaction with the front-end

    /** * orderId Succeeded * -1 seckill failed * 0 continue polling *@param miaoShaUser
     * @param goodsId
     * @return* /
    @RequestMapping(value = "/result",method = RequestMethod.GET)
    @ResponseBody
    public Result<Long> miaoshaResult(MiaoShaUser miaoShaUser,
                                      @RequestParam("goodsId")long goodsId){
        if(miaoShaUser == null)
            return Result.error(CodeMsg.SESSION_ERROR);

        long result = miaoshaService.getMiaoshaResult(miaoShaUser.getId(),goodsId);
        return Result.success(result);
    }
Copy the code
  • Here is a /resulet request, and the front end will determine the status of the kill based on the return value

3.6.1 getMiaoshaResult method

    public long getMiaoshaResult(long userId, long goodsId) {
        MiaoshaOrder order = orderService.selectMiaoshaOrderByUserIdGoodsId(userId, goodsId);

        if(order ! =null) {// Seckill succeeded
            return order.getOrderId();
        }else {
            boolean isOver = getGoodsOver(goodsId);
            if(isOver)
                return -1;
            else
                // Continue polling
                return 0; }}Copy the code
  • During the process of seckilling the item, the user will continue polling until the result of seckilling is returned to orderId or -1 to indicate the success or failure of seckilling
  • In this method, from the database to see if you can query the second kill order information, if the second kill is successful, return the order number; SetGoodsOver (), getGoodsOver(), obtains the value of set. If the command is not in stock, it fails to poll. If the command is not in stock, it continues to poll.

In summary, the process is simple.