目录
Sentinel 限流(gateway-server-sentinel-0)
gateway
官网: https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gateway-starter
工作流程
环境准备
nginx, 后面做高可用的时候使用
官网地址: http://nginx.org/en/download.html
创建项目
文章最后有源码地址,可以看具体的实现,
模块为 getway-server-0 和 gateway-server-sentinel-0
路由规则(getway-server-0)
动态路由
断言(条件判断,是否走该路由id)
路径匹配
http://172.28.14.55:9000/product/list
请求参数
http://172.28.14.55:9000/product/list?token=abc1
请求方式
http://172.28.14.55:9000/product/list?token=abc1
时间设置
http://172.28.14.55:9000/product/info?id=123&token=abc1
远程地址
规定远程地址为 172.28.14.52 则使用 localhost 的时候,无法访问
请求头参数设置
网关过滤器
PrefixPath
- PrefixPath=/product #将 /1 重写为 /product/1
RewritePath
http://172.28.14.55:9000/api-gateway/product/info?id=123&token=abc1
- RewritePath=/api-gateway(?<segment>/?.*), $\{segment} #将/api-gateway/product/info?id=123&token=abc1 重写为 /product/info?id=123&token=abc1
StripPrefix
http://172.28.14.55:9000/api-gateway/test/api/product/info?id=123&token=abc1
- RewritePath=/api-gateway(?<segment>/?.*), $\{segment} #将/api-gateway/product/info?id=123&token=abc1 重写为 /product/info?id=123&token=abc1
- StripPrefix=2 #过滤掉前两个路径个数,将/api-gateway/test/api/product/info?id=123&token=abc1 第一个重写为 /test/api/product/info?id=123&token=abc1 第二个重写为 /product/info?id=123&token=abc1 ,
SetPath
http://172.28.14.55:9000/api-gateway/info?id=123&token=abc1
predicates: #断言(判断条件,相当于 zuul 中的 shouldFilter
- Path=/product/**, /api-gateway/{segment} #匹配对应的 url 的请求,将匹配到的请求追加在目标 url 之后,lb://service-provider/**
filters: #网关过滤器
- SetPath=/product/{segment} #将 predicates - Path 中的{segment} 添加到 /product/{segment} 将 /api-gateway/info?id=123&token=abc1 重写为 /product/info?id=123&token=abc1
AddRequestParameter
predicates: #断言(判断条件,相当于 zuul 中的 shouldFilter
- Path=/product/**, /api-gateway/{segment} #匹配对应的 url 的请求,将匹配到的请求追加在目标 url 之后,lb://service-provider/**
- Query=token, abc. #匹配请求参数中包含token 并且参数值满足正则表达式 abc. 的请求
- Method=GET #匹配任意 GET 请求
- After=2020-12-29T11:12:58.000+08:00[Asia/Shanghai] #匹配北京时间 2020-12-29 11:12:58之后的时间,能够正常访问
- RemoteAddr=172.28.14.55/0 #匹配远程地址请求是RemoteAddr 的请求, 0 表示子网掩码(表示所有网段)
filters: #网关过滤器
- SetPath=/product/{segment} #将 predicates - Path 中的{segment} 添加到 /product/{segment} 将 /api-gateway/info?id=123&token=abc1 重写为 /product/info?id=123&token=abc1
- AddRequestParameter=flag, 10086 #在请求中添加 flag=10086
SetStatus
- SetStatus=NOT_FOUND #任何情况下相应的 http 请求的状态都是404 或者请求的枚举 NOT_FOUND
接口正常的相应,但是返回的状态码是 404
过滤器
自定义过滤器
创建过滤器
/**
* 自定义网关过滤器
* @author yangLongFei 2020-12-29-15:07
*/
public class CustomGatewayFilter implements GatewayFilter, Ordered {
/**
* 过滤器业务逻辑
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println(" !!!!!!!!!!!!!!自定义网关过滤器 !!!!!!!!!!!!!!");
//继续向下执行
return chain.filter(exchange);
}
/**
* 过滤器执行顺序,数值越小,优先级越高
*/
@Override
public int getOrder() {
return 1;
}
}
注册过滤器
/**
* 网关路由配置类
*
* @author yangLongFei 2020-12-29-15:11
*/
@Configuration
public class GateWayRoutesConfiguration {
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder locatorBuilder) {
System.out.println(".......................自定义过滤器.......................");
return locatorBuilder.routes().route(route ->
route
//断言
.path("/product/**")
//目标 url, 路由到微服务地址
.uri("lb://service-provider")
//注册自定义网关过滤器
.filters(new CustomGatewayFilter())
// 路由唯一id
.id("product-service-custom-filter"))
.build();
}
}
全局过滤器
全局过滤器,使用 @Component 进行注册, 实现的是 GlobalFitler 接口
/**
* 自定义全局过滤器
* @author yangLongFei 2020-12-29-15:51
*/
@Component
public class GlobalGatewayFilter implements GlobalFilter, Ordered {
/**
* 过滤器业务逻辑
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println(" ??????????????自定义全局过滤器 ??????????????");
//继续向下执行
return chain.filter(exchange);
}
/**
* 过滤器执行顺序,数值越小,优先级越高
*/
@Override
public int getOrder() {
return -1;
}
}
统一鉴权(使用的全局过滤器)
/**
* 权限验证过滤器
* @author yangLongFei 2020-12-29-15:59
*/
@Component
@Slf4j
public class AccessGlobalGatewayFilter implements GlobalFilter, Ordered {
/**
* 过滤器业务逻辑
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println(" 。。。。。。。。。。。。。。权限验证过滤器 。。。。。。。。。。。。。。");
//获取请求参数
String token = exchange.getRequest().getQueryParams().getFirst("token");
//业务逻辑处理
if (StringUtils.isEmpty(token)) {
log.warn(" tokenn is null ........... ");
ServerHttpResponse response = exchange.getResponse();
//响应类型
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
//相应转态码, 401 没有权限访问
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//相应内容
String messge = "{\"message\": \"10000100001000000 "+ HttpStatus.UNAUTHORIZED.getReasonPhrase()+"\" }";
DataBuffer wrap = response.bufferFactory().wrap(messge.getBytes());
//请求结束,不执行后面的请求
return response.writeWith(Mono.just(wrap));
}
// 使用token 进行身份验证
log.info("token is ok ! ");
log.info("value : " + token);
//继续向下执行
return chain.filter(exchange);
}
/**
* 过滤器执行顺序,数值越小,优先级越高
*/
@Override
public int getOrder() {
return 3;
}
}
通过上面的执行,依次经历
??????????????自定义全局过滤器 ??????????????
!!!!!!!!!!!!!!自定义网关过滤器 !!!!!!!!!!!!!!
。。。。。。。。。。。。。。权限验证过滤器 。。。。。。。。。。。。。。
但是最后,没有执行 配置文件中配置的 过滤器,因为执行了自定义的网关过滤器,
每个请求,除了全局过滤器外,只能匹配一个 非全局过滤器(通过测试时这样的)
路由唯一id 不一样的时候,默认先执行 自定义的网关过滤器
路由唯一id 一样的时候, 默认执行 自定义的网关过滤器
总的来说,先执行自定义网关过滤器,没有匹配成功执行,配置文件中的路由过滤器(自定义的网关过滤器可以和配置文件中的路由过滤器的 id 一样)
限流过滤器(网关限流)
限流算法
1.计数器算法
如果在 59秒之前一直没有请求,在59秒的时候发过来100个请求,然后在第二个 0 秒的时候又来了100 个请求,就造成服务器的压力过大
2.漏桶算法(Leaky Bucket)
使用队列的机制实现
会导致流量请求的堆积,不能及时的进行处理热点的请求
3.令牌桶算法(Token Bucket)
能够处理,突发的热点请求,也能以一定的速率去执行请求兼顾漏桶算法的优点
redis令牌桶
redis, 快速安装: https://blog.youkuaiyun.com/yang_zzu/article/details/105081615
docker, 安装: https://blog.youkuaiyun.com/yang_zzu/article/details/104469902
3.1 添加依赖
<!-- 添加 spring data redis reactive 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- commons-pool2 对象池依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
3.2 设置限流规则
/**
* 限流配置(redis 令牌桶)
* @author yangLongFei 2020-12-30-19:56
*/
@Configuration
public class KeyResolverConfiguration {
/**
* 限流规则
*/
@Bean
public KeyResolver pathKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
}
}
3.3 修改配置文件
filters: #网关过滤器
- name: RequestRateLimiter #限流过滤器
args:
redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充速率
redis-rate-limiter.burstCapacity: 3 #令牌桶总容量
# key-resolver: "#{@pathKeyResolver}" #使用 SpEl 表达式按名称应用bean, 请求路径限流
# key-resolver: "#{@parameterKeyResolver}" #使用 SpEl 表达式按名称应用bean, 请求参数限流
key-resolver: "#{@ipKeyResolver}" #使用 SpEl 表达式按名称应用bean, ip限流
http://192.168.43.179:9000/product/info?id=123&token=abc1
多次访问接口后,会被限流,返回429状态码
Sentinel 限流(gateway-server-sentinel-0)
官网地址: https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
引入依赖
当前模块引入依赖
<!-- sentinel getaway adapter 依赖 单独使用 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
父依赖
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
<com-alibaba-cloud.version>2.2.0.RELEASE</com-alibaba-cloud.version>
</properties>
<!-- 使用 springcloud 进行版本的管理操作 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${com-alibaba-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
添加限流规则
com.sys.yang.config.sentinel.GatewayConfiguration 都是在这个类上进行操作
查看官网写的demo,代码基本上是从上面拷贝的然后做了一点修改
sentinel 官网 : https://github.com/alibaba/Sentinel/wiki/
sentinel-demo : https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-spring-cloud-gateway
/**
* 网关限流规则
*/
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("service-provider123") // spring.cloud.gateway.routes.id ,即路由id 的值,
.setCount(3) //限流阈值
.setIntervalSec(60) //统计时间窗口,单位是秒,默认1秒
);
// 加载网关限流规则
GatewayRuleManager.loadRules(rules);
}
doInit , 将自己写的限流规则,通过 spring 进行初始化,写完方法后,将方法在这里进行调用加载
/**
* spring 容器初始化的时候执行该方法
*/
@PostConstruct
public void doInit() {
//限流分组
// initCustomizedApis();
// 加载网关阻塞规则
initGatewayRules();
//加载限流异常处理器
// initBlockHandler();
}
注意: GatewayFlowRule 的内容是 spring.cloud.gateway.routes.id ,即路由id 的值,
自定义异常处理器
/**
* 自定义限流异常处理器
*/
private void initBlockHandler() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map<String, String> result = new HashMap<>();
result.put("code", String.valueOf(HttpStatus.TOO_MANY_REQUESTS.value()));
result.put("message", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase());
result.put("routeId", "service-provider123");
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(result));
}
};
//加载自定义限流异常处理器
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
限流分组
进行分组
/**
* 限流分组
*/
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
// product-api 组
ApiDefinition api1 = new ApiDefinition("product-api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/sentinelProduct/**"));
//匹配 /product 及其子路径的所有请求
add(new ApiPathPredicateItem().setPattern("/product/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
// order-api 组
ApiDefinition api2 = new ApiDefinition("order-api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
//匹配所有路径
add(new ApiPathPredicateItem().setPattern("/order/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
definitions.add(api1);
definitions.add(api2);
// 加载限流分组
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
注意: 在 doInit 中调用方法,进行加载
对每个组,设置限流规则
/**
* 网关限流规则
*/
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("service-provider123") // spring.cloud.gateway.routes.id ,即路由id 的值,
.setCount(6) //限流阈值
.setIntervalSec(60) //统计时间窗口,单位是秒,默认1秒
);
// -------------------------限流分组 start ------------------------
rules.add(new GatewayFlowRule("product-api")
.setCount(3)
.setIntervalSec(60));
rules.add(new GatewayFlowRule("order-api")
.setCount(3)
.setIntervalSec(60));
// -------------------------限流分组 end ------------------------
// 加载网关限流规则
GatewayRuleManager.loadRules(rules);
}
yml 配置文件
routes:
- id: service-provider123 #路由id, 唯一
uri: lb://service-provider #目标url, lb:// 更具服务名称从注册中心获得服务请求地址
predicates: #断言(判断条件,相当于 zuul 中的 shouldFilter
- Path=/product/**, /sentinelProduct/**, /api-gateway/{segment} #匹配对应的 url 的请求,将匹配到的请求追加在目标 url 之后,lb://service-provider/**
- Query=token, abc. #匹配请求参数中包含token 并且参数值满足正则表达式 abc. 的请求, abc 后面的点,匹配除换行符 \n 之外的任何单字符
- Method=GET #匹配任意 GET 请求
- After=2020-12-29T11:12:58.000+08:00[Asia/Shanghai] #匹配北京时间 2020-12-29 11:12:58之后的时间,能够正常访问
# filters: #网关过滤器
# - AddRequestParameter=flag, 10086 #在请求中添加 flag=10086 传递给接口
# - SetStatus=NOT_FOUND #任何情况下相应的 http 请求的状态都是404 或者请求的枚举 NOT_FOUND
- id: service-consumer
uri: lb://service-consumer
predicates: #断言(判断条件,相当于 zuul 中的 shouldFilter
- Path=/order/** #匹配对应的 url 的请求,将匹配到的请求追加在目标 url 之后
product-api 组,是生产者 provider 服务中的,
service-provider123 路由id ,是对 provider 服务的路由规则
网关限流规则中,
首先对, service-provider123 ,设置60秒访问6次
又对, product-api 组设置为访问3次,
组成员 /sentinelProduct/** 没有设置匹配策略
组成员 /product/** 设置了匹配策略 setMatchStrategy()
由于 product-api 中两个组成员是一组,所以访问的时候会出现:
情况1:
访问3次后,限流
http://172.28.14.55:5000/product/info?id=123&token=abc1
该请求能够正常访问,访问3次后限流
http://172.28.14.55:5000/sentinelProduct/list?token=abc4
情况2:
访问4次
http://172.28.14.55:5000/sentinelProduct/list?token=abc4
该请求能够正常访问,访问2次后,限流
http://172.28.14.55:5000/product/info?id=123&token=abc1
再次访问, 限流
http://172.28.14.55:5000/sentinelProduct/list?token=abc4
即:
service-provider123 匹配到的请求,只能访问6次,之后会被限流,组内的规则再对这 6 次请求进行进一步的划分
gateway高可用设置
gateway-server-sentinel-0 服务的配置文件,返回状态码的设置被取消了
getway-server-0 服务的配置文件,返回状态码被设置为404
nginx 配置反向代理,负载均衡
nginx启动
关闭的话,直接在任务管理器关闭
nginx 负载的时候,默认采用的是 轮询的方式,所以在请求的时候,会发现 返回码 会在 200 和 404 之间变换
http://172.28.14.55/product/info?id=123&token=abc1
源码地址
https://github.com/YANG-sty/study