zoukankan      html  css  js  c++  java
  • 服务保护浅谈--Guava

    为什么需要限流?

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

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

    现有的方案

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

    令牌桶算法

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

    1. 假如用户配置的平均发送速率为r,则每隔1/r秒一个令牌被加入到桶中。
    2. 桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝。
    3. 当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上。
    4. 如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待)。

    限流器实现

    1.pom文件中引入Guava包

    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>23.0</version>
    </dependency>
    

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

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

    package com.xwx.guava.intercepter;
    
    import com.alibaba.fastjson.JSONObject;
    import com.xwx.guava.dto.ResponseDTO;
    import com.xwx.guava.enums.ResponseEnum;
    import lombok.extern.log4j.Log4j;
    import org.springframework.http.MediaType;
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Objects;
    
    @Component
    public abstract class AbstractInterceptor extends HandlerInterceptorAdapter {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            ResponseEnum result;
            try {
                result = preFilter(request);
            } catch (Exception e) {
                result = ResponseEnum.SERVER_ERROR;
            }
            if (ResponseEnum.OK == result) {
                return true;
            }
            handleResponse(result, response);
            return  false;
        }
    
        //过滤方法
        protected abstract ResponseEnum preFilter(HttpServletRequest request);
    
        private void handleResponse(ResponseEnum result, HttpServletResponse response) {
            ResponseDTO responseDTO = new ResponseDTO();
            responseDTO.setCode(result.getCode());
            responseDTO.setMsg(result.getMsg());
            response.setStatus(HttpServletResponse.SC_OK);
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            PrintWriter writer = null;
            try {
                writer = response.getWriter();
                writer.write(JSONObject.toJSONString(responseDTO));
            } catch (IOException e) {
    
            } finally {
                if (writer != null) {
                    writer.close();
                }
            }
        }
    }
    

      

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

    package com.xwx.guava.intercepter;

    import com.google.common.util.concurrent.RateLimiter;
    import com.xwx.guava.enums.ResponseEnum;
    import org.springframework.stereotype.Component;

    import javax.servlet.http.HttpServletRequest;

    @Component("rateLimitInterceptor")
    public class RateLimitInterceptor extends AbstractInterceptor {

    /**
    * 单机全局限流器
    */
    private static final RateLimiter rateLimiter=RateLimiter.create(1);
    @Override
    protected ResponseEnum preFilter(HttpServletRequest request) {
    if(!rateLimiter.tryAcquire()){
    System.out.println("限流中......");
    return ResponseEnum.RATE_LIMIT;
    }
    System.out.println("请求成功");
    return ResponseEnum.OK;
    }
    }

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

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

    package com.xwx.guava.config;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.boot.autoconfigure.web.ResourceProperties;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.CacheControl;
    import org.springframework.web.method.support.HandlerMethodArgumentResolver;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    import org.springframework.web.servlet.mvc.method.annotation.RequestAttributeMethodArgumentResolver;

    import java.util.List;
    import java.util.concurrent.TimeUnit;

    @Configuration
    @EnableWebMvc
    public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Autowired
    @Qualifier("rateLimitInterceptor")
    private HandlerInterceptorAdapter rateLimitInterceptor;

    // @Autowired
    // @Qualifier("authorityInterceptor")
    // private HandlerInterceptorAdapter authorityInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(rateLimitInterceptor).addPathPatterns("/**");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/**").addResourceLocations("/").setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS).cachePublic());
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
    argumentResolvers.add(new RequestAttributeMethodArgumentResolver());
    }
    }

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

    package com.xwx.guava.controller;
    
    import com.xwx.guava.dto.ResponseDTO;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/test")
    public class TestController {
    
        @RequestMapping("testGuava")
        public ResponseDTO testGuava() throws InterruptedException {
            ResponseDTO dto=new ResponseDTO();
            return  dto;
        }
    }
    

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

    package com.xwx.guava.enums;
    
    public enum  ResponseEnum {
        OK(200,"成功"),
        RATE_LIMIT(401,"访问次数受限"),
        SERVER_ERROR(500,"服务器错误"),
        QUERY_USER_FAILED(601,"查询用户失败");
    
        private  int code;
        private  String msg;
    
        ResponseEnum(int code,String msg){
            this.code=code;
            this.msg=msg;
        }
    
        public  int getCode(){
            return  code;
        }
        public  String getMsg(){
            return  msg;
        }
    }

    最后我们使用jmeter来进行测试

    选择10个线程

    我们看下结果

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

  • 相关阅读:
    [P4721] 【模板】分治 FFT
    [GYM102452E] Erasing Numbers
    [LOJ6220] sum
    [CF776B] Sherlock and His Girlfriend
    [LOJ6087] 毒瘤题
    [LOJ2612] 花匠
    [LOJ529] 自然语言
    [CTSC2017] 吉夫特
    [LOJ6671] EntropyIncreaser 与 Minecraft
    [LOJ3196] 挂架
  • 原文地址:https://www.cnblogs.com/xwx20160804/p/13814888.html
Copyright © 2011-2022 走看看