系统的统一入口,封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,例如认证
、鉴权
、监控
、路由转发
等。
网关 = 路由转发 + 过滤器(编写额外功能)
作用就是把各个服务对外提供的API汇集起来,让外界看起来是一个统一的接口。
稳定与安全
提供更好的服务
什么是Spring Cloud Gateway?
网关作为流量的入口,常用的功能包括路由转发,权限校验,限流等。
Spring Cloud Gateway 是Spring Cloud官方推出的第二代网关框架,提供了微服务网关功能。定位于取代Netflix Zuul。相比 Zuul 来说,Spring Cloud Gateway提供更优秀的性能,更强大的有功能。
Spring Cloud Gateway 是由 WebFlux+Netty+Reactor实现的响应式的API 网关。它不能在传统的 servlet 容器中工作,也不能构建成 war 包。
Spring Cloud Gateway 旨在为微服务架构提供一种简单且有效的API路由的管理方式,并基于Filter 的方式提供网关的基本功能,例如说安全认证、监控、限流等等。
Spring Cloud Gateway 功能特性:
Spring Cloud Gateway主要包含:
Route:路由,一个路由包含ID、URI、Predicate(附加条件和内容,如当满足某种条件再进行路由转发)集合、Filter(在Gateway运行过程中Filter负责在代理服务『之前』或『之后』去做一些事情 )集合
网关客户端访问Gateway网关,Gateway中Handler Mapping对请求URL进行处理。处理完成后交换Web Handler,Web Handler会被Filter进行过滤。Filter中前半部分代码是处理请求的代码。处理完成后调用真实被代理的服务。被代理服务响应结果,结果会被Filter中后半部分代码进行操作,操作完成后把结果返回给Web Handler,再返回给Handler Mapping,最终响应给客户端。
路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和配置的路由匹配。Route规则参考:org.springframework.cloud.gateway.route.RouteDefinition
Java8中的断言函数,Spring Cloud Gateway中的断言函数类型是Spring5.0框架中的ServerWebExchange。断言函数允许开发者去定义匹配Http request中的任何信息,比如请求头和参数等。
SpringCloud Gateway中的filter分为Gateway Filler和Global Filter。Filter可以对请求和响应进行处理。
示例:
先引入依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
application.yml配置:
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
# Gateway配置
gateway:
# 路由规则
routes:
- id: order_route # 路由的唯一标识,此示例理由到订单服务
uri: http://127.0.0.1:8082 # 需要转发的地址
# 断言规则,用于路由规则的匹配
predicates:
- Path=/order-service/** # http://localhost:8088/order-service/order 会路由到 http://127.0.0.1:8082/order-service/order
filters:
- StripPrefix=1 # 转发之前去掉第一层路径,去掉后变成http://127.0.0.1:8082/order
集成Nacos注册中心:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
修改配置文件
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# Gateway配置
gateway:
# 路由规则
routes:
- id: order_route # 路由的唯一标识,此示例理由到订单服务
uri: lb://order-service # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡器
# 断言规则,用于路由规则的匹配
predicates:
- Path=/order-service/** # http://localhost:8088/order-service/order 会路由到 http://127.0.0.1:8082/order-service/order
filters:
- StripPrefix=1 # 转发之前去掉第一层路径,去掉后变成http://127.0.0.1:8082/order
或使用:
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# Gateway配置
gateway:
discovery:
locator:
enabled: true # 是否启动自动识别nacos服务,约定大于配置
lower-case-service-id: true # 把服务名称转换为小写,Eureka中默认都是大写
当请求gateway的时候,使用断言对请求进行匹配,如果匹配成功就路由转发,如果匹配失败就返回404。
断言Predicate:在Spring Cloud Gateway中断言实现org.springframework.cloud.gateway.handler.predicate.GatewayPredicate接口。其中类名符合XXXRoutePredicateFactory,其中XXX就是在配置文件中断言名称。Path=/server/book/** 实际使用的就是PathRoutePredicateFactory。
参考示例:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
BeforeRoutePredicateFactory:接收一个日期参数,判断请求日期是否早于指定日期。
AfterRoutePredicateFactory:设置在指定时间点之后。
BetweenRoutePredicateFactory:请求时必须在设定的时间范围内容,才进行路由转发。
spring:
cloud:
gateway:
routes:
- id: between_route
uri: https://example.org
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
RemoteAddrRoutePredicateFactory:设置允许访问的客户端地址。接收一个IP地址端,判断请求主机地址是否在地址段中。
- RemoteAddr=192.168.1.1/24
CookieRoutePredicateFactory:接收两个参数,cookie名字和一个正则表达式,判断请求cookie是否具有给定名称且值与正则表达式匹配。设置请求中包含指定Cookie名和满足特定正则要求的值(Cookie必须有两个值,第一个Cookie包含的参数名,第二个表示参数对应的值(可以正则表达式))。
- Cookie=chocolate, ch.p
HeaderRoutePredicateFactory:接收两个参数,标题名称和正确表达式。判断请求Header是否具有给定名称且值与正则表达式匹配。设置请求头中必须包含的内容(参数、参数值)。
- Header=X-Request-Id, \d+
HostRoutePredicateFactory:接收一个参数,主机名模式,判断请求的Host是否满足匹配规则。设置匹配请求参数中Host参数的值。(支持?匹配一个字符,*匹配0个或多个字符,**匹配0个或多个目录)。
- Host=**.somehost.org,**.anotherhost.org
MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。设置允许的请求方式。
- Method=GET,POST
PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。
spring:
cloud:
gateway:
routes:
- id: path_route
uri: https://example.org
predicates:
- Path=/red/{segment},/blue/{segment}
QueryRoutePredicateFactory:接收两个参数,请求param和正则表达式,判断请求参数是否具有指定名称且值与正则表达式匹配。设置必须包含的参数名。
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=green
WeightRoutePredicateFactory:接收一个[组名,权重],然后对于同一个组内的路由按照权重转发。设置负载均衡中权重。同一组中URI进行负载均衡。(语法:Weight=组名,负责均衡权重),默认条件是标准轮询。分组名称必须相同。
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
自定义路由断言工厂需要继承AbstractRoutePredicateFactory类,重写apply方法的逻辑,在apply方法中可以通过exchange.getRequest()拿到ServletHttpRequest对象,从而可以获取到请求的参数、请求方式、请求头等信息。
RoutePredicateFactory
作为结尾import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
@Slf4j
@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
// 构造函数
public CheckAuthRoutePredicateFactory() {
super(CheckAuthRoutePredicateFactory.Config.class);
}
// 接收的参数需要结合shortcutFieldOrder进行绑定
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("name");
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
String name = config.getName();
if (Objects.equals(name, "zhangsan")) {
return true;
}
return false;
}
};
}
// 用于接收配置文件中断言的信息
@Validated
public static class Config {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
在路由转发到代理服务之前和代理服务返回结果之后额外做的事情。Filter执行了说明断言条件通过了。
在Spring Cloud Gateway的路由中Filter分为:
内置Filter工厂,都是org.springframework.cloud.gateway.filter.GatewayFilter实现类
Gateway内置了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加、剔除响应头,添加、去除参数等。
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
过滤器工厂 | 作用 | 参数 |
---|---|---|
AddRequestHeader | 为原始请求添加Header | Header的名称及值,参数和值之间使用逗号分隔 |
AddRequestParameter | 为原始请求添加请求参数 | 添加请求表单参数,多个参数需要有多个过滤器 |
AddResponseHeader | 添加响应头 | 参数和值之间使用逗号分隔 |
DedupeResponseHeader | 剔除响应头中重复的值 | DedupeResponseHeader=响应头参数,策略(RETAIN_FIRST:默认值,保留第一个;RETAIN_LAST:保留最后一个;RETAIN_UNIQUE:保留唯一的) |
CircuitBreaker | 实现熔断时使用 | 支持Resilience4J |
FallbackHeaders | 为FallbackUri的请求头中添加具体的异常信息,降级的异常信息 | Header的名称 |
PrefixPath | 为满足条件的原始请求路径添加前缀 | 前缀路径 |
PreserveHostHeader | 为请求添加一个preserveHostHeader=true的属性,路油过滤器会检查该属性以决定是否要发送原始Host | 无 |
RequestRateLimiter | 限流过滤器,对于请求限流,限流算法为令牌桶 | 参考文档 |
RedirectTo | 将原始请求重定向到指定的URL | 有两个参数,status(http状态码)和url(重定向url)。其中status应该是300系列重定向状态码 |
RemoveHopByHopHeadersFilter | 为原始请求删除IETF组织规定的一系列Header | 默认就会启用,可以通过配置指定近删除哪些Header |
RemoveRequestHeader | 为原始请求删除某个Header | Header名称 |
RemoveResponseHeader | 为原始响应删除某个Header | Header名称 |
RewritePath | 重写原始的请求路径 | 原始路径正则表达式以及重写后路径的正则表达式 |
RewriteResponseHeader | 重写原始响应中的某个Header | Header名称,值的正则表达式,重写后的值 |
SaveSession | 在转发请求之前,强制执行WebSession::save操作 | 无 |
SecureHeaders | 为原始响应添加一系列起安全作用的响应头 | 无,支持修改这些安全响应头的值 |
SetPath | 修改原始的请求路径,功能与StripPrefix类似,语法更贴近restful(SetPath=/{segment}) | 修改后的路径 |
SetResponseHeader | 修改原始响应中某个Header的值 | Header名称,修改后的值 |
SetStatus | 修改原始响应的状态码 | Http状态码,可以是数字,也可以是字符串 |
StripPrefix | 用于截断原始请求的路径,跳过路由URI中前几段后发送给下游 | 使用数字表示要截断的路径的数量,(StripPrefix=1,跳过第一段URI ) |
Retry | 针对不同的响应进行重试 | retries、statuses、methods、senes |
RequestSize | 设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回413 Payload Too Large | 请求包大小,单位为字节,默认值为5M |
ModifyRequestBody | 在转发请求之前修改原始请求体内容 | 修改后的请求体内容 |
ModifyResponseBody | 修改原始响应体的内容 | 修改后的响应体内容 |
AddRequestHeader示例,在order-service的Controller中添加hello方法
/**
* 通过@RequestHeader注解来接收请求头参数
*
* @param color X-Request-color请求头参数的值
* @return X-Request-color请求头参数的值
*/
@GetMapping("/hello")
public String hello(@RequestHeader("X-Request-color") String color) {
log.info("请求头X-Request-color的值为:{}", color);
return color;
}
网关配置如下:
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# Gateway配置
gateway:
# 路由规则
routes:
- id: order_route # 路由的唯一标识,此示例理由到订单服务
uri: lb://order-service # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡器
# 断言规则,用于路由规则的匹配
predicates:
- Path=/**
filters:
- AddRequestHeader=X-Request-color, red # 添加请求头
PrefixPath示例:
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# Gateway配置
gateway:
# 路由规则
routes:
- id: order_route # 路由的唯一标识,此示例理由到订单服务
uri: lb://order-service # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡器
# 断言规则,用于路由规则的匹配
predicates:
- Path=/**
filters:
- AddRequestHeader=X-Request-color, red # 添加请求头
- PrefixPath=/myorder # 添加前缀,对应微服务需要配置context-path
继承AbstractNameValueGatewayFilterFactory
且我们的自定义名称必须要以GatewayFilterFactory
结尾并交给Spring管理。
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# Gateway配置
gateway:
# 路由规则
routes:
- id: order_route # 路由的唯一标识,此示例理由到订单服务
uri: lb://order-service # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡器
# 断言规则,用于路由规则的匹配
predicates:
- Path=/**
filters:
- PrefixPath=/myorder
- CheckAuth=zhangsan
自定义CheckAuthGatewayFilterFactory:
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.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@Component
public class CheckAuthGatewayFilterFactory
extends AbstractGatewayFilterFactory<CheckAuthGatewayFilterFactory.Config> {
public CheckAuthGatewayFilterFactory() {
super(CheckAuthGatewayFilterFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("name");
}
/**
* 最好使用断言,name必须为zhangsan才能正常访问,如果没有带参数也可以正常访问
*/
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取name参数
String name = exchange.getRequest().getQueryParams().getFirst("name");
if (StringUtils.hasText(name)) {
if (Objects.equals(name, config.getName())) {
// 正常访问
return chain.filter(exchange);
} else {
// 如果不等于zhangsan就失败返回404
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
return exchange.getResponse().setComplete();
}
}
// 正常请求
return chain.filter(exchange);
}
};
}
@Validated
public static class Config {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
局部过滤器和全局过滤器:
局部过滤器针对某个路由生效,需要在路由中配置。全局过滤器针对所有路由生效,一旦定义就会投入使用。
自定义全局过滤器:
需要实现GlobalFilter接口。
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 打印记录请求路径
*/
@Component
@Slf4j
public class LogFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("请求路径:{}", exchange.getRequest().getPath().value());
return chain.filter(exchange);
}
}
Reactor Netty 访问日志
要启用Reactor Netty 访问日志,请设置 -Dreactor.netty.http.server.accessLogEnabled=true 它必须是Java系统属性(环境变量),而不是Spring Boot 属性
在logback.xml中配置到打印文件
<appender name="accessLog" class="ch.qos.logback.core.FileAppender">
<file>access_log.logfile>
<encoder>
<pattern>%msg%npattern>
encoder>
appender>
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="accessLog"/>
appender>
<logger name="reactor.netty.http.server.AccessLog" level="INFO" additivity="false">
<appender-ref ref="async"/>
logger>
参考文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration
解决CORS policy:no 'Access-Control-Allow-Origin' header is present on the requested resource
问题。
通过yml配置的方式:
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': # 允许跨域访问的资源
allowedOrigins: "https://docs.spring.io" # 跨域允许的来源
allowedMethods:
- GET
- POST
- PUT
- DELETE
通过Java配置方式
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
// 允许的Method
config.addAllowedMethod("*");
// 允许的来源
config.addAllowedOrigin("*");
// 允许的请求头参数
config.addAllowedHeader("*");
// WebFlux,允许访问的资源
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
常见的限流算法
以QPS(每秒查询率)为100举例:从第一个请求开始计时,当达到100以后,其它的请求都拒绝。如果1秒钟前200ms的请求数量已经达到了100,后面800ms中多少次的请求都被拒绝了,这种情况称为『突刺现象』。
和生活中漏桶一样,有一个水桶,下面有一个『漏眼』往出漏水,不管桶里有多少水,漏水的速率都是一样的。但是既然是一个桶,桶里装的水都是有上限的。当达到了上限,新进来的水就装不了了(主要出现在突然倒进来大量水的情况)。
令牌桶算法可以说是对漏桶算法的一种改进。
在桶中放令牌,请求获取令牌后才能继续执行。如果桶中没有令牌,请求可以选择进行等待或者直接拒绝。由于桶中令牌是按照一定速率放置的,所以可以一定程度解决突发访问。如果桶中令牌最多有100个,QPS最大为100。
网关作为内部系统外的一层屏障,对内起到一定的保护作用,限流便是其中之一。网关层的限流可以简单地针对不同路由进行限流,也可以针对业务的接口进行限流,或者根据接口的特征分组限流。
参考文档:https://sentinelguard.io/zh-cn/docs/api-gateway-flow-control.html
添加依赖:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-sentinel-gatewayartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
添加配置:
# 配置Sentinel
sentinel:
transport:
dashboard: 127.0.0.1:9000
使用 Gateway 中的RequestRateLimiter实现限流,RequestRateLimiter是基于Redis和Lua脚本实现的令牌桶算法。
使用Gateway实现服务降级
Spring Cloud Gateway可以使用Hystrix实现服务降级功能。
当Gateway进行路由转发时,如果发现下游服务连接超时允许进行服务降级。
实现原理:当连接超时时,使用Gateway自己的一个降级接口返回托底数据,保证程序继续运行。