五. Zuul 限流

一. spring-cloud-zuul-ratelimit 基础解释

  1. 基础限流算法与常见限流实现参考四. Gateway 限流
  2. zuul本身是没有提供限流的功能的,可以根据提供的filter自己去做限流,当然也可以使用经写好了的限流组件去集成,例如spring-cloud-zuul-ratelimit
  3. spring-cloud-zuul-ratelimit是和zuul整合提供分布式限流策略的扩展,只需在yaml中配置几行配置,就可使应用支持限流
  4. 支持的限流粒度
  1. 服务粒度 (默认配置,当前服务模块的限流控制)
  2. 用户粒度 (详细说明,见文末总结)
  3. ORIGIN粒度 (用户请求的origin作为粒度控制)
  4. 接口粒度 (请求接口的地址作为粒度控制)
  5. 以上粒度自由组合,又可以支持多种情况。
  6. 如果还不够,自定义RateLimitKeyGenerator实现(注意在使用其它组件进行存储时添加对应组件依赖,配置)
  1. 限流还是按照一个基数进行判断的,提供了存储方式有
  1. InMemoryRateLimiter - 使用 ConcurrentHashMap作为数据存储
  2. ConsulRateLimiter - 使用 Consul 作为数据存储
  3. RedisRateLimiter - 使用 Redis 作为数据存储
  4. SpringDataRateLimiter - 使用 数据库 作为数据存储
  1. 设置限流是的几个参数解释
  1. limit 单位时间内允许访问的个数
  2. quota 单位时间内允许访问的总时间(统计每次请求的时间综合)
  3. refresh-interval 单位时间设置

二. 实现案例

1. pom 添加依赖

  1. 注意点:
  1. 引入zuul相关依赖
  2. 引入zuul实现限流的spring-cloud-zuul-ratelimit依赖
  3. 引入redis相关依赖,此处使用redis存储限流基数
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>demo-parent</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>test-zuul</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--Zuul 依赖-->
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-zuul -->
        <!--<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>-->
        <!--Zuul 依赖此处使用与SpringCloud整合的依赖-->
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-zuul -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            <version>2.1.2.RELEASE</version>
        </dependency>

        <!--引入zuul 限流组件依赖 spring-cloud-zuul-ratelimit-->
        <!-- https://mvnrepository.com/artifact/com.marcosbarbero.cloud/spring-cloud-zuul-ratelimit -->
        <dependency>
            <groupId>com.marcosbarbero.cloud</groupId>
            <artifactId>spring-cloud-zuul-ratelimit</artifactId>
            <version>2.1.0.RELEASE</version>
        </dependency>

        <!--引入redis依赖,注意如果使用spring-boot-starter-data-redis,该依赖需要使用c3p0连接池-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--由于上面有引入的redis依赖为spring-boot-starter-data-redis,
        该依赖需要使用c3p0连接池,通过commons-pool2引入c3p0连接池-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <!--spring boot2.x以上版本如果yml中不使用lettuce而是jedis需要引入,不然启动会报错!-->
        <!--<dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>-->

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.8</version>
        </dependency>

        <!--lombok 依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>
  1. 注意点2: 上方的pom是通过parent指定的父工程, 父工程中使用的SpringBoot依赖版本为
	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <!--注意点可能需要添加一个relativePath标签
        表示: 设定一个空值将始终从仓库中获取,不从本地路径获取-->
        <!--<relativePath></relativePath>-->
    </parent>

2. yml 配置

  1. 注意点:
  1. 使用redis存储,配置redis连接
  2. 配置Zuul拦截的地址
  3. 通常情况下Zuul拦截请求,路由时会路由指定服务名,由于此处模拟,路由到指定ip即可
server:
  port: 8080
spring:
  application:
    name: cloud-zuul
  redis:
    host: 118.222.61.141
    port: 16379
    password: sdfasdf
    default-timeout: 60

zuul:
  #设置网关在请求转发前为请求设置HOST头信息
  add-host-header: true
  ignored-services: '*' #忽略路径或指定服务,*表示忽略所有,只通过下面配置的路由进行访问,多个用逗号隔开
  #通过 prefix来指定路由前缀,这样请求路径必须以/api开头的才会被zuul拦截代理
  #prefix: /api
  host:
    # 连接时间semaphores
    connect-timeout-millis: 3000
    # 每个router最大连接数
    max-per-route-connections: 100
    # 最大连接数
    max-total-connections: 1000
    # socket超时时间
    socket-timeout-millis: 60000
  routes:
    #分组名
    servicewel:
      #为true时会将path中的前缀去除掉
      strip-prefix: true
      #当前网关服务拦截路径
      path: /test/**  #假设请求当前网关服务"http://网关服务ip:网关服务端口号/此处配置拦截的路径/目标服务接口路径"
      #目标服务在注册中心注册的名称
      #serviceId: service-wel #实际会转发"http://service-wel/目标服务接口路径"
      #如果要路由到指定ip端口号,可以使用下方的,也可以使用去指定 url: http://localhost:8080/
      service-id: http://127.0.0.1:8080/
  ratelimit:
    enabled: true
    behind-proxy: true
    repository: REDIS #指定限流参数存储方式
    key-prefix: ilea-getway-key #指定存储时key前缀
    
    policies:
      #针对分组限流
      servicewel:
        limit: 1 #每秒多少个请求
        quota: 30 #单位时间内允许访问的总时间
        refresh-interval: 60 #刷新时间窗口的时间,默认值(秒)
        #限流粒度:
        type:
          - URL #URL通过请求路径区分,
          - USER #USER是通过登录用户名进行区分,也包括匿名用户
          - ORIGIN #ORIGIN通过客户端IP地址区分
  1. 限流部分解释
  1. repository :是key值保存方式,可以选Redis、Consul、Spring Data JPA等方式,这里选择的是 Redis,所以要添加redis依赖和配置。
  2. limit 单位时间内允许访问的次数
  3. quota 单位时间内允许访问的总时间(单位时间窗口期内,所有的请求的总时间不能超过这个时间限制)
  4. refresh-interval 单位时间设置
  5. type 限流类型:

url类型的限流就是通过请求路径区分
origin是通过客户端IP地址区分
user是通过登录用户名进行区分,也包括匿名用户

3. redis 配置类

  1. 因为上面使用redis进行存储,所以添加配置类
package com.test.zuul.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    // 默认用的是用JdkSerializationRedisSerializer进行序列化的
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 注入数据源
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        // key-value结构序列化数据结构
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // hash数据结构序列化方式,必须这样否则存hash 就是基于jdk序列化的
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        // 启用默认序列化方式
        redisTemplate.setEnableDefaultSerializer(true);
        redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);

        /// redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

4. 自定义Zuul过滤器

  1. 首先Zuul中过滤器分为

“pre”: 请求路由之前执行
“route”: 请求路由之后执行
“post”: 在"routing" 和 error 过滤器之后执行

  1. 创建pre路由前过滤器,方便确认哪些请求被zuul拦截到了,方便获取路由前的参数,状态等等
package com.test.zuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class PreLogFilter extends ZuulFilter {

    //1.设置当前过滤器的过滤类型
    @Override
    public String filterType() {
        //"pre": 请求路由之前执行
        //"route": 请求路由之后执行
        //"post": 在"routing" 和 error 过滤器之后执行
        return "pre";
    }

    //2.根据返回值设置当前过滤器执行的优先级,参数越小优先级越高
    @Override
    public int filterOrder() {
        return 0;
    }

    //3.判断过滤是否生效
    @Override
    public boolean shouldFilter() {
        //可以在该方法中通过 RequestContext 获取请求的上下文
        //通过获取到的数据判断设置当前过滤器是否生效
        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
        HttpServletResponse response = RequestContext.getCurrentContext().getResponse();
        return true;
    }

    //4.过滤方法,当向 RequestContext 中存储一个 "sendZuulResponse" 变量,值为false
    //时代表当前请求被拦截,解决访问,当没有存储该变量为false时return null 拦截放行
    @Override
    public Object run() throws ZuulException {
        //1.通过 RequestContext 获取上下文
        RequestContext currentContext = RequestContext.getCurrentContext();
        //2.通过上下文获取 Request
        HttpServletRequest request = currentContext.getRequest();
        //3.在 Request 中获取请求数据
        String userToken = request.getParameter("userToken");
        /*if (null == userToken) {
            //4.注意点,当上下文 RequestContext 调用 setSendZuulResponse() 方法,
            //设置为 false 时, 后续 return null,当前请求则不会继续向下执行,代表拒绝访问
            //查看该方法内部是向 RequestContext 中存储了一个"sendZuulResponse"变量值为false
            currentContext.setSendZuulResponse(false);
            currentContext.setResponseStatusCode(401);
            currentContext.setResponseBody("userToken is null");
            return null;
        }*/
        //5.RequestContext 中的 SendZuulResponse 不为 false
        //return null,代表当前过滤器放行,继续执行下一个过滤器
        return null;
    }
}
  1. 创建route, 路由后过滤器,方便确认被zuul拦截到的请求路由到哪了,最终生成的路由地址等,路由后的参数状态是否正确等等,也可以根据实际需求进行指定的全局业务处理
package com.test.zuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class RouteLogFilter extends ZuulFilter {

    //1.设置当前过滤器的过滤类型
    @Override
    public String filterType() {
        //"pre": 请求路由之前执行
        //"route": 请求路由之后执行
        //"post": 在"routing" 和 error 过滤器之后执行
        return "route";
    }

    //2.根据返回值设置当前过滤器执行的优先级,参数越小优先级越高
    @Override
    public int filterOrder() {
        return 0;
    }

    //3.判断过滤是否生效
    @Override
    public boolean shouldFilter() {
        //可以在该方法中通过 RequestContext 获取请求的上下文
        //通过获取到的数据判断设置当前过滤器是否生效
        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
        HttpServletResponse response = RequestContext.getCurrentContext().getResponse();
        return true;
    }

    //4.过滤方法,当向 RequestContext 中存储一个 "sendZuulResponse" 变量,值为false
    //时代表当前请求被拦截,解决访问,当没有存储该变量为false时return null 拦截放行
    @Override
    public Object run() throws ZuulException {
        //1.通过 RequestContext 获取上下文
        RequestContext currentContext = RequestContext.getCurrentContext();
        //2.通过上下文获取 Request
        HttpServletRequest request = currentContext.getRequest();
        //3.在 Request 中获取请求数据
        String userToken = request.getParameter("userToken");
        /*if (null == userToken) {
            //4.注意点,当上下文 RequestContext 调用 setSendZuulResponse() 方法,
            //设置为 false 时, 后续 return null,当前请求则不会继续向下执行,代表拒绝访问
            //查看该方法内部是向 RequestContext 中存储了一个"sendZuulResponse"变量值为false
            currentContext.setSendZuulResponse(false);
            currentContext.setResponseStatusCode(401);
            currentContext.setResponseBody("userToken is null");
            return null;
        }*/
        //5.RequestContext 中的 SendZuulResponse 不为 false
        //return null,代表当前过滤器放行,继续执行下一个过滤器
        return null;
    }
}
  1. 创建post拦截器,Zuul路由完成后向下执行,请求完目标接口正常拿到响应的拦截器,可以在此处根据需求进行指定的网关处全局业务处理
package com.test.zuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.test.zuul.constant.GatewayFilterType;
import com.test.zuul.utils.AESBodyUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

@Slf4j
@Component
public class PostEncodeFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return GatewayFilterType.POST;
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return RequestContext.getCurrentContext().sendZuulResponse();
    }

    @Override
    public Object run() {
        try {
            //1.获取上下文
            RequestContext ctx = RequestContext.getCurrentContext();

            //2.根据请求进行业务处理
            /*GwAdapterInfo gwAdapterInfo = (GwAdapterInfo) ctx.get(GatewayFilterCtxKey.CTX_ADAPTER_INFO);
            if (gwAdapterInfo == null) {
                return false;
            }*/

            //3.获取响应根据响应进行业务处理,例如加密
            String body = ctx.getResponseBody();
            if (StringUtils.isEmpty(body)) {
                InputStream stream = ctx.getResponseDataStream();
                body = StreamUtils.copyToString(stream, StandardCharsets.UTF_8);
            }
            if (StringUtils.isNotEmpty(body)) {
                //log.info("body value {}", body);
                //加密处理,根据请求入参中的标识判断是否需要加密
                if ("1".equals(ctx.get("请求入参中的某个key"))) {
                    body = AESBodyUtil.encrypt(body, (String) ctx.get("请求入参中加密用的key"), (String)ctx.get("请求入参中加密用的iv"));
                }
                ctx.setResponseBody(body);
            } else {
                log.error("zuul post filter error body is null");
            }
        } catch (IOException e) {
            log.error("zuul post filter error", e);
        }
        return null;
    }
}
  1. 创建error过滤器,方便zuul网关发送请求发生异常时进行异常定位
package com.test.zuul.filter;
import com.alibaba.fastjson.JSON;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.test.zuul.constant.GatewayFilterType;
import com.test.zuul.result.BizResponseData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;

@Slf4j
@Component
public class ErrorFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return GatewayFilterType.ERROR;
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return RequestContext.getCurrentContext().getThrowable() != null;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        log.error("=================in error filter path " + ctx.getRequest().getRequestURI());
        Throwable e = ctx.getThrowable();
        if (e instanceof ZuulException) {
            ctx.remove("throwable");
        }
        log.error("ErrorFilter error ", e);
        HttpServletRequest request = ctx.getRequest();
        log.error("error.code={}", ctx.getResponseStatusCode(), e);
        request.setAttribute("exception", e);
        request.setAttribute("status_code", ctx.getResponseStatusCode()+"网关异常");

        //根据异常类型进行指定处理
        if (e != null && e.getCause() instanceof Exception) {

        }
        ctx.setSendZuulResponse(false);
        ctx.setResponseStatusCode(HttpStatus.OK.value());
        ctx.addZuulResponseHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
        ctx.setResponseBody(JSON.toJSONString(BizResponseData.error(44444, "网关异常")));
        return null;
    }
}
  1. 这几个过滤器中间用到的配置类
package com.test.zuul.constant;
import lombok.experimental.UtilityClass;
@UtilityClass
public class GatewayFilterType {
    /**
     * 请求被路由之前调用
     */
    public static final String PRE   = "pre";
    /**
     * 在路由请求时候被调用,
     */
    public static final String ROUTE = "route";
    /**
     * 在route和error过滤器之后被调用,
     */
    public static final String POST  = "post";
    /**
     * 处理请求时发生错误时被调用
     */
    public static final String ERROR = "error";
}
package com.test.zuul.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
@Slf4j
public class AESBodyUtil {

    private AESBodyUtil() {
    }

    private static final String AES_NAME = "AES";
    private static final String ENCRYPT_MODEL = "AES/CBC/PKCS7Padding";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 加密
     *
     * @param content
     * @param key
     * @return
     */
    public static String encrypt(String content, String key, String aiv) {
        byte[] result = null;
        try {
            Cipher cipher = Cipher.getInstance(ENCRYPT_MODEL);
            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES_NAME);
            AlgorithmParameterSpec paramSpec = new IvParameterSpec(aiv.getBytes());
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, paramSpec);
            result = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
        } catch (Exception ex) {
            log.error("AESBodyUtil encrypt error ", ex);
        }
        return Base64.encodeBase64String(result);
    }

    /**
     * 解密
     *
     * @param content
     * @param key
     * @return
     */
    public static String decrypt(String content, String key, String aiv) {
        try {
            Cipher cipher = Cipher.getInstance(ENCRYPT_MODEL);
            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES_NAME);
            AlgorithmParameterSpec paramSpec = new IvParameterSpec(aiv.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, keySpec, paramSpec);
            return new String(cipher.doFinal(Base64.decodeBase64(content)), StandardCharsets.UTF_8).trim();
        } catch (Exception ex) {
            log.error("AESBodyUtil decrypt error ", ex);
        }
        return StringUtils.EMPTY;
    }
}

package com.test.zuul.result;
import lombok.Data;
@Data
public class BizResponseData<T> {
    private Integer errorcode;
    private String msg;
    private T data;

    public BizResponseData() {

    }

    public BizResponseData(Integer errorcode, String msg) {
        this.errorcode = errorcode;
        this.msg = msg;
    }

    public BizResponseData(Integer errorcode, String msg, T data) {
        this.errorcode = errorcode;
        this.msg = msg;
        this.data = data;
    }


    public static <T> BizResponseData<T> error(int errorcode, String msg, T data) {
        return new BizResponseData(errorcode, msg, data);
    }

    public static BizResponseData error(int errorcode, String msg) {
        return new BizResponseData(errorcode, msg);
    }

    public static <T> BizResponseData<T> success(T data) {
        return new BizResponseData(0, "SUCCESS", data);
    }

    public static BizResponseData success() {
        return new BizResponseData(0, "SUCCESS");
    }
}

5. 自定义限流策略key

  1. 上面有提高在yml中对zuul进行限流配置时可以在type中设置限流类型:

url类型的限流就是通过请求路径区分
origin是通过客户端IP地址区分
user是通过登录用户名进行区分,也包括匿名用户

  1. 如果不满足也可以自定义,例如根据用户名进行限流设置
package com.test.zuul.config;

import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimitKeyGenerator;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimitUtils;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.support.DefaultRateLimitKeyGenerator;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.http.HttpServletRequest;

@Configuration
public class RateLimitKeyGeneratorStrategy {
    @Bean
    public RateLimitKeyGenerator rateLimitKeyGenerator(final RateLimitProperties properties, final RateLimitUtils rateLimitUtils) {
        return new DefaultRateLimitKeyGenerator(properties, rateLimitUtils) {
            @Override
            public String key(final HttpServletRequest request, final Route route, final RateLimitProperties.Policy policy) {
                String name = request.getParameter("name");
                return super.key(request, route, policy) + ":" + name;
            }
        };

    }
}

6. 在当前Zuul服务中编写Controller接口进行模拟

  1. Controller接口
package com.test.zuul.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/limit")
public class TestController {
    @PostMapping("/execute")
    public String extcute(@RequestBody String val) {
        log.info("execte val:{}", val);
        return val;
    }
}
  1. 请求解释:
  1. 如下图请求"/test/limit/execute"接口
  2. 在yml中配置zuul拦截"path: /test/** "所以可以拦截到上面执行的接口请求
  3. 又因为yml中zuul配置了" strip-prefix: true" #为true时会将path中的前缀去除掉
  4. 并且yml中配置"service-id: http://127.0.0.1:8080/ " 被拦击的请求会被路由为该ip
  5. 最终被zuul路由后实际请求的是"http://127.0.0.1:8080/limit/execute" 所以会请求到controller接口
    在这里插入图片描述
  1. 当被限流时zuul会报异常,并且该异常可以被上面自定义的error过滤器拦截到,可以根据需求在里面进行指定处理,如果不处理会返回
{
	"timestamp": "2022-08-25T09:12:13.959+0000",
	"status": 429,
	"error": "Too Many Requests",
	"message": "429 TOO_MANY_REQUESTS"
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值