Spike mall project ----- spike interface optimization

The core idea of interface optimization: reduce database access. (the ability of database to resist concurrency is limited)

  1. Use Redis to pre reduce inventory and reduce access to the database
  2. Use memory tags to reduce Redis access
  3. Use RabbitMQ queue buffer to place orders asynchronously to enhance the user experience

I Second kill interface optimization

①. The system initializes and loads the commodity inventory quantity onto Redis

    // After propertiesset performs some operations during system initialization
    @Override
    public void afterPropertiesSet() throws Exception {
        List<GoodsVO> list = goodsService.getGoodsVO();
        if (list == null) {
            return;
        }
        for (GoodsVO goodsVO : list) {   // Delete old cache
            redisService.delete(GoodsPrefix.goodsMiaoshaStock, "" + goodsVO.getId());
        }
        for (GoodsVO goodsVO : list) {   //Cache preheating
            redisService.set(GoodsPrefix.goodsMiaoshaStock, "" + goodsVO.getId(), goodsVO.getStockCount());
            localOverMap.put(goodsVO.getId(), false); //Initialize storage false
        }
    }

②. Verify the path. If there is no path, the request is illegal

③. Judge whether the second kill is over through goodsId. If it fails, it will be returned directly to reduce redis access

④. Judge whether there are orders in the cache

⑤. Pre decrease inventory redis inventory minus one and returns the remaining inventory

⑥. Send it to RabbitMQ for processing, join the queue, and change the synchronous request into asynchronous request through the queue to reduce the waiting time

  • The inventory is sufficient and there is no repeated second kill. The second kill request is encapsulated and put into the message queue. At the same time, a string "queuing" is returned to the front end, which means that it is queuing (the returned is not failure or success, which cannot be judged at this time)

        // ⑤. Join the queue to change synchronous requests into asynchronous requests through the queue to reduce the waiting time
        MiaoshaMessageDTO miaoshaMessageDTO = new MiaoshaMessageDTO();
        miaoshaMessageDTO.setGoodsId(goodsId);
        miaoshaMessageDTO.setUser(user);
        //   [reduce inventory, place order and write second kill order] are all done in the mq message queue
        mqSender.sendMessage(miaoshaMessageDTO);
        //  Asynchronous return queued
        return ResultUtil.success("In line");

Encapsulate MQ queue information class

@Data
public class MiaoshaMessageDTO {
    //User information
    private User user;
    //Item ID
    private Long goodsId;
}

⑦. Rabbit mq processing asynchronous orders - RabbitMQ configuration

1. Guide Package

        <!--Message queue-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

2. application.yml configuration

3. Rabbitmq configuration class_ Queue_ Switch

package com.xizi.miaosha.rabbitmq;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author xizizzz
 * @description: Message queue configuration class
 * @date 2021-6-23 07:48 PM
 */

@Configuration
public class MQConfig {

    //Second kill queue
    public static final String MIAOSHA_QUEUE = "miaosha.queue";

    //Second kill switch
    public static final String MIAOSHA_EXCHANGE = "miaosha.exchange";

    //Queue injection into ioc container
    @Bean
    public Queue queue() {
        return new Queue(MQConfig.MIAOSHA_QUEUE, true);
    }
}

4. Send information component

package com.xizi.miaosha.rabbitmq;

import com.alibaba.fastjson.JSON;
import com.xizi.miaosha.dto.MiaoshaMessageDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author xizizzz
 * @description:
 * @date 2021-6-23 07:48 PM
 */

@Slf4j
@Component
public class MQSender {

    //Inject default amqp template
    @Resource
    private AmqpTemplate amqpTemplate;

    //Send message method
    public void sendMessage(MiaoshaMessageDTO miaoshaMessageDTO) {
        //Print log
        log.info("[MQ [request to join],message={}", miaoshaMessageDTO.toString());
        amqpTemplate.convertAndSend(MQConfig.MIAOSHA_QUEUE, JSON.toJSONString(miaoshaMessageDTO));
    }
}

5. Receiver monitoring component

⑧. After receiving the data, the front end displays that it is queued, and polls the request server according to the commodity id and user id (once every 200ms)

1. Request for order interface

2. The background commodity id and user id determine whether there is a second kill order in the cache

Polling query seckill results, success: orderId failed: - 1 queued: 0

   // Polling query second kill results
    // Success: orderId failed: - 1 queued: 0
    @GetMapping(value = "/result")
    @ResponseBody
    public ResultVO result(@RequestParam(value = "goodsId") Long goodsId, User user) {
        //Judge the user first
        if (user == null) {
            return ResultUtil.error(ResultEnum.SESSION_OVERDUE);
        }
        //Judge the result of the order according to the user id and commodity id
        Long result = miaoshaOrderService.getMiaoshaResult(user.getId(), goodsId);
        return ResultUtil.success(result);
    }

Check the result of the order in the cache according to the user id and commodity id

The order information is directly obtained from redis according to the user id and commodity id h

⑨. Backend RabbitMQ listens to MIAOSHA_QUEUE queue

  • If there is a message, the incoming information will be obtained. Before executing the real second kill, judge the inventory of the database, judge whether to repeat the second kill, and then execute the second kill transaction (second kill transaction is an atomic operation: inventory minus 1, place an order and write order details)
  • At this time, the front end polls the request result interface according to the commodity id to see whether a commodity order has been generated. If - 1 is returned, it means the second kill failed, 0 means the queue is in progress, and > 0 means the second kill succeeded

1. Rabbitmq receiver listening

2. miaosha key business processing

    //The second kill operation is a transaction and needs to be identified with the @ Transactional annotation. If the inventory reduction fails, it will be rolled back
    @Transactional
    public OrderInfo miaosha(User user, GoodsVO goods) {
        //1. Inventory reduction - update the data in the database
        int i = miaoshaGoodsService.reduceStockById(goods.getId());
        if (i == 0) { //Return update i=0
            // End of second kill redis save flag
            setGoodsOver(goods.getId());
            // Throw a custom exception and the second kill ends
            throw new CustomException(ResultEnum.MIAOSHA_OVER);
        }
        // 2. Create orders based on user information and product information
        OrderInfo orderInfo = orderInfoService.createOrder(user, goods);
        // 3. Store the order information in redis, and the front end will continuously poll the query results of the order in the timer
        redisService.set(MiaoshaOrderPrefix.getByUserIdAndGoodsId, "" + user.getId() + "_" + goods.getId(), orderInfo);
        //4. Return order information
        return orderInfo;
    }

3. Inventory reduction - update the data in the database


public interface MiaoshaGoodsMapper extends Mapper<MiaoshaGoods> {
    //Update inventory reduction 1 in the database and stock_ count>0
    @Update("update miaosha_goods set stock_count=stock_count-1 where goods_id=#{goodsId} and stock_count>0")
    public int reduceStockById(@Param("goodsId") Long goodsId);
}

4. Create order

    //Create order
    @Transactional
    public OrderInfo createOrder(User user, GoodsVO goodsVO) {
        //Create order information table
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setCreateDate(new Date());
        orderInfo.setDeliveryAddrId(0L);
        orderInfo.setGoodsCount(1);
        orderInfo.setGoodsId(goodsVO.getId());
        orderInfo.setGoodsName(goodsVO.getGoodsName());
        orderInfo.setGoodsPrice(goodsVO.getMiaoshaPrice());
        orderInfo.setUserId(user.getId());
        orderInfo.setOrderChannel(1);
        orderInfo.setStatus(PayStatusEnum.CREATE_NOT_PAY.getCode());
        //Insert into order information table
        orderInfoMapper.insertOrderInfo(orderInfo);
        //Create second kill order
        MiaoshaOrder miaoshaOrder = new MiaoshaOrder();
        miaoshaOrder.setGoodsId(goodsVO.getId());
        miaoshaOrder.setUserId(user.getId());
        miaoshaOrder.setOrderId(orderInfo.getId() );
        //Insert into second kill order
        miaoshaOrderService.createOrder(miaoshaOrder);
        //Return order information
        return orderInfo;
    }

5. Store the order information into redis, and the front end will continuously poll the results of the order query in the timer

// 3. Store the order information in redis, and the front end will continuously poll the query results of the order in the timer
        redisService.set(MiaoshaOrderPrefix.getByUserIdAndGoodsId, "" + user.getId() + "_" + goods.getId(), orderInfo);

Added by fareedreg on Sun, 23 Jan 2022 06:41:44 +0200