五、服务网关Gateway
5.1 Gateway基础使用
-
创建api-gateway网关服务
-
导入相关依赖
pom.xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>3.1.1</version>
</dependency>
- 修改配置文件
application.yml
server:
port: 7000
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848 # 将gateway注册到nacos
gateway:
discovery:
locator:
enabled: true # 让gateway从nacos中获取服务信息
routes: # 路由数组配置,就是请求满足什么样的条件的时候转发到哪个微服务上
- id: order-service # 当前路由发的标识,要求唯一
url: http://localhost:8091 #请求最终要被转发的地址
order: 1 # 路由的优先级,数字越小代表路由的优先级越高
# 断言规则默认由RoutePredicateFactory接口的子类提供,如下面的Path实际上就是PathRoutePredicateFactory
predicates: # 断言条件判断,返回值是Boolean类型的,表示转发请求要求满足的条件
- Path=/product-serv/** # 请求满足Path指定的规则是,次路由才会正常转发
filters: # 过滤器,在请求传递过程中,对请求做一些手脚
- StripPrefix=1 # 在请求转发之前去掉一层路径
- id: product-service # 当前路由发的标识,要求唯一
url: lb://service-product # lb:指定是负载均衡,后面跟的是具体微服务在nacos中的标识
order: 1 # 路由的优先级,数字越小代表路由的优先级越高
predicates: # 断言条件判断,返回值是Boolean类型的,表示转发请求要求满足的条件
- Path=/product-serv/** # 请求满足Path指定的规则是,次路由才会正常转发
filters: # 过滤器,在请求传递过程中,对请求做一些手脚
- StripPrefix=1 # 在请求转发之前去掉一层路径
5.2 Gateway的执行流程
5.3 断言
Predicate(断言)用于进行条件判断,只有断言都返回真,才会真正的执行路由,简言之就是路由转发触发的条件
5.3.1 内置路由断言工厂
SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与http请求的不同属性匹配,SpringCloud Gateway提供的内置
断言规则都是RoutePredicateFactory
接口的子类,默认后缀名都以RoutePredicateFactory结尾,配置是只要写前缀即可
5.3.1.1 基于Datetime类型的断言工厂:此类型的断言根据时间做判断
- AfterRoutePredicateFactory: 接受一个日期参数,判断请求日期是否晚于指定日期
- BeforeRoutePredicateFactory: 接受一个日期参数,判断请求时期是否早于指定日期
- BetweenRoutePredicateFactory:接受两个日期参数,判断请求日期是否在指定时间段内
5.3.1.2 基于远程地址的断言工厂
- RemoteAddrRoutePredicateFactory:接受一个IP地址段,判断请求主机是否在地址段中
-RemoteAddr=192.168.1.1/24
5.3.1.3 基于Cookie的断言工厂
- CookieRoutePredicateFactory:接受两个参数,cookie名字和一个正则表达式,判断cookie是否具有给定名称且值与正则表达式匹配
-Cookie=age,\d
5.3.1.4 基于Header的断言工厂
- HeaderRoutePredicateFactory:接受两个参数,标题名称和正则表达式。判断请求Header是否具有给定名称且值与正则表达式匹配
-Header=X-request-Id,\d+
5.3.1.5 基于路由权重的断言工厂
- WeightRoutePredicateFactory:接受一个【组名,权重】,然后对于同一个组内的路由按照权重转发
routes:
- id: weight_route
url: host1
predicates:
- Path=/product/**
- Weigth=gourp3,1
- id: weight_route
url: host2
predicates:
- Path=/product/**
- Weigth=gourp3,9
5.3.2 自定义路由断言工厂
场景:应用只允许让age在(min,max)之间的人来访问
- 在配置文件中添加一个Age的断言配置
spring:
cloud:
gateway:
routes: # 路由数组配置,就是请求满足什么样的条件的时候转发到哪个微服务上
- id: product-service # 当前路由发的标识,要求唯一
url: lb://service-product # lb:指定是负载均衡,后面跟的是具体微服务在nacos中的标识
predicates: # 断言条件判断,返回值是Boolean类型的,表示转发请求要求满足的条件
- Path=/product-serv/** # 请求满足Path指定的规则是,次路由才会正常转发
- Age=16,32
filters: # 过滤器,在请求传递过程中,对请求做一些手脚
- StripPrefix=1 # 在请求转发之前去掉一层路径
- 自定义一个断言工厂,实现断言方法
package com.example.apigateway.predicate;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/**
* 自定义的路由断言工厂类
* 1. 名字必须是配置名+RoutePredicateFactory,即Age+RoutePredicateFactory
* 2. 必须继承AbstractRoutePredicateFactory<配置类>
*
*/
@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
public AgeRoutePredicateFactory() {
super(Config.class);
}
//用于从配置文件中获取参数值赋值到配置类中的属性上
@Override
public List<String> shortcutFieldOrder() {
//这里的顺序要跟配置文件中的参数顺序一致
return Arrays.asList("minAge","maxAge");
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
//从serverWebExchange获取传入的参数
String ageStr = serverWebExchange.getRequest().getQueryParams().getFirst("age");
if(StringUtils.isNotBlank(ageStr)){
int age = Integer.parseInt(ageStr);
return age > config.getMinAge() && age < config.getMaxAge();
}
return true;
}
};
}
// 配置类,用于接受配置文件中对应的参数
public static class Config{
private int minAge;
private int maxAge;
public int getMinAge() {
return minAge;
}
public void setMinAge(int minAge) {
this.minAge = minAge;
}
public int getMaxAge() {
return maxAge;
}
public void setMaxAge(int maxAge) {
this.maxAge = maxAge;
}
}
}
5.4 过滤器
在Gateway中,过滤器(filter)的生命周期只有两个:pre和post,主要作用就是在请求传递过程中,对请求和响应做一些手脚。
gateway中过滤器从作用范围分为两种类型:局部过滤器(GatewayFilter)和全局过滤器(GlobalFilter)
- PRE:这种过滤器在请求被路由之前调用。我们可以利用这种过滤器实现身份验证、在集群种种选择情趣的微服务、记录调试信息等
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可以用来为响应添加标准的Http Header、收集统计信息和指标、将响应从微服务发送给客户端等
5.4.1 局部过滤器
局部过滤器是针对单个路由的过滤器
5.4.1.1 内置局部过滤器
在Gateway中,内置了很多不同类型的网关路由过滤器,且命名方法与上面的断言器类似,过滤器均已
AbstractGatewayFilterFactory
结尾且都是AbstractGatewayFilterFactory
抽象类的子类,
要想使用内置过滤器,其规则就是子类中类名的前缀,如:StripPrefixGatewayFilterFactory
过滤器的配置规则就是StripPrefix=1
过滤器工厂 | 作用 | 参数 |
---|---|---|
AddRequestHeader | 为原始请求添加Header | Header的名称及值 |
AddRequesstParameter | 为原始请求添加请求参数 | 参数名称及值 |
AddResponseHeader | 为原始响应添加Header | Header的名称及值 |
DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名称及去重策略 |
FallbackHeader | 为fallbackUri的请求头中添加具体的异常信息 | Header的名称 |
Prefixpath | 为原始请求路径添加前缀 | 前缀路径 |
RequestRateLimiter | 用于对请求限流,限流算法为令牌桶 | keyResolver,rateLimiter,statusCode,denyEmptyKey,emptyKeyStatus |
redirectTo | 将原始请求重定向到指定的URL | Http状态码及重定向的url |
RewritePath | 重写原始的请求路径 | 原始路径正则表达式以及重写后路径的正则表达式 |
Savession | 在请求转发前,强制执行webSession::save操作 | 无 |
5.4.1.1 自定义局部过滤器
-
在配置文件中添加一个Log的过滤器配置
server: port: 7000 spring: application: name: api-gateway cloud: nacos: discovery: server-addr: localhost:8848 # \u5C06gateway\u6CE8\u518C\u5230nacos gateway: discovery: locator: enabled: true # 让gateway从nacos中获取服务信息 routes: # 路由数组配置,就是请求满足什么样的条件的时候转发到哪个微服务上 - id: product-service # 当前路由发的标识,要求唯一 url: lb://service-product predicates: - Path=/product-serv/** # 请求满足Path指定的规则是,次路由才会正常转发 filters: # 过滤器,在请求传递过程中,对请求做一些手脚 - StripPrefix=1 # 在请求转发之前去掉一层路径 - Log=true,fase # 控制日志是否开启自定义一个过滤器工厂
-
自定义一个过滤器工厂,实现方法
package com.example.apigateway.filter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
public LogGatewayFilterFactory(){
super(LogGatewayFilterFactory.Config.class);
}
/**
* 读取配置文件的参数,并赋值到配置类上
* @return
*/
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("consoleLog","cacheLog");
}
//过滤器逻辑
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if(config.isCacheLog()){//缓存日志开启
System.out.println("cacheLog已经开启");
}
if(config.isConsoleLog()){
System.out.println("consoleLog已经开启");
}
return chain.filter(exchange);
}
};
}
//配置类,接受配置参数
public static class Config {
private boolean consoleLog;
private boolean cacheLog;
public boolean isConsoleLog() {
return consoleLog;
}
public void setConsoleLog(boolean consoleLog) {
this.consoleLog = consoleLog;
}
public boolean isCacheLog() {
return cacheLog;
}
public void setCacheLog(boolean cacheLog) {
this.cacheLog = cacheLog;
}
}
}
5.4.2 全局过滤器
全局过滤器作用于所有路由,无需配置。通过全局过滤器可以实现对权限的同意校验,安全性验证等功能
4.6.2.1 内置全局过滤器
SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理如下:
4.6.2.2 自定义全局过滤器
场景:统一鉴权过滤器,用来校验所有请求参数中是否包含token,若不包含token则不转发路由,否则执行正常逻辑
- 实现GlobalGlobalFilter,Ordered两个接口,后交给spring容器管理即可
/**
* 统一鉴权全局过滤器
*/
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if(! "admin".equals(token)){//认知失败
System.out.println("认证失败了...");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//设置响应完成
return exchange.getResponse().setComplete();
}
//认证通过,继续往下执行
return chain.filter(exchange);
}
//标识当前过滤器的优先级,返回值越小,优先级越高
@Override
public int getOrder() {
return 0;
}
}
5.5 网关限流
sentinel提供了SpringCloud Gateway的适配模块,可以提供两种资源维度的限流
- route维度:即在Spring配置文件中配置的路由条目,资源名为对应的routeId
- 自定义API维度:用户可以利用sentinel提供的API来自定义一些API分组
5.5.1 route维度限流
- 导入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
- 编写配置类
基于sentinel的Gateway限流是通过其提供的Filter来完成的,使用时只需注入对应的SentinelGatewayFilter实例以及SentinelGatewayBlockExceptionHandler实例即可
package com.example.apigateway.configuration;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.*;
@Configurable
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodeConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodeConfigurer = serverCodecConfigurer;
}
//初始化一个限流的过滤器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter(){
return new SentinelGatewayFilter();
}
//配置初始化限流参数
@PostConstruct
public void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
//创建路由规则对象,参数为路由id
GatewayFlowRule flowRule = new GatewayFlowRule("product_route");
flowRule.setCount(1);//限流阈值
flowRule.setIntervalSec(1);//统计时间窗口,单位:秒
rules.add(flowRule);
GatewayRuleManager.loadRules(rules);
}
// 配置限流的异常处理器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler(){
return new SentinelGatewayBlockExceptionHandler(viewResolvers,serverCodeConfigurer);
}
// 自定义限流异常页面
@PostConstruct
public void initBlockHandlers(){
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap();
map.put("code",0);
map.put("message","接口被限流了");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
5.5.2 API分组维度
5.5.2.1 自定义API分组
自定义API分组是一种更细粒度的限流规则定义
package com.example.apigateway.configuration;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.*;
@Configurable
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodeConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodeConfigurer = serverCodecConfigurer;
}
//初始化一个限流的过滤器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
//配置初始化限流参数
@PostConstruct
public void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
GatewayFlowRule flowRule = new GatewayFlowRule("product_api1");
flowRule.setCount(1);//限流阈值
flowRule.setIntervalSec(1);//统计时间窗口,单位:秒
rules.add(flowRule);
GatewayFlowRule flowRule2 = new GatewayFlowRule("product_api2");
flowRule2.setCount(1);//限流阈值
flowRule2.setIntervalSec(1);//统计时间窗口,单位:秒
rules.add(flowRule2);
GatewayRuleManager.loadRules(rules);
}
// 配置限流的异常处理器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodeConfigurer);
}
// 自定义限流异常页面
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap();
map.put("code", 0);
map.put("message", "接口被限流了");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
//自定义API分组
@PostConstruct
private void initCustomizeApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("product_api1")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem()
// 以//product-server/product/api开头的请求
.setPattern("/product-server/product/api/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("product_api2")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem()
// /product-server/product/api2/demo 完全的url匹配
.setPattern("/product-server/product/api2/demo"));
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
}