High parallel jmeter performance pressure measurement and performance improvement solution brush proof current limiting

Verification code generation and verification technology

Before packaging the second kill token, the verification code is required to stagger the peak.

code:

Generate verification code

package com.miaoshaProject.util;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * @author aric
 * @create 2021-08-11-17:05
 * @fun
 */
public class CodeUtil {
    private static int width = 90; //Define picture width
    private static int height = 20; //Define picture height
    private static int codeCount = 4; //Defines the number of verification codes displayed on the picture
    private static int xx = 15;
    private static int fontHeight = 18;
    private static int codeY = 16;
    private static char[] codeSequence={'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q',
            'R','S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9'};

    /**
     * Generate a map collection
     * code Verification code generated for
     * codePic BufferedImage object for generated verification code
     * @return
     */
    public static Map<String,Object> generateCodeAndPic(){
        //Define picture buffer
        BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        //Graphics2D gd = buffImg.createGraphics();
        //Graphics2D gd = (Graphics2D)buffImg.getGraphics();
        Graphics gd = buffImg.getGraphics();
        //Create a random number generator class
        Random random = new Random();
        //Fill the image with white
        gd.setColor(Color.WHITE);
        gd.fillRect(0,0,width,height);
        //Create a font. The size of the font should be determined according to the height of the picture
        Font font = new Font("Fixedsys",Font.BOLD,fontHeight);
        //Set font
        gd.setFont(font);

        //Draw border
        gd.setColor(Color.BLACK);
        gd.drawRect(0,0,width-1,height-1);

        //40 interference lines are randomly generated, so that the authentication code in the image is not easy to be detected by other programs
        gd.setColor(Color.BLACK);
        for (int i = 0; i < 30; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int xl = random.nextInt(12);
            int yl = random.nextInt(12);
            gd.drawLine(x,y,x+xl,y+yl);
        }

        //randomCode is used to save the randomly generated verification code so that users can verify after logging in
        StringBuffer randomCode = new StringBuffer();
        int red = 0,green = 0,blue = 0;

        //Verification code of randomly generated codeCount number
        for (int i = 0; i < codeCount; i++) {
            //Get the randomly generated verification code number
            String code = String.valueOf(codeSequence[random.nextInt(36)]);
            //Generate random color components to construct color values, so that the color values of each array output will be different
            red = random.nextInt(255);
            green = random.nextInt(255);
            blue = random.nextInt(255);

            //Draw the verification code into the image with randomly generated colors
            gd.setColor(new Color(red,green,blue));
            gd.drawString(code,(i+1)*xx,codeY);

            //Combine the generated four random numbers together
            randomCode.append(code);
        }
        Map<String,Object> map = new HashMap<>();
        //Store verification code
        map.put("code",randomCode);
        //BufferedImage object for storing generated verification code
        map.put("codePic",buffImg);
        return map;
    }

    public static void main(String[] args) throws IOException {
        //Create file output stream object
        FileOutputStream out = new FileOutputStream("/Desktop/javaworkspace/miaoshaStable/" + System.currentTimeMillis()+".jpg");
        Map<String, Object> map = CodeUtil.generateCodeAndPic();
        ImageIO.write((RenderedImage) map.get("codePic"),"jepg",out);  //Draw on the page
        System.out.println(""+map.get("code"));
    }
}

Add verification code to the controller layer:

package com.miaoshaProject.controller;

import com.miaoshaProject.error.BusinessException;
import com.miaoshaProject.error.EmBusinessError;
import com.miaoshaProject.mq.MqProducer;
import com.miaoshaProject.response.CommonReturnType;
import com.miaoshaProject.service.ItemService;
import com.miaoshaProject.service.OrderService;
import com.miaoshaProject.service.PromoService;
import com.miaoshaProject.service.model.UserModel;
import com.miaoshaProject.util.CodeUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.*;

/**
 * @author aric
 * @create 2021-07-02-18:41
 * @fun
 */
@Controller("order")
@RequestMapping("/user")
@CrossOrigin(origins = {"*"}, allowedHeaders = "true")
public class orderController extends BaseController {

    @Autowired
    private OrderService orderService;

    @Autowired
    private HttpServletRequest httpServletRequest;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private MqProducer mqProducer;

    @Autowired
    private ItemService itemService;

    @Autowired
    private PromoService promoService;

    private ExecutorService executorService;

    @PostConstruct
    public void init(){
        executorService = Executors.newFixedThreadPool(20);  //new comes out of a thread pool. Only 20 threads can be assigned to executorService
    }

    //Generate verification code
    @RequestMapping(value = "/generateverifycode", method = {RequestMethod.GET,RequestMethod.POST})
    @ResponseBody
    public void generateverifycode(HttpServletResponse response) throws BusinessException, IOException {
        //Get the user's login information, and then read it from Redis
        String token = httpServletRequest.getParameterMap().get("token")[0];  //It can also be obtained from parameters
        if (StringUtils.isEmpty(token)) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "The user is not logged in,Unable to generate verification code");
        }
        //Get user login information
        UserModel userModel = (UserModel) redisTemplate.opsForValue().get(token);
        if (userModel == null) {  //To expire
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "The user is not logged in,Unable to generate verification code");
        }
        Map<String, Object> map = CodeUtil.generateCodeAndPic();
        redisTemplate.opsForValue().set("verify_code_"+userModel.getId(),map.get("code"));  //Bind the verification code to the user Id
        redisTemplate.expire("verify_code_"+userModel.getId(),5,TimeUnit.MINUTES);
        ImageIO.write((RenderedImage) map.get("codePic"),"jepg",response.getOutputStream());  //Write to HttpServletResponse and return to the front end
    }

   //Generate second kill token
    @RequestMapping(value = "/generatetoken", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
    @ResponseBody
    public CommonReturnType generatetoken(@RequestParam(name = "itemId") Integer itemId,
                                          @RequestParam(name = "promoId") Integer promoId,
                                          @RequestParam(name = "verifyCode")String verifyCode) throws BusinessException {
        //Get the user's login information, and then read it from Redis
        String token = httpServletRequest.getParameterMap().get("token")[0];  //It can also be obtained from parameters
        if (StringUtils.isEmpty(token)) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "The user is not logged in,Can't place an order");
        }
        //Get user login information
        UserModel userModel = (UserModel) redisTemplate.opsForValue().get(token);
        if (userModel == null) {  //To expire
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "The user is not logged in,Can't place an order");
        }

        //Verify the validity of the verification code through verifycode
        String redisVerifyCode = (String)redisTemplate.opsForValue().get("verify_code_" + userModel.getId());
        if(StringUtils.isEmpty(redisVerifyCode)){
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"Illegal request");
        }
        if(!redisVerifyCode.equalsIgnoreCase(verifyCode)){
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"Illegal request, verification code error!");
        }

        //Get second kill access token
        String promoToken = promoService.generateSecondKillToken(promoId, itemId, userModel.getId());
        if (promoToken == null) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "Failed to generate token");
        }
        return CommonReturnType.create(promoToken);
    }

    //Encapsulate order request
    @RequestMapping(value = "/createorder", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
    @ResponseBody
    public CommonReturnType createOrder(@RequestParam(name = "itemId") Integer itemId,
                                        @RequestParam(name = "promoId", required = false) Integer promoId,
                                        @RequestParam(name = "amount") Integer amount,
                                        @RequestParam(name = "promoToken") String promoToken) throws BusinessException {
        //Get the user's login information, and then read it from Redis
//        Boolean isLogin = (Boolean)httpServletRequest.getSession().getAttribute("IS_LOGIN");
        String token = httpServletRequest.getParameterMap().get("token")[0];  //It can also be obtained from parameters
        if (StringUtils.isEmpty(token)) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "The user is not logged in,Can't place an order");
        }
        //Get user login information
        UserModel userModel = (UserModel) redisTemplate.opsForValue().get(token);
        if (userModel == null) {  //To expire
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "The user is not logged in,Can't place an order");
        }
        //Verify whether the second kill order is correct
        if (promoId != null) {
            String promoTokenInRedis = (String) redisTemplate.opsForValue().get("promo_token_" + promoId + "_userid_" + userModel.getId() + "_itemid_" + itemId);
            if (promoTokenInRedis == null) {
                throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "Second kill token verification failed");
            }
            if (!StringUtils.equals(promoToken, promoTokenInRedis)) {
                throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "Second kill token verification failed");
            }
        }

        //Synchronously call the submit method of the thread pool and the asynchronous thread of new Callable
        //A waiting queue with a congestion window of 20 is used for flood discharge
        Future<Object> future = executorService.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                //Add inventory flow init status
                String stockLogId = itemService.initStockLog(itemId, amount);

                //Then complete the corresponding single transaction message mechanism
                if (!mqProducer.transactionAsyncReduceStock(userModel.getId(), itemId, promoId, amount, stockLogId)) {
                    throw new BusinessException(EmBusinessError.UNKNOWN_ERROR, "Order failed");
                }
                return null;
            }
        });
        try {
            future.get();
        } catch (InterruptedException e) {
            throw new BusinessException(EmBusinessError.UNKNOWN_ERROR);
        } catch (ExecutionException e) {
            throw new BusinessException(EmBusinessError.UNKNOWN_ERROR);
        }
        return CommonReturnType.create(null);
    }
}

Principle and implementation of current limiting

Principle: the flow is much more than you think. It's better for the system to live than to hang up. You'd rather let only a few people use it than let everyone not use it

Scheme: 1 Concurrent 2 Token bucket algorithm (limited TPS) 3. Leaky bucket algorithm (smoothing network traffic, providing network services at a fixed rate, unable to cope with sudden traffic)

Token bucket algorithm:

 

Only 10 (several clients) tokens are issued every second. The timer prevents 10 tokens from entering the bucket every second. The corresponding client can enter 10 corresponding traffic in 1s and the next 10 in the next second. In this way, the performance of limiting the traffic in each interface is about 10TPS.

Leaky bucket algorithm:

Only one drop of water is allowed to pass at a time. When it is full, it will refuse and can't deal with emergencies.

Current limiting force: 1 Interface dimension 2 Total dimension

Interface dimension: set token bucket algorithm for each interface

Total dimension: each interface * 80% of the total number of interfaces is taken as the general dimension

Current limiting range: 1 Cluster current limiting 2 Single machine current limiting

Cluster current limiting: relying on redis or other middleware technologies as unified counters often leads to performance bottlenecks

Single machine current limiting: under the premise of load balancing, the average current limiting effect of single machine is better

code:

/**
 * @author aric
 * @create 2021-07-02-18:41
 * @fun
 */
@Controller("order")
@RequestMapping("/user")
@CrossOrigin(origins = {"*"}, allowedHeaders = "true")
public class orderController extends BaseController {    

    private RateLimiter orderCreateRateLimiter;

    @PostConstruct
    public void init(){
        orderCreateRateLimiter = RateLimiter.create(300);  //Initializing current limiting, 300 times per second for a single machine (depending on the number of voltage measuring TPS per second)
    }

    //Encapsulate order request
    @RequestMapping(value = "/createorder", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
    @ResponseBody
    public CommonReturnType createOrder(@RequestParam(name = "itemId") Integer itemId,
                                        @RequestParam(name = "promoId", required = false) Integer promoId,
                                        @RequestParam(name = "amount") Integer amount,
                                        @RequestParam(name = "promoToken") String promoToken) throws BusinessException {
        if(orderCreateRateLimiter.tryAcquire()){  //The underlying implementation is similar to the token bucket algorithm, but it can advance the request at the next time
            throw new BusinessException(EmBusinessError.RATELIMIT);
        }
    }
}

Cattle control technology

Anti brushing Technology: queuing and current limiting tokens can only control the total flow, but can not control the flow of scalpers.

Traditional anti brush:

1. Limit the number of interface calls of a session (session_id, token) in the same second / minute. Disadvantages: multi session intervention bypass is invalid.

2. Limit the number of interface calls of an ip in the same second / minute. Disadvantages: the quantity is not easy to control and is easy to be injured by mistake.

Why yellow cattle are difficult to prevent:

1. Simulator cheating: simulate hardware equipment and modify equipment information

2. Equipment: a batch of mobile devices in the studio

3. Manual cheating: attract part-time personnel to swipe bills by commission

Set fingerprint:

1. Collect various parameters of the terminal equipment and generate a unique device fingerprint when starting the application

2. Guess the probability of suspicious equipment such as simulator according to the parameters of corresponding equipment fingerprint.

3. Issue the certificate according to the fingerprint of the equipment

4. Take the certificate on the key service link and verify it on the certificate server by the business system

5. The certificate server determines the suspicious degree score of the corresponding certificate according to the equipment fingerprint parameters equivalent to the corresponding certificate and the real-time behavior risk control system

6. If the score is lower than a certain value, the business system returns a fixed error code, pulls up the front-end verification code for self-examination, and adds the corresponding score of the voucher server after the self-examination is successful.

Keywords: Java

Added by A584537 on Sat, 01 Jan 2022 06:10:30 +0200