前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Guava实现限流器

使用Guava实现限流器

作者头像
林老师带你学编程
发布2019-05-26 16:43:36
1.2K0
发布2019-05-26 16:43:36
举报
文章被收录于专栏:强仔仔强仔仔

为什么需要限流?

在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。限流可以认为服务降级的一种,限流通过限制请求的流量以达到保护系统的目的。

一般来说,系统的吞吐量是可以计算出一个阈值的,为了保证系统的稳定运行,一旦达到这个阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。否则,很容易导致服务器的宕机。

现有的方案

Google的Guava工具包中就提供了一个限流工具类——RateLimiter,本文也是通过使用该工具类来实现限流功能。RateLimiter是基于“令牌通算法”来实现限流的。

令牌桶算法

令牌桶算法是一个存放固定容量令牌(token)的桶,按照固定速率往桶里添加令牌。令牌桶算法基本可以用下面的几个概念来描述:

假如用户配置的平均发送速率为r,则每隔1/r秒一个令牌被加入到桶中。

桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝。

当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上。

如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待)。

限流器实现

1.pom文件中引入Guava包

代码语言:javascript
复制
		<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>27.0.1-jre</version>
		</dependency>

2.自定义拦截器,并在拦截器中实现限流

a)定义一个拦截器抽象类,用于多个拦截器复用,主要是继承HandlerInterceptorAdapter,重写preHandle方法;并提供preFilter抽象方法,供子类实现。

代码语言:javascript
复制
/**
 * @author linzhiqiang
 * @date 2019/4/17
 */
public abstract class AbstractInterceptor extends HandlerInterceptorAdapter {
    private Logger logger = LoggerFactory.getLogger(AbstractInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        ResponseEnum result;
        try {
            result = preFilter(request);
        } catch (Exception e) {
            logger.error("preHandle catch a exception:" + e.getMessage());
            result = ResponseEnum.FAIL;
        }
        if (ResponseEnum.SUCCESS.code.equals(result.code)) {
            return true;
        }
        handlerResponse(result, response);
        return false;
    }


    /**
     * 自定义pre处理
     *
     * @param request
     * @return
     */
    protected abstract ResponseEnum preFilter(HttpServletRequest request);

    /**
     * 错误处理事件
     *
     * @param result
     * @param response
     */
    private void handlerResponse(ResponseEnum result, HttpServletResponse response) {
        ResponseDto responseDto = new ResponseDto();
        responseDto.setCode(result.code);
        responseDto.setStatus(result.status);
        responseDto.setMessage(result.message);
        response.setStatus(HttpServletResponse.SC_OK);
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        PrintWriter printWriter = null;
        try {
            printWriter = response.getWriter();
            printWriter.write(JsonUtils.toJson(responseDto));
        } catch (Exception e) {
            logger.error("handlerResponse catch a exception:" + e.getMessage());
        } finally {
            if (printWriter != null) {
                printWriter.close();
            }
        }
    }
}

b)定义流量控制拦截器,流量控制拦截器继承自上面的拦截器抽象类,在preFilter方法中进行流量控制。

代码语言:javascript
复制
/**
 *
 * @author linzhiqiang
 * @date 2019/4/17
 */
@Component("rateLimitInterceptor")
public class RateLimitInterceptor extends AbstractInterceptor {
    private Logger logger = LoggerFactory.getLogger(RateLimitInterceptor.class);

    /**
     * 单机全局限流器(限制QPS为250)
     */
    private static final RateLimiter rateLimiter = RateLimiter.create(300);

    public static void setRate(double limiterQPS){
        rateLimiter.setRate(limiterQPS);
    }
    @Override
    protected ResponseEnum preFilter(HttpServletRequest request) {
        if (!rateLimiter.tryAcquire()) {
            logger.warn("限流中......");
            return ResponseEnum.RATE_LIMIT;
        }
        return ResponseEnum.SUCCESS;
    }
}

使用Guava提供的RateLimiter类来实现流量控制,过程很简单:定义了一个QPS为1的全局限流器(便于测试),使用tryAcquire()方法来尝试获取令牌,如果成功则返回ResponseEnum.OK,否则返回ResponseEnum.RATE_LIMIT。

3.继承WebMvcConfigurerAdapter来添加自定义拦截器

代码语言:javascript
复制
@Configuration
public class MyWebAppConfigurer extends WebMvcConfigurationSupport {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 多个拦截器组成一个拦截器链
        // addPathPatterns 用于添加拦截规则
        // excludePathPatterns 用户排除拦截
        registry.addInterceptor(new RateLimitInterceptor()).addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}

4.写一个Controller来提供一个简单的访问接口

代码语言:javascript
复制
    /**
     * request测试专用
     * @return
     */
    @RequestMapping(value = "getUserList", method = RequestMethod.GET)
    public String getUserList() {
        String result = null;
        try {
            result = "请求成功";
        }catch (Exception e){
            logger.error("请求失败", e);
            return JsonUtils.toJson(ResponseUtils.failInServer(result));
        }
        return JsonUtils.toJson(ResponseUtils.success(result));
    }

上文使用到的ResponseEnum是一个返回Code的枚举:

代码语言:javascript
复制
/**
 * @description:
 * @Date : 2019/3/19 19:04
 * @Author : 樊康康-(kangkang.fan@mljr.com)
 */
public enum ResponseEnum {

    SUCCESS("200","000000","请求成功"),
    FAIL("200","100000","请求失败"),
    FAIL_BY_PARAMS("200","200000","请求参数异常"),
    FAIL_IN_SERVER("200","300000","服务器内部异常"),
    RATE_LIMIT("200","400000","限流中");

    public String status;
    public String code;
    public String message;

    ResponseEnum(String s, String s1, String s2) {
        this.status = s;
        this.code = s1;
        this.message = s2;
    }
}

5.使用Postman来测试接口

快速并且反复的调用接口,可以很容易的看到两种结果。

成功通过限流器的结果:

没有成功通过限流器的返回结果:

反复调用时,Console输出如下:

至此,简单的限流器实现完成。

文章转载于:https://wolzq.com

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年04月19日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么需要限流?
  • 现有的方案
  • 令牌桶算法
  • 限流器实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档