Open the flow control filter in the JAVA WEB project

    Foreword: Flow control, that is, Flow Control, is mainly used to limit the maximum (highly concurrent) traffic peak that the server can carry, so as to avoid server overload and downtime at the peak. For WEB systems, it is usually distributed deployment. If The large number of concurrent requests will cause the entire cluster to crash, which is commonly referred to as the "avalanche effect". Therefore, we not only set up flow control at the network proxy level (such as nginx) to resist and reject overflow traffic, we should also have a certain self-protection strategy at the application server level to ensure that the current JVM load should be within the controllable range, Requests beyond the JVM's carrying capacity should be properly managed.

 

    This article mainly limits the concurrency of the application by developing a Filter:

    1) For excessive requests, the request buffer is first placed in the queue.

    2) When the buffer queue is full, redundant requests will be rejected directly. (overloaded requests)

    3) Those requests that are blocked in the buffer cannot be executed after waiting for a certain period of time, and the error URL will be returned directly. (overflow request volume)

    4) We set an allowable concurrency, which is controlled by Semaphore in java. Only the request to acquire the "lock" can proceed.

 

1. web.xml configuration

 

    <filter>
        <filter-name>flowControlFilter</filter-name>
        <filter-class>com.demo.security.FlowControlFilter</filter-class>
        <init-param>
            <param-name>permits</param-name>
            <param-value>128</param-value>
        </init-param>
        <init-param>
            <param-name>timeout</param-name>
            <param-value>15000</param-value>
        </init-param>
        <init-param>
            <param-name>bufferSize</param-name>
            <param-value>500</param-value>
        </init-param>
        <init-param>
            <param-name>errorUrl</param-name>
            <param-value>/error.html</param-value>
        </init-param>
    </filter>

 

    <filter-mapping>
        <filter-name>flowControlFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

 

二、FlowControlFilter

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * Flow control filter
 * It is used to protect the current JVM process and is in a stable and controllable state under overloaded traffic.
 */
public class FlowControlFilter implements Filter {

    //Maximum concurrency
    private int permits = Runtime.getRuntime().availableProcessors() + 1;//The default is 500

    //When the concurrency reaches the permits, the new request will be buffered, the maximum size of the buffer
    //If the buffer is full, reject directly
    private int bufferSize = 500;//
    //The request in the buffer is blocked, this value is used to control the maximum blocking time
    private long timeout = 30000;//Default blocking time

    private String errorUrl;//Jump error page

    private BlockingQueue<Node> waitingQueue;

    private Thread selectorThread;
    private Semaphore semaphore;

    private Object lock = new Object();

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String p = filterConfig.getInitParameter("permits");
        if(p != null) {
            permits = Integer.parseInt(p);
            if(permits < 0) {
                throw new IllegalArgumentException("FlowControlFilter,permits parameter should be greater than 0 !");
            }
        }

        String t = filterConfig.getInitParameter("timeout");
        if(t != null) {
            timeout = Long.parseLong(t);
            if(timeout < 1) {
                throw new IllegalArgumentException("FlowControlFilter,timeout parameter should be greater than 0 !");
            }
        }

        String b = filterConfig.getInitParameter("bufferSize");
        if(b != null) {
            bufferSize = Integer.parseInt(b);
            if(bufferSize < 0) {
                throw new IllegalArgumentException("FlowControlFilter,bufferSize parameter should be greater than 0 !");
            }
        }

        errorUrl = filterConfig.getInitParameter("errorUrl");

        waitingQueue = new LinkedBlockingQueue<>(bufferSize);
        semaphore = new Semaphore(permits);

        selectorThread = new Thread(new SelectorRunner());
        selectorThread.setDaemon(true);
        selectorThread.start();


    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        checkSelector();
        Thread t = Thread.currentThread();
        HttpServletResponse httpServletResponse = (HttpServletResponse)response;

        Node node = new Node(t,false);
        boolean buffered = waitingQueue.offer(node);
        //if the buffer is full
        if (!buffered) {
            if(errorUrl != null) {
                httpServletResponse.sendRedirect(errorUrl);
            }
            return;
        }
        long deadline = System.currentTimeMillis() + timeout;
        //After entering the waiting queue, the current thread is blocked
        LockSupport.parkNanos(this, TimeUnit.MICROSECONDS.toNanos(timeout));
        if (t.isInterrupted()) {
            //If the thread is interrupted return
            t.interrupted();//clear status

        }
        //If the wait expires, return directly
        if(deadline >= System.currentTimeMillis()) {
            if(errorUrl != null) {
                httpServletResponse.sendRedirect(errorUrl);
            }
            // supplement the semaphore
            synchronized (lock) {
                if(node.dequeued) {
                    semaphore.release();
                } else {
                    node.dequeued = true;
                }
            }
            return;
        }
        // continue to execute
        try {
            chain.doFilter(request,response);
        } finally {
            semaphore.release();
            checkSelector();
        }
    }

    private void checkSelector() {
        if(selectorThread != null && selectorThread.isAlive()) {
            return;
        }
        synchronized (lock) {
            if(selectorThread != null && selectorThread.isAlive()) {
                return;
            }
            selectorThread = new Thread(new SelectorRunner());
            selectorThread.setDaemon(true);
            selectorThread.start();
        }
    }

    private class SelectorRunner implements Runnable {

        @Override
        public void run() {
            try {
                while (true) {
                    Node node = waitingQueue.take();
                    //If t, blocking escape, can only exit after pack timeout
                    synchronized (lock) {
                        if(node.dequeued) {
                            //If the thread has exited after the park has expired, it will be ignored directly
                            continue;
                        } else {
                            node.dequeued = true;
                        }

                    }
                    semaphore.acquire();
                    LockSupport.unpark(node.currentThread);
                }
            } catch (Exception e) {
                //
            } finally {
                // release all blocking
                Queue<Node> queue = new LinkedList<>();
                waitingQueue.drainTo (queue);
                for(Node n : queue) {
                    if(!n.dequeued) {
                        LockSupport.unpark(n.currentThread);
                    }
                }
            }
        }
    }

    private class Node {
        Thread currentThread;
        boolean dequeued;//Whether it has been dequeued
        public Node(Thread t,boolean dequeued) {
            this.currentThread = t;
            this.dequeued = dequeued;
        }
    }


    @Override
    public void destroy() {
        selectorThread.interrupt()
    }

}

 

    Of course, you can re-Filter and use Spring's interceptor, but the overall idea is figured out.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326725921&siteId=291194637