The purpose of flow restriction is to protect the system by limiting the speed of concurrent access / requests, or the speed of requests within a time window. Once the limit rate is reached, the system can be denied service, queued or waiting, degraded and other processing.
This paper mainly introduces how to use the official RateLimiter of Guava to realize current limiting. It mainly uses RateLimiter to write interceptors or filters to realize current limiting.
Step 1: define an exception handling class, which is specially used to handle flow restriction
/** * Abnormal handling of current limiting * @author yu */ public class RateLimiterException extends RuntimeException { public RateLimiterException(String message) { super(message); } }
Step 2: write interceptor or filter to intercept interface traffic
Implementation based on Interceptor:
/** * Current limiter */ public class RateLimitingInterceptor extends HandlerInterceptorAdapter { public enum LimitType { DROP,//Discard WAIT //wait for } /** * Current limiter */ private RateLimiter limiter; private LimitType limitType = LimitType.DROP; public RateLimitingInterceptor(){ limiter = RateLimiter.create(10); } /** * * @param tps Limit traffic (processing per second) * @param limitType Current limiting mode */ public RateLimitingInterceptor(int tps, RateLimitingInterceptor.LimitType limitType) { this.limiter = RateLimiter.create(tps); this.limitType = limitType; } /** * * @param permitsPerSecond Number of new tokens per second * @param limitType Current limiting type */ public RateLimitingInterceptor(double permitsPerSecond, RateLimitingInterceptor.LimitType limitType){ this.limiter = RateLimiter.create(permitsPerSecond, 1000, TimeUnit.MILLISECONDS); this.limitType = limitType; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (limitType.equals(LimitType.DROP)) { if (limiter.tryAcquire()) { return super.preHandle(request, response, handler); } } else { limiter.acquire(); return super.preHandle(request, response, handler); } throw new RateLimiterException("TOO MANY REQUESTS"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception{ super.afterCompletion(request, response, handler, ex); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { super.postHandle(request, response, handler, modelAndView); }
Register current limiter
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new RateLimitingInterceptor(5, RateLimitingInterceptor.LimitType.WAIT)) .addPathPatterns("/test/api/suggestion/*"); } }
Filter based implementation:
@Slf4j public class RateLimiterFilter implements Filter { /** * tps */ public static final String TPS = "tps"; /** * drop */ public static final String IS_DROP = "isDrop"; /** * Current limiter */ private RateLimiter limiter = null; /** * Whether the superfluid needs to be discarded */ private boolean isDrop; @Override public void init(FilterConfig filterConfig) { //Get the tps flow limit. If the configuration is wrong, set the configuration to 100 Double tps = NumberUtils.toDouble(filterConfig.getInitParameter(TPS),100); isDrop = Boolean.valueOf(filterConfig.getInitParameter(IS_DROP)); limiter = RateLimiter.create(tps); //100 request per second } @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,ServletException{ if(isDrop){ if(limiter.tryAcquire()) { log.debug("get access:"); chain.doFilter(request, response); }else { throw new RateLimiterException("TOO MANY REQUESTS"); } }else{ log.debug("access wait"); limiter.acquire(); chain.doFilter(request, response); } } }
Register current limiting filter
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Bean public FilterRegistrationBean testFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new RateLimiterFilter()); registration.addUrlPatterns("/test/api/suggestion/*"); registration.addInitParameter(RateLimiterFilter.TPS,"5"); registration.addInitParameter(RateLimiterFilter.IS_DROP, "true"); registration.setName("rateLimiterFilter"); registration.setOrder(1); return registration; } }
Step 3: current limiting test
jemeter can be used in the current limiting test to make concurrent requests to the interface. If a large number of requests exceed the flow limit, based on the non blocking flow limit, you can see the thrown flow limiting exception. For the blocking flow limit, the request will be processed slowly