网关的作用
性能:API
高可用,负载均衡,容错机制
安全:权限身份认证,脱敏,流量清洗,后端签(保证全链路可信调用)名,黑名单(非法调用的限制)
日志:日志记录,一旦涉及分布式,全链路跟踪必不可少
缓存:数据缓存
监控:记录请求响应数据,API
耗时分析,性能监控
限流:流量控制,错峰流控,可以定义多种限流规则
灰度:线上灰度部署,可以减小风险
路由:动态路由规则
核心概念
-
路由:路由是网关最基础的部分,路由信息由ID、目标URI、一组断言(路由规则)和一组过滤器组成。如果断言为真,则说明请求的URI和配置匹配
-
断言:
Java8
中的断言函数。Spring cloud Gateway中的断言函数输入类型是Spring 5.0框架中的ServerWebExchange
。Spring cloud Gateway中的断言函数允许开发者去定义匹配来自于Http Request
中的任何信息,比如请求头和参数等。 -
过滤器:一个标准的Spring Web Filter。Spring cloud Gateway中的filter分为两种类型,分别为Gateway Filter和Global Filter。过滤器将会对请求和相应进行处理。
搭建入门案例
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
配置文件
spring:
application:
name: gateway
# 路由规则
cloud:
gateway:
routes:
- id: user-service # 路由ID,全局唯一
uri: http://localhost:8080/ # 目标uri,路由到微服务地址
predicates: # 断言(判断条件)
- Path=/api/user/** # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
server:
port: 9999
logging:
level:
root: info
路由规则
Path
spring:
application:
name: gateway
# 路由规则
cloud:
gateway:
routes:
- id: user-service # 路由ID,全局唯一
uri: http://localhost:8080/ # 目标uri,路由到微服务地址
predicates: # 断言(判断条件)
- Path=/api/user/** # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
Query
spring:
application:
name: gateway
# 路由规则
cloud:
gateway:
routes:
- id: user-service # 路由ID,全局唯一
uri: http://localhost:8080/ # 目标uri,路由到微服务地址
predicates: # 断言(判断条件)
#- Query=token # 匹配请求参数中含有token的请求
- Query=token,abc. # 匹配请求参数中包含token并且参数值满足正则表达式abc的请求
Query=token,比如,http://localhost:9999/api/user/2?token=123
Query=token,abc. 比如,http://localhost:9999/api/user/2?token=abc1
Method
spring:
application:
name: gateway
# 路由规则
cloud:
gateway:
routes:
- id: user-service # 路由ID,全局唯一
uri: http://localhost:8080/ # 目标uri,路由到微服务地址
predicates: # 断言(判断条件)
- Method=GET # 匹配任意GET请求
Datetime
spring:
application:
name: gateway
# 路由规则
cloud:
gateway:
routes:
- id: user-service # 路由ID,全局唯一
uri: http://localhost:8080/ # 目标uri,路由到微服务地址
predicates: # 断言(判断条件)
# 匹配中国上海时间2022-02-02 20:20:20之后的请求
- After=2022-02-02T20:20:20.000+08:00[Asia/Shanghai]
RemoteAddr
spring:
application:
name: gateway
# 路由规则
cloud:
gateway:
routes:
- id: user-service # 路由ID,全局唯一
uri: http://localhost:8080/ # 目标uri,路由到微服务地址
predicates: # 断言(判断条件)
- RemoteAddr=12.12.7.250/0 # 匹配远程地址是RemoteAddr的请求,0表示子网掩码
RemoteAddr=12.12.7.250/0
,比如 http://12.12.7.250:9999/api/user/2?token=abc1
Header
spring:
application:
name: gateway
# 路由规则
cloud:
gateway:
routes:
- id: user-service # 路由ID,全局唯一
uri: http://localhost:8080/ # 目标uri,路由到微服务地址
predicates: # 断言(判断条件)
# 匹配请求头包含request_id并且其值满足正则表达式 \d+的请求
- Header=request_id,\d+
动态路由(服务发现的路由规则)
动态路由就是面向服务的路由,spring cloud gateway支持与eureka整合,根据serviceId
自动注册中心获取服务地址并转发请求,这样做的好处是不仅可以通过单点来访问所有的服务,而且再添加或移除服务实例时不用修改Gateway的路由配置。
添加依赖
<!-- eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置文件
spring:
application:
name: gateway
# 路由规则
cloud:
gateway:
routes:
- id: user-service # 路由ID,全局唯一
uri: lb://user # lb://根据服务名称从注册中心获取服务请求地址
predicates: # 断言(判断条件)
- Path=/api/user/** # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
过滤器
spring cloud gateway根据作用范围划分为 GatewayFilter
和GlobalFilter
,两者区别如下
GatewayFilter
:网关过滤器,需要通过spring.cloud.gateway.routes.filters
配置在具体的路由下,只作用在当前路由上或者通过spring.cloud.gataway.default-filters
配置在全局,作用在所有路由上。
GlobalFilter
:全局过滤器,不需要在配置文件中配置,作用在所有的路由上。最终通过GatewayFilterAdapter
包装成GatewayFilterChain
可识别的过滤器。它为请求业务以及路由的URI转换为真实业务服务请求地址的核心过滤器,不需要配置系统初始化时加载,并作用在每个路由上。
网关过滤器GatewayFilter
Path路径过滤器
RewritePath GatewayFilter Factory
该RewritePath
GatewayFilter
工厂采用的路径regexp
参数和replacement
参数。这使用 Java 正则表达式来灵活地重写请求路径。
spring:
application:
name: gateway
# 路由规则
cloud:
gateway:
routes:
- id: user-service # 路由ID,全局唯一
uri: lb://user # lb://根据服务名称从注册中心获取服务请求地址
predicates: # 断言(判断条件)
# 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
- Path=/api/user/**,/gateway/**
filters:
# 将/gateway/api/user/1,重写为/api/user/1
# 注:在YAML 的格式中使用$\来代替$。
- RewritePath=/gateway/?(?<segment>.*),/$\{segment}
注:在YAML
的格式中使用KaTeX parse error: Undefined control sequence: \来 at position 1: \̲来̲代替。
PrefixPathGatewayFilterFacotry
spring:
application:
name: gateway
# 路由规则
cloud:
gateway:
routes:
- id: user-service # 路由ID,全局唯一
uri: lb://user # lb://根据服务名称从注册中心获取服务请求地址
predicates: # 断言(判断条件)
- Path=/** # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
filters:
# 将/1重写为/api/user/1
- PrefixPath=/api/user
StripPrefixGatewayFilterFactory
StripPrefix
网关过滤器工厂采用一个参数StripPrefix
,该参数表示将再请求发送到下游之前从请求中剥离的路径个数
spring:
application:
name: gateway
# 路由规则
cloud:
gateway:
routes:
- id: user-service # 路由ID,全局唯一
uri: lb://user # lb://根据服务名称从注册中心获取服务请求地址
predicates: # 断言(判断条件)
- Path=/** # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
filters:
# 将/a/b/api/user/2 重写为/api/user/2
- StripPrefix=2
SetPathGatewayFilterFactory
SetPath
网关过滤器工厂采用路径模板参数,它提供了一种通过允许模板化路径段来操作请求路径的简单方法,使用了Spring Framework中的uri
模板,允许多个匹配段。
spring:
application:
name: gateway
# 路由规则
cloud:
gateway:
routes:
- id: user-service # 路由ID,全局唯一
uri: lb://user # lb://根据服务名称从注册中心获取服务请求地址
predicates: # 断言(判断条件)
- Path=/a/b/{segment} # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
filters:
# 将/a/b/2,重写为/api/user/2
- SetPath=/api/user/{segment}
参数过滤器
AddRequestParameter
网关过滤器工厂会将指定参数添加到匹配到的下游请求中。
spring:
application:
name: gateway
# 路由规则
cloud:
gateway:
routes:
- id: user-service # 路由ID,全局唯一
uri: lb://user # lb://根据服务名称从注册中心获取服务请求地址
predicates: # 断言(判断条件)
- Path=/api/user/** # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
filters:
# 在下游请求中添加flag参数
- AddRequestParameter=flag,1
状态过滤器
Status状态过滤器
SetStatus
网关过滤器工厂采用单个状态参数,它必须是有效的Spring HttpStatus
。它可以是整数404或者枚举NOT_FOUND的字符串表示。
spring:
application:
name: gateway
# 路由规则
cloud:
gateway:
routes:
- id: user-service # 路由ID,全局唯一
uri: lb://user # lb://根据服务名称从注册中心获取服务请求地址
predicates: # 断言(判断条件)
- Path=/api/user/** # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
filters:
# 任何情况下,响应的HTTP状态都将设置为404
- SetStatus=404
自定义网关过滤器
自定义网关过滤器需要实现以下两个接口:GatewayFilter
,Ordered
创建过滤器
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 自定义网关过滤器
*/
public class CustomerGatewayFilter implements GatewayFilter , Ordered {
private Logger log= LoggerFactory.getLogger(CustomerGatewayFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("自定义网关过滤器被执行--");
// 继续向下执行,如果不满足则不调用chain.filter
return chain.filter(exchange);
}
/**
* 过滤器执行顺序,数值越小,优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
注册网关过滤器
/**
* 网关路由配置类
*/
@Configuration
public class GatewayRoutesConfiguration {
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder){
return builder.routes().route(r -> r
// 断言(判断条件)
.path("/api/user/**")
// 目标URI,路由到微服务的地址
.uri("lb://user")
// 注册自定义网关过滤器
.filter(new CustomerGatewayFilter())
// 路由ID,唯一
.id("user-service2")).build();
}
}
自定义过滤器工厂
在上面的自定义过滤器中,有没有办法自定义过滤器工厂类呢?这样就可以在配置文件中配置过滤器了。现在需要实现一个过滤器工厂。
自定义过滤器工厂继承AbstractGatewayFilterFactory
,且自定义类名应按照"名称"+GatewayFilterFactory
package com.gateway;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
/**
* 命名规则必须按照"名称"+GatewayFilterFactory
*/
public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory <RequestTimeGatewayFilterFactory.Config>{
private Logger log= LoggerFactory.getLogger(RequestTimeGatewayFilterFactory.class);
private static final String KEY = "print";
public RequestTimeGatewayFilterFactory(){
super(Config.class);
}
/**
* 通过shortcutFieldOrder方法设置Config配置类中的属性,
* 默认按照shortcutFieldOrder顺序依次赋值
*/
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(KEY);
}
@Override
public GatewayFilter apply(Config config) {
// grab configuration from Config object
return (exchange, chain) -> {
// //If you want to build a "pre" filter you need to manipulate the
// //request before calling chain.filter
// ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
// //use builder to manipulate the request
// return chain.filter(exchange.mutate().request(builder.build()).build());
Long beginTime=System.currentTimeMillis();
log.info("开始时间:{}",beginTime);
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
//ServerHttpResponse response = exchange.getResponse();
//Manipulate the response in some way
Long endTime=System.currentTimeMillis();
if(config.isPrint()){
log.info("请求耗时:{}",endTime-beginTime);
}
}));
};
}
public static class Config{
//Put the configuration properties for your filter here
private boolean print;
public boolean isPrint(){
return print;
}
public void setPrint(boolean print){
this.print=print;
}
}
}
@Bean
public RequestTimeGatewayFilterFactory elapsedGatewayFilterFactory() {
return new RequestTimeGatewayFilterFactory();
}
配置文件
spring:
application:
name: gateway
# 路由规则
cloud:
gateway:
routes:
- id: user-service # 路由ID,全局唯一
uri: lb://user # lb://根据服务名称从注册中心获取服务请求地址
predicates: # 断言(判断条件)
- Path=/api/user/** # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
filters:
# 限流过滤器
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充速率
redis-rate-limiter.burstCapacity: 2 # 令牌桶总容量
key-resolver: '#{@keyResolver}' # 使用SpEL表达式按名称引用bean
# 自定义的过滤器,参数print=true
- RequestTime=true
全局过滤器
GlobalFilter:
全局过滤器,不需要在配置文件中配置,作用在所有的路由上。最终通过GatewayFilterAdapter
包装成GatewayFilterChain
可识别的过滤器。它为请求业务以及路由的URI转换为真实业务服务请求地址的核心过滤器,不需要配置系统初始化时加载,并作用在每个路由上。
包括:
通过全局过滤器统一鉴权
接下来我们自定义全局过滤器通过token判断用户是否登陆,完成统一鉴权
创建过滤器
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 权限验证过滤器
*/
@Component
public class AccessFilter implements GlobalFilter, Ordered {
private Logger log= LoggerFactory.getLogger(AccessFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token=exchange.getRequest().getQueryParams().getFirst("token");
if(token==null){
log.warn("token is null");
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
DataBuffer dataBuffer=response.bufferFactory().wrap("result".getBytes());
return response.writeWith(Mono.just(dataBuffer));
}
log.info("token is ok");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 1;
}
}
网关限流
常见的限流算法有:
- 计数器算法:时间范围内限制固定次数
- 漏桶算法:往桶中任意速率流入水,以一定速率流出水。当水超过桶流量则丢弃。会导致请求堆积,网关压力过大;丢弃请求;该算法只要是保护微服务,伤害自己。
- 令牌桶算法:基于漏桶算法,能够限制请求的速率,也能允许一定程度的突发调用。该算法,存在一个桶,用于存放固定数量的令牌。算法存在一种机制,以一定的速率往桶中放令牌。每次请求调用都要先获取令牌,只有拿到令牌,才能继续执行,否则选择等待可用的令牌。或者直接拒绝。放令牌动作是持续不断的进行,如果桶中的令牌达到上线,就丢弃令牌。
spring cloud gateway内部使用的就是该算法,大致描述如下:
- 所有请求在处理之前需要拿到一个可用的令牌才会被处理;
- 根据限流大小,设置按照一定的速率往桶中添加令牌
- 桶设置最大的放置令牌限制,当桶满了,新添加的令牌就会被丢弃或者拒绝。
- 请求到达后,首先获取令牌桶中的令牌,拿着令牌才可以进行其他业务逻辑。处理业务逻辑之后,将令牌直接删除。
- 令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流。
Gateway限流
添加依赖
<!--spring dta redis reactive依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- redis common pool2对象池依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
配置文件
spring:
application:
name: gateway
# 路由规则
cloud:
gateway:
routes:
- id: user-service # 路由ID,全局唯一
uri: lb://user # lb://根据服务名称从注册中心获取服务请求地址
predicates: # 断言(判断条件)
- Path=/api/user/** # 匹配对应的URL的请求,将匹配到的请求追加到目标URI之后
filters:
# 限流过滤器
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充速率
redis-rate-limiter.burstCapacity: 2 # 令牌桶总容量
key-resolver: '#{@pathKeyResolver}' # 使用SpEL表达式按名称引用bean
注册限流规则类
@Configuration
public class KeyResolverConfiguration {
private Logger log= LoggerFactory.getLogger(KeyResolverConfiguration.class);
/**
* 按照URI限流
* @return
*/
@Bean
public KeyResolver pathKeyResolver(){
log.info("注册pathKeyResolver");
// JDK 1.8
return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
}
}
/**
* 按照参数限流
* @return
*/
@Bean
public KeyResolver keyResolver(){
log.info("注册pathKeyResolver");
// JDK 1.8
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}
/**
* 按照IP限流
* @return
*/
@Bean
public KeyResolver keyResolver(){
log.info("keyResolver");
// JDK 1.8
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
lver");
// JDK 1.8
return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
}
}
/**
* 按照参数限流
* @return
*/
@Bean
public KeyResolver keyResolver(){
log.info("注册pathKeyResolver");
// JDK 1.8
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}
/**
* 按照IP限流
* @return
*/
@Bean
public KeyResolver keyResolver(){
log.info("keyResolver");
// JDK 1.8
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}