前言:流量控制,即Flow Control,主要是用来限定server所能承载的最大(高并发)流量峰值,以免在峰值是Server过载而宕机,对于WEB系统而言,通常是分布式部署,如果请求并发量很大,会导致整个集群崩溃,也就是通常所说的“雪崩效应”。所以,我们不仅在网络代理层面(比如nginx)设置流量控制以抵抗、拒止溢出流量,我们还应该在application server层面有一定的自我保护策略,确保当前JVM的负载应该在可控范围之内,对于JVM承载能力之外的请求,应该被合理管理。
本文主要通过开发一个Filter,来限定application的并发量:
1)对于过量的请求,首先将请求buffer在队列中。
2)当buffer队列满时,多余的请求将会被直接拒绝。(过载请求量)
3)那些buffer中被阻塞的请求,等待一定时间后任然无法被执行,则直接返回错误URL。(溢出请求量)
4)我们设定一个允许的并发量,通过java中Semaphore控制。只有获取“锁”的请求,才能继续执行。
一、web.xml配置
<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>
扫描二维码关注公众号,回复:
295488 查看本文章
<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; /** * 流量控制过滤器 * 用于保护当前JVM进程,在过载流量下,处于稳定可控状态。 */ public class FlowControlFilter implements Filter { //最大并发量 private int permits = Runtime.getRuntime().availableProcessors() + 1;//默认为500 //当并发量达到permits后,新的请求将会被buffer,buffer最大尺寸 //如果buffer已满,则直接拒绝 private int bufferSize = 500;// //buffer中的请求被阻塞,此值用于控制最大阻塞时间 private long timeout = 30000;//默认阻塞时间 private String errorUrl;//跳转的错误页面 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); //如果buffer已满 if (!buffered) { if(errorUrl != null) { httpServletResponse.sendRedirect(errorUrl); } return; } long deadline = System.currentTimeMillis() + timeout; //进入等待队列后,当前线程阻塞 LockSupport.parkNanos(this, TimeUnit.MICROSECONDS.toNanos(timeout)); if (t.isInterrupted()) { //如果线程是中断返回 t.interrupted();//clear status } //如果等待过期,则直接返回 if(deadline >= System.currentTimeMillis()) { if(errorUrl != null) { httpServletResponse.sendRedirect(errorUrl); } //对信号量进行补充 synchronized (lock) { if(node.dequeued) { semaphore.release(); } else { node.dequeued = true; } } return; } //继续执行 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(); //如果t,阻塞逃逸,只能在pack超时后退出 synchronized (lock) { if(node.dequeued) { //如果此线程已经park过期而退出了,则直接忽略 continue; } else { node.dequeued = true; } } semaphore.acquire(); LockSupport.unpark(node.currentThread); } } catch (Exception e) { // } finally { //全部释放阻塞 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;//是否已经出队 public Node(Thread t,boolean dequeued) { this.currentThread = t; this.dequeued = dequeued; } } @Override public void destroy() { selectorThread.interrupt() } }
当然大家可以重新Filter,使用Spring的拦截器,不过整体思想是想通的。