目录
一. spring-cloud-zuul-ratelimit 基础解释
- 基础限流算法与常见限流实现参考四. Gateway 限流
- zuul本身是没有提供限流的功能的,可以根据提供的filter自己去做限流,当然也可以使用经写好了的限流组件去集成,例如spring-cloud-zuul-ratelimit
- spring-cloud-zuul-ratelimit是和zuul整合提供分布式限流策略的扩展,只需在yaml中配置几行配置,就可使应用支持限流
- 支持的限流粒度
- 服务粒度 (默认配置,当前服务模块的限流控制)
- 用户粒度 (详细说明,见文末总结)
- ORIGIN粒度 (用户请求的origin作为粒度控制)
- 接口粒度 (请求接口的地址作为粒度控制)
- 以上粒度自由组合,又可以支持多种情况。
- 如果还不够,自定义RateLimitKeyGenerator实现(注意在使用其它组件进行存储时添加对应组件依赖,配置)
- 限流还是按照一个基数进行判断的,提供了存储方式有
- InMemoryRateLimiter - 使用 ConcurrentHashMap作为数据存储
- ConsulRateLimiter - 使用 Consul 作为数据存储
- RedisRateLimiter - 使用 Redis 作为数据存储
- SpringDataRateLimiter - 使用 数据库 作为数据存储
- 设置限流是的几个参数解释
- limit 单位时间内允许访问的个数
- quota 单位时间内允许访问的总时间(统计每次请求的时间综合)
- refresh-interval 单位时间设置
二. 实现案例
1. pom 添加依赖
- 注意点:
- 引入zuul相关依赖
- 引入zuul实现限流的spring-cloud-zuul-ratelimit依赖
- 引入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>
- 注意点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 配置
- 注意点:
- 使用redis存储,配置redis连接
- 配置Zuul拦截的地址
- 通常情况下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地址区分
- 限流部分解释
- repository :是key值保存方式,可以选Redis、Consul、Spring Data JPA等方式,这里选择的是 Redis,所以要添加redis依赖和配置。
- limit 单位时间内允许访问的次数
- quota 单位时间内允许访问的总时间(单位时间窗口期内,所有的请求的总时间不能超过这个时间限制)
- refresh-interval 单位时间设置
- type 限流类型:
url类型的限流就是通过请求路径区分
origin是通过客户端IP地址区分
user是通过登录用户名进行区分,也包括匿名用户
3. redis 配置类
- 因为上面使用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过滤器
- 首先Zuul中过滤器分为
“pre”: 请求路由之前执行
“route”: 请求路由之后执行
“post”: 在"routing" 和 error 过滤器之后执行
- 创建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;
}
}
- 创建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;
}
}
- 创建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;
}
}
- 创建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;
}
}
- 这几个过滤器中间用到的配置类
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
- 上面有提高在yml中对zuul进行限流配置时可以在type中设置限流类型:
url类型的限流就是通过请求路径区分
origin是通过客户端IP地址区分
user是通过登录用户名进行区分,也包括匿名用户
- 如果不满足也可以自定义,例如根据用户名进行限流设置
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接口进行模拟
- 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;
}
}
- 请求解释:
- 如下图请求"/test/limit/execute"接口
- 在yml中配置zuul拦截"path: /test/** "所以可以拦截到上面执行的接口请求
- 又因为yml中zuul配置了" strip-prefix: true" #为true时会将path中的前缀去除掉
- 并且yml中配置"service-id: http://127.0.0.1:8080/ " 被拦击的请求会被路由为该ip
- 最终被zuul路由后实际请求的是"http://127.0.0.1:8080/limit/execute" 所以会请求到controller接口
- 当被限流时zuul会报异常,并且该异常可以被上面自定义的error过滤器拦截到,可以根据需求在里面进行指定处理,如果不处理会返回
{
"timestamp": "2022-08-25T09:12:13.959+0000",
"status": 429,
"error": "Too Many Requests",
"message": "429 TOO_MANY_REQUESTS"
}