JavaWeb限流QPS简易框架

时间:2021-07-19 13:30:53

 

Java Web利用filter实现拦截请求,统计信息、并控制单台机器QPS。

 

JavaWeb限流QPS简易框架

 

/**
 * 网络流量控制器
 */
public class TrafficFilter implements Filter {

    private ITrafficStatic trafficStatic;
    private ITrafficHandler trafficHandler;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        trafficHandler = new AgentTrafficHandler();
        trafficStatic = new TrafficStatic();
        trafficStatic.init();
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // step1: parse bizFunId
        String bizFunId = request.getParameter("funBizId");
        if (StringUtils.isBlank(bizFunId)) {
            chain.doFilter(request, response);
            return;
        }
        bizFunId = StringUtils.trim(bizFunId);
        // step2: check whether it should be cached.
        if (!trafficStatic.shouldLimitTraffic(bizFunId)) {
            chain.doFilter(request, response);
            return;
        }
        // step3: static the visitor.
        if (trafficStatic.isOutOfTrafficLimit(bizFunId)) {
            trafficHandler.handle((HttpServletRequest) request, (HttpServletResponse) response);
            return;
        }
        int visNum = trafficStatic.incAndGetTraffic(bizFunId);
        if (trafficStatic.isOutOfTrafficLimit(bizFunId, visNum)) {
            trafficHandler.handle((HttpServletRequest) request, (HttpServletResponse) response);
            return;
        } else {
            chain.doFilter(request, response);
            return;
        }
    }

    @Override
    public void destroy() {
        trafficStatic.destroy();
    }
}

 

public interface ITrafficStatic {

    public void init();

    public void destroy();

    public boolean shouldLimitTraffic(String bizId);

    public int incAndGetTraffic(String bizId);

    public boolean isOutOfTrafficLimit(String bizId, int number);

    public boolean isOutOfTrafficLimit(String bizId);

}

 

public class TrafficStatic implements ITrafficStatic {

    private static final AvatarLogger LOGGER = AvatarLoggerFactory.getLogger(TrafficStatic.class);

    private Map<Integer, Map<String, AtomicInteger>> staticMap = new ConcurrentHashMap<Integer, Map<String, AtomicInteger>>();
    private volatile Map<String, Integer> limitMap = new HashMap<String, Integer>();
    private volatile Map<String, Integer> tempMap;

    @Override
    public void init() {
        initCleanThread();
        initSyncThread();
    }

    @Override
    public void destroy() {
    }

    private void initCleanThread() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    sleep(60 * 1000); // sync pv every 1 min.
                    int version = (int) System.currentTimeMillis() / 1000 - 60;

                    Iterator<Map.Entry<Integer, Map<String, AtomicInteger>>> iterator = staticMap.entrySet().iterator();
                    while (iterator.hasNext()) {
                        Map.Entry<Integer, Map<String, AtomicInteger>> entry = iterator.next();
                        if (entry.getKey() <= version) {
                            iterator.remove();
                        }
                    }
                } // while
            }
        }).start();
    }

    private void initSyncThread() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    sleep(60 * 1000); // sync pv every 1 min.
                    if (MapUtils.isNotEmpty(tempMap)) {
                        tempMap.clear();
                        tempMap = null;
                    }
                    int version = getTimestampVersion();
                    tempMap = limitMap;
                    limitMap = readConfigFromLion();
                    for (int i = version; i < (version + 5*60); ++i) {
                        if (!staticMap.containsKey(i)) {
                            staticMap.put(i, new ConcurrentHashMap<String, AtomicInteger>());
                        }
                        checkAndNewMapEntries(limitMap, staticMap.get(i));
                    }
                } // while
            }
        }).start();
    }

    private static Map<String, Integer> readConfigFromLion() {
        try {
            Map<String, Integer> map = JsonUtils.fromJson(PropertiesLoaderSupportUtils.getProperty("tpfun-promo-web.networktraffic.traffic-config", "{}"), Map.class);
            return MapUtils.isEmpty(map) ? MapUtils.EMPTY_MAP : map;
        } catch (Exception e) {
            LOGGER.error("[networktraffic] error with reading config from lion", e);
            return MapUtils.EMPTY_MAP;
        }
    }

    private void checkAndNewMapEntries(Map<String, Integer> source, Map<String, AtomicInteger> target) {
        for (Map.Entry<String, Integer> entry : source.entrySet()) {
            if (!target.containsKey(entry.getKey())) {
                target.put(entry.getKey(), new AtomicInteger(0));
            }
        }
    }

    private void sleep(long mills) {
        try {
            Thread.sleep(mills);
        } catch (InterruptedException e) {
            LOGGER.error("[networktraffic] PvCounterBiz threads.sleep error", e);
        }
    }

    private int getTimestampVersion() {
        return (int) (System.currentTimeMillis() / 1000);
    }

    @Override
    public boolean shouldLimitTraffic(String bizId) {
        return limitMap.containsKey(bizId);
    }

    @Override
    public int incAndGetTraffic(String bizId) {
        int ver = getTimestampVersion();
        if (!staticMap.containsKey(ver)) {
            return 1;
        }
        Map<String, AtomicInteger> map = staticMap.get(ver);
        if (MapUtils.isEmpty(map) || !map.containsKey(bizId)) {
            return 1;
        }
        return map.get(bizId).incrementAndGet();
    }

    @Override
    public boolean isOutOfTrafficLimit(String bizId, int number) {
        int ver = getTimestampVersion();
        if (!limitMap.containsKey(bizId)) {
            return false;
        }
        return (number > limitMap.get(bizId));
    }

    @Override
    public boolean isOutOfTrafficLimit(String bizId) {
        int ver = getTimestampVersion();
        if (!staticMap.containsKey(ver)) {
            return false;
        }
        Map<String, AtomicInteger> map = staticMap.get(ver);
        if (MapUtils.isEmpty(map) || !map.containsKey(bizId)) {
            return false;
        }
        return isOutOfTrafficLimit(bizId, map.get(bizId).intValue());
    }

}

 

public interface ITrafficHandler {

    void handle(HttpServletRequest request, HttpServletResponse response);

}

 

public class AgentTrafficHandler implements ITrafficHandler {

    private AjaxTrafficHandler ajaxTrafficHandler = new AjaxTrafficHandler();
    private MobileTrafficHandler mobileTrafficHandler = new MobileTrafficHandler();

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response) {
        if (StringUtils.contains(request.getRequestURI(), "ajax")) {
            ajaxTrafficHandler.handle(request, response);
        } else {
            mobileTrafficHandler.handle(request, response);
        }
    }

}
public class AjaxTrafficHandler implements ITrafficHandler {


    private static final AvatarLogger LOGGER = AvatarLoggerFactory.getLogger(MobileTrafficHandler.class);

    private final static String RETURN_JSON = "{code:509, message:'out of max req limit'}";

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response) {
        response.setHeader("Content-Type", "application/json;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setStatus(200);
        try {
            response.getOutputStream().write(RETURN_JSON.getBytes("UTF-8"));
        } catch (IOException e) {
            LOGGER.error(e);
        }
    }

}

 

public class MobileTrafficHandler implements ITrafficHandler {

    private static final AvatarLogger LOGGER = AvatarLoggerFactory.getLogger(MobileTrafficHandler.class);

    private final static String RETURN_HTML = "<html>" +
            "<head>" +
            "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />" +
            "\n<meta charset='utf-8'>" +
            "<title>大众点评网</title>" +
            "</head><body>" +
            "<center>挤爆了、请稍等...</center>" +
            "</body>" +
            "<script type='text/javascript'>(function(){function reload(){ window.location.reload(); setTimeout(reload, 1500);} setTimeout(reload, 1500);})();" +
            "</script>" +
            "</html>";

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response) {
        response.setHeader("Content-Type", "text/html;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        response.setStatus(200);
        try {
            response.getOutputStream().write(RETURN_HTML.getBytes("UTF-8"));
        } catch (IOException e) {
            LOGGER.error(e);
        }
    }

}