11. Anti-brush current limiting

We have already talked about the peaking of the flow rate before, and next we will talk about the anti-brush flow limitation.

First of all, we can use a more general verification code to wrap the spike token in front, and the verification code is needed to stagger the peak.

We can use awt to generate pictures:

public class CodeUtil {
    private static int width = 90;// 定义图片的width
    private static int height = 20;// 定义图片的height
    private static int codeCount = 4;// 定义图片上显示验证码的个数
    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' };

    /**
     * 生成一个map集合
     * code为生成的验证码
     * codePic为生成的验证码BufferedImage对象
     * @return
     */
    public static Map<String,Object> generateCodeAndPic() {
        // 定义图像buffer
        BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        // Graphics2D gd = buffImg.createGraphics();
        // Graphics2D gd = (Graphics2D) buffImg.getGraphics();
        Graphics gd = buffImg.getGraphics();
        // 创建一个随机数生成器类
        Random random = new Random();
        // 将图像填充为白色
        gd.setColor(Color.WHITE);
        gd.fillRect(0, 0, width, height);

        // 创建字体,字体的大小应该根据图片的高度来定。
        Font font = new Font("Fixedsys", Font.BOLD, fontHeight);
        // 设置字体。
        gd.setFont(font);

        // 画边框。
        gd.setColor(Color.BLACK);
        gd.drawRect(0, 0, width - 1, height - 1);

        // 随机产生40条干扰线,使图象中的认证码不易被其它程序探测到。
        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用于保存随机产生的验证码,以便用户登录后进行验证。
        StringBuffer randomCode = new StringBuffer();
        int red = 0, green = 0, blue = 0;

        // 随机产生codeCount数字的验证码。
        for (int i = 0; i < codeCount; i++) {
            // 得到随机产生的验证码数字。
            String code = String.valueOf(codeSequence[random.nextInt(36)]);
            // 产生随机的颜色分量来构造颜色值,这样输出的每位数字的颜色值都将不同。
            red = random.nextInt(255);
            green = random.nextInt(255);
            blue = random.nextInt(255);

            // 用随机产生的颜色将验证码绘制到图像中。
            gd.setColor(new Color(red, green, blue));
            gd.drawString(code, (i + 1) * xx, codeY);

            // 将产生的四个随机数组合在一起。
            randomCode.append(code);
        }
        Map<String,Object> map  =new HashMap<String,Object>();
        //存放验证码
        map.put("code", randomCode);
        //存放生成的验证码BufferedImage对象
        map.put("codePic", buffImg);
        return map;
    }

    public static void main(String[] args) throws Exception {
        //创建文件输出流对象
        OutputStream out = new FileOutputStream("E://"+System.currentTimeMillis()+".jpg");
        Map<String,Object> map = CodeUtil.generateCodeAndPic();
        ImageIO.write((RenderedImage) map.get("codePic"), "jpeg", out);
        System.out.println("验证码的值为:"+map.get("code"));
    }
}

Open a new interface

    //生成验证码
    @RequestMapping(value = "/generateverifycode",method = {RequestMethod.POST , RequestMethod.GET})
    @ResponseBody
    public void generateverifycode(HttpServletResponse response) throws BusinessException, IOException {
        //根据token获取用户信息
        String token = httpServletRequest.getParameterMap().get("token")[0];
        if (StringUtils.isEmpty(token)){
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN,"用户还未登陆,不能生成");
        }
        UserModel userModel = (UserModel) redisTemplate.opsForValue().get(token);
        if(userModel == null){
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN,"用户还未登陆,不能下单");
        }
        //创建文件输出流对象
        Map<String,Object> map = CodeUtil.generateCodeAndPic();
        redisTemplate.opsForValue().set("verify_code_" + userModel.getId() , map.get("code"));
        redisTemplate.expire("verify_code_" + userModel.getId() , 5 , TimeUnit.MINUTES);
        ImageIO.write((RenderedImage) map.get("codePic"), "jpeg", response.getOutputStream());
    }

Just check when generating the order.

So what is the purpose of our current limit? Because the traffic is much more than you think, the system is better alive than hanging. I'd rather only make it available to a small number of people, but not to everyone.

Current limit scheme:

Concurrency limit: For example, add a counter at the controller entrance, and decrement each time at the request entrance, and add 1 back at each exit, so that the number of request interfaces can be limited at the same time. Once it exceeds, the request is rejected.

Limit tps (capacity index for write operations to the database) and qps (query capacity index): There are two algorithms, token bucket algorithm and leaky bucket algorithm.

Token bucket principle:

We first set up a token bucket of 10 tokens, requesting that each time we come in, we will get a token from the token bucket, and when we set up a timer, we will add 10 tokens to the token bucket every second. Our tps is 10 per second.

Principle of leaky bucket algorithm:

There is a bucket with 100 drops of water flowing out at a rate of 10 drops per second. Each request by the client is equivalent to adding a drop of water to the bucket. If the bucket is full, it cannot be added, that is, the request is rejected. This also guarantees a tps of 10.

The difference between the two is that there is no way for the leaky bucket algorithm to deal with burst traffic. Even if there is a large amount of traffic coming in, the fixed 10 are allowed to pass. The token bucket algorithm can flexibly set the number of tokens. So the leaky bucket algorithm smoothes network traffic and flows in at a fixed rate. The token bucket algorithm limits the maximum value of traffic in a certain second and can cope with larger traffic, but cannot exceed the limit.

In practical applications, the token bucket algorithm is used more.

There are two kinds of current limit: interface dimension and total dimension

For current limiting method:

Cluster current limiting: Relying on redis or other middleware for uniform odd-numbered rides often results in performance bottlenecks.

Single machine current limit: Under the premise of load balancing, the single machine average current limit effect is better.

coding:

    private RateLimiter orderCreatRateLimiter;

    @PostConstruct
    public void init(){
        orderCreatRateLimiter = RateLimiter.create(100);
    }

Use the following code on the interface that requires current limiting:

    if (orderCreatRateLimiter.tryAcquire()){
            throw new BusinessException(EmBusinessError.RATELIMIT);
        }

RateLimiter belongs to guava, he is similar to the principle of token bucket, but instead of passing the timer, but when the bucket is empty, the rest of the request sleeps and uses the traffic given by the next second to make the request.

Next is the problem of anti-brush:

Queuing, current limiting, and tokens only control the total flow, but cannot control the flow of scalpers.

Traditional anti-brush:

Limit how many times a session (session_id, token) is called on the same second / minute interface: it is not valid for bypassing multi-session access.

Limit the number of ip access: the number is not easy to control, easy to accidentally hurt.

Therefore, we can introduce the concept of device fingerprints:

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

Based on the parameters of the fingerprint of the corresponding device, guess the probability of a device such as a simulator (for example, determine whether a Bluetooth device exists).

Next, you need a credential system:

Issue credentials based on device fingerprint

Bring the voucher on the key business link and verify it from the business system to the voucher server

The credential server determines the readable score of the corresponding credential according to the device fingerprint parameters equivalent to the corresponding credential and the real-time behavior risk control system. If the score is lower than a certain value, the business system returns a fixed error code and pulls up the front-end verification code to verify After successful verification, join the corresponding score of the credential server.

Published 97 original articles · won 28 · 10,000+ views

Guess you like

Origin blog.csdn.net/haozi_rou/article/details/105453351