简介
SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,目标是替代 Zuul
可以与Spring Cloud Discovery Client(如Eureka)、Ribbon、Hystrix等组件配合使用,实现路由转发、负载均衡、熔断、鉴权、路径重写、日志监控等
Gateway还内置了限流过滤器,实现了限流的功能。
是有 WebFlux+Netty+Reactor实现的响应式API网关
官网:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-starter
网关简介
网关为微服务架构的系统提供简单、有效且统一的API路由管理,作为系统的统一入口,提供内部服务的路由中转,给客户端提供统一的服务,可以实现一些和业务没有耦合的公用逻辑,主要功能包含认证、鉴权、路由转发、安全策略、防刷、流量控制、监控日志等。
网关在微服务架构中的位置:、
为什么需要网关服务:
对于微服务而言,微服务之间调用繁多,如果通过配置服务地址和接口来进行调用比较杂乱,采用网关后微服务都与网关进行交互,网关再找到对应服务及接口
流量网关和微服务网关
流量网关(如Nignx)是指提供全局性的、与后端业务应用无关的策略,例如 HTTPS证书卸载、Web防火墙、全局流量监控等。
微服务网关(如Spring Cloud Gateway)是指与业务紧耦合的、提供单个业务域级别的策略,如服务治理、身份认证等。
核心概念
路由(route):路由是构建网关的基本模块,它由ID,目标URI,断言集合和过滤器集合组成,如果断言为true,则匹配该路由。
断言(Predicate):参考Java8的java.util.function.Predicate,用来匹配HTTP请求中的所有内容,例如请求头或请求参数,如果请求与断言匹配结果返回一个布尔值。
过滤(filter):Spring框架中GatewayFilter的实例,使用过滤器,可以载请求被路由前或者后对请求进行处理。
断言说明
Spring Cloud Gataway 帮我们内置了很多 Predicates 功能
After:匹配在指定日期时间之后发生的请求。
Before:匹配在指定日期之前发生的请求。
Between:需要指定两个日期参数,设定一个时间区间,匹配此时间区间内的请求。
Cookie:需要指定两个参数,分别为name和regexp(正则表达式),也可以理解Key和Value,匹配具有给定名称且其值与正则表达式匹配的Cookie。
Header:需要两个参数header和regexp(正则表达式),也可以理解为Key和Value,匹配请求携带信息。
Host:匹配当前请求是否来自于设置的主机。
Method:可以设置一个或多个参数,匹配HTTP请求,比如GET、POST
Path:匹配指定路径下的请求,可以是多个用逗号分隔
Query:需要指定一个或者多个参数,一个必须参数和一个可选的正则表达式,匹配请求中是否包含第一个参数,如果有两个参数,则匹配请求中第一个参数的值是否符合正则表达式。
RemoteAddr:匹配指定IP或IP段,符合条件转发。
Weight:需要两个参数group和weight(int),实现了路由权重功能,按照路由权重选择同一个分组中的路由
- After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #这个时间之后的请求为true,否则为false
- Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai] #这个时间之前的请求为true,否则为false
- Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
- Cookie=myCookieKey,[a-z]+ # 匹配Cookie的key和value(正则表达式)
- Header=myHeaderKey, d+ # 一个 header 中属性名称和一个正则表达式
- Host=**.muxiaonong.com #匹配当前的主机地址发出的请求
- Method=POST,GET #多个用逗号分割
- Path=/foo/{segment} #匹配路径返回true,否则返回false
- Query=id,.+ # 一个是属性名一个为属性值,属性值可以是正则表达式;这里如果需要匹配多个参数,可以写多个- Query=;如果只写属性名,只要入参有该属性就返回true
- RemoteAddr=192.168.1.1/24 #支持通过设置某个 ip 区间号段的请求才会路由,(其中 192.168.1.1 是 IP 地址,24 是子网掩码)。
- Weight=groupName,8 #两个参数group和weight(int)权重数值,实现了路由权重功能
完整定义和快捷定义
完整定义:
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://imdony.org
predicates:
- name: Cookie
args:
name: mycookie
regexp: mycookievalue
快捷定义
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- Cookie=mycookie,mycookievalue
过滤说明
内置的Filter生命周期有两种:pre(业务逻辑之前)、post(业务逻辑之后)
自带的Filter分为两种: GateWayFilter(局部)、GlobalFilter(全局)
官网:
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#global-filters
局部过滤器需要在路由中配置才能生效
全局过滤器应用全部路由上,当然除了内置的全局过滤器,实际工作中还需要定制过滤器,全局过滤器不必在路由上配置,注入到IOC容器中即可全局生效。
自定义全局过滤器
@Slf4j
@Component
@Order(value = Integer.MIN_VALUE)
public class AccessLogGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//filter的前置处理
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().pathWithinApplication().value();
InetSocketAddress remoteAddress = request.getRemoteAddress();
return chain
//继续调用filter
.filter(exchange)
//filter的后置处理
.then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
HttpStatus statusCode = response.getStatusCode();
log.info("请求路径:{},远程IP地址:{},响应码:{}", path, remoteAddress, statusCode);
}));
}
}
@Order指定优先级,值越小,优先级越高
由于过滤器有 pre 和 post 两种类型,pre 类型过滤器如果 order 值越小,那么它就应该在pre过滤器链的顶层,post 类型过滤器如果 order 值越小,那么它就应该在 post 过滤器链的底层。
监控和指标功能
management:
endpoints:
web:
exposure:
include: gateway, metrics
endpoint:
gateway:
enabled: true
metrics:
export:
prometheus:
enabled: true
在这个示例中:
- management.endpoints.web.exposure.include配置了暴露的端点,包括gateway和metrics端点。
- management.endpoint.gateway.enabled启用了Gateway端点,用于查看Gateway的路由信息和状态。
- management.metrics.export.prometheus.enabled启用了Prometheus指标导出,可以将Gateway的指标数据导出到Prometheus监控系统中。
通过这样的配置,可以实现对Gateway的监控和指标功能,方便管理员监控和分析Gateway的运行状态和性能指标。
Gateway配置的监控功能可以监控以下内容:
1. 路由信息:可以查看当前配置的路由信息,包括路由规则、目标服务、匹配条件等。
2. 请求统计:可以监控请求的数量、响应时间、成功率等指标,帮助分析系统的负载情况和性能表现。
3. 错误信息:可以查看请求中出现的错误信息,帮助排查问题和进行故障诊断。
4. 流量控制:可以监控流量的分布情况,帮助进行流量控制和负载均衡。
5. 熔断器状态:可以查看熔断器的状态和触发情况,帮助监控系统的稳定性和可用性。
流程
当用户发出请求达到 GateWay 之后,会通过一些匹配条件,定位到真正的服务节点,并且在这个转发过程前后,进行一些细粒度的控制,其中 Predicate(断言) 是我们的匹配条件,Filter 是一个拦截器,有了这两点,再加上URL,就可以实现一个具体的路由,核心思想:路由转发+执行过滤器链
熔断降级
网关作为流量的入口,因此会有大量的请求进入网关,向其他服务发起调用,其他服务不可避免的会出现调用失败(超时、异常),失败时不能让请求堆积在网关上,需要快速失败并返回给客户端,想要实现这个要求,就必须在网关上做熔断、降级操作。
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: rateLimit_route
uri: http://localhost:8000
order: 0
predicates:
- Path=/test/**
filters:
- StripPrefix=1
- name: Hystrix
args:
name: fallbackCmdA
fallbackUri: forward:/fallbackA
hystrix.command.fallbackCmdA.execution.isolation.thread.timeoutInMilliseconds: 5000
过滤器Hystrix,作用是通过Hystrix进行熔断降级
当上游的请求,进入了Hystrix熔断降级机制时,就会调用fallbackUri配置的降级地址。需要注意的是,还需要单独设置Hystrix的commandKey的超时时间
fallbackUri配置的降级地址的代码如下:
@RestController
public class FallbackController {
@GetMapping("/fallbackA")
public Response fallbackA() {
Response response = new Response();
response.setCode("100");
response.setMessage("服务暂时不可用");
return response;
}
}
gateway项目搭建
添加依赖,依赖的版本根据自己的springboot的版本确定
<!-- 引入gateway网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency>
配置文件
server:
port: 8888
servlet:
context-path: /gateway-demo
spring:
application:
name: demo
cloud:
gateway:
#路由规则
routes:
- id: xgss1 #路由标识,要求唯一,名称任意
uri: http://localhost:8080 #请求被路由到的地址
order: 1 #优先级,数字越小优先级越高
#设置断言
predicates:
- Path=/gateway/xgss/** #满足该路径的才会被路由到uri地址
- Weight=group1,8 #同一分组按照权重进行分配流量,这里分配了80%,第一个group1是分组名,第二个参数是权重
filters:
- StripPrefix=1 # StripPrefix:去除原始请求路径中的前1级路径,即/gateway
- id: xgss2
uri: http://localhost:8081
predicates:
- Path=/gateway/xgss/**
- Weight=group1,2 #这里分配权重20%
filters:
- StripPrefix=1
logging:
file:
path: logs/
level:
com.xgss.demo: info
启动类跟普通springBoot项目的启动类一样
启动日志如下
当访问:localhost:8888/gateway/xgss/test 时,就会有80%的请求会路由到 8080, 20%的请求会路由到 8081,并且路径上去掉了gateway
配置文件说明
- spring.cloud.gateway.routes:定义路由规则的列表。
- spring.cloud.gateway.routes.id:路由的唯一标识符。
- spring.cloud.gateway.routes.uri:目标服务的URI。
- spring.cloud.gateway.routes.predicates:定义路由的匹配条件(断言),例如请求路径、请求方法等。
- spring.cloud.gateway.routes.filters:定义路由的过滤器,用于对请求进行预处理和后处理。
实例:
spring:
cloud:
gateway:
routes:
- id: example_route
uri: http://example.com
predicates:
- Path=/example/**
filters:
- AddRequestHeader=X-ExampleHeader, example-value
断言配置:
After:匹配在指定日期时间之后发生的请求。
Before:匹配在指定日期之前发生的请求。
Between:需要指定两个日期参数,设定一个时间区间,匹配此时间区间内的请求。
Cookie:需要指定两个参数,分别为name和regexp(正则表达式),也可以理解Key和Value,匹配具有给定名称且其值与正则表达式匹配的Cookie。
Header:需要两个参数header和regexp(正则表达式),也可以理解为Key和Value,匹配请求携带信息。
Host:匹配当前请求是否来自于设置的主机。
Method:可以设置一个或多个参数,匹配HTTP请求,比如GET、POST
Path:匹配指定路径下的请求,可以是多个用逗号分隔
Query:需要指定一个或者多个参数,一个必须参数和一个可选的正则表达式,匹配请求中是否包含第一个参数,如果有两个参数,则匹配请求中第一个参数的值是否符合正则表达式。
RemoteAddr:匹配指定IP或IP段,符合条件转发。
Weight:需要两个参数group和weight(int),实现了路由权重功能,按照路由权重选择同一个分组中的路由
匹配所有以/api/开头的请求路径。可以使用Ant风格的路径模式(如/api/**)或正则表达式来进行匹配。
predicates:
- Path=/api/**
匹配所有GET请求方法的请求。可以使用HTTP请求方法(如GET、POST、PUT等)进行匹配。
predicates:
- Method=GET
该配置将匹配具有X-Custom-Header请求头且值为custom-value的请求。
predicates:
- Header=X-Custom-Header, custom-value
匹配具有名为param1且值为value1的查询参数的请求。
predicates:
- Query=param1, value1
匹配具有名为cookie1且值为value1的Cookie的请求。
predicates:
- Cookie=cookie1, value1
过滤器配置:
可以使用过滤器对请求进行预处理和后处理。常用的内置过滤器包括:
- AddRequestHeader:添加请求头。
- AddResponseHeader:添加响应头。
- RewritePath:重写请求路径。
- Hystrix:添加熔断器功能。
- RateLimiter:添加限流功能。
spring:
cloud:
gateway:
routes:
- id: example_route
uri: http://example.com
predicates:
- Path=/example/**
filters:
- AddRequestHeader=X-ExampleHeader, example-value
- RewritePath=/example/(?<segment>.*), /${segment}
/example/(?<segment>.*)是一个正则表达式,用于匹配以/example/开头的请求路径,并捕获路径中的内容作为一个命名组(named group),命名为segment。
/${segment}是一个替换字符串,用于将捕获的路径内容作为替换的一部分。用于转义$符号,使其被解释为普通字符而不是正则表达式的特殊符号,${segment}表示引用捕获的命名组segment的值。
综合起来,这个表达式的意思是将以/example/开头的请求路径进行重写,将路径中的内容作为替换的一部分。例如,对于请求路径/example/foo,经过这个表达式的处理后,将被重写为/foo。
除了内置过滤器,还可以自定义过滤器来满足特定的需求。需要实现GatewayFilter接口,并注册到Spring容器
@Component
public class CustomFilter implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 自定义过滤器逻辑
return chain.filter(exchange);
}
}
spring:
cloud:
gateway:
routes:
- id: example_route
uri: http://example.com
predicates:
- Path=/example/**
filters:
- name: CustomFilter
gateway集成nacos注册中心
上述 demo 中并没有集成注册中心,每次路由配置都是指定固定的服务uri
缺点:
网关服务需要知道所有服务的域名或IP地址
一旦服务的域名或IP地址发生修改,路由配置中的 uri 就必须修改
无法实现负载均衡
那么此时我们可以集成的注册中心,使得网关能够从注册中心自动获取uri,并实现负载均衡
使用nacos作为注册中心:
增加依赖:
<!--nacos注册中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
启动类添加 @EnableDiscoveryClient 注解开启注册中心功能
配置文件配置 nacos 注册中心的地址:
nacos:
namespace: 856a40d7-6548-4494-bdb9-c44491865f63
url: 120.76.129.106:80
spring:
cloud:
nacos:
discovery:
server-addr: ${nacos.url}
namespace: ${nacos.namespace}
register-enabled: true
微服务网关路由配置:
spring:
cloud:
gateway:
routes:
- id: gateway-provider_1
## 使用了lb形式,从注册中心负载均衡的获取uri
uri: lb://gateway-provider
## 配置断言
predicates:
- Path=/gateway/provider/**
filters:
- AddResponseHeader=X-Response-Foo, Bar
路由配置中唯一不同的就是路由的 uri,格式:lb://service-name,这是固定写法:
在Spring Cloud Gateway中,lb:是用于定义路由的URI的一种特殊前缀,表示使用负载均衡的方式进行服务路由。
具体来说,当使用lb:前缀时,Spring Cloud Gateway会将请求路由到注册在服务注册中心的服务实例上,以实现负载均衡的效果。
使用lb:前缀进行负载均衡路由时,需要确保已经配置了服务注册中心,并且目标服务已经注册到服务注册中心中。
lb:固定格式,指的是从注册中心按照名称获取微服务,并遵循负载均衡策略
service-name:注册中心的服务名称,这里并不是IP地址形式的
为什么指定了 lb 就可以开启负载均衡,全局过滤器 LoadBalancerClientFilter 就是负责路由寻址和负载均衡的,可以看到如下源码:
开启 gateway 自动路由配置:
我们的系统架构不断地发展,系统中微服务的数量越来越多,不可能每添加一个服务,就在网关配置一个新的路由规则,这样的维护成本很大;我们在请求路径中会携带一个路由标识方便进行转发,而这个路由标识一般都是服务在注册中心中的服务名,因此这是我们就可以开启 spring cloud gateway 的自动路由功能,网关自动根据注册中心的服务名为每个服务创建一个router,将以服务名开头的请求路径转发到对应的服务,配置如下:
# enabled:默认为false,设置为true表明spring cloud gateway开启服务发现和路由的功能,网关自动根据注册中心的服务名为每个服务创建一个router,将以服务名开头的请求路径转发到对应的服务
spring.cloud.gateway.discovery.locator.enabled = true
# lowerCaseServiceId:启动 locator.enabled=true 自动路由时,路由的路径默认会使用大写ID,若想要使用小写ID,可将lowerCaseServiceId设置为true
spring.cloud.gateway.discovery.locator.lower-case-service-id = true
由于我们的网关项目配置了 server.servlet.context-path 属性,这会导致自动路由失败的问题,因此我们需要做如下两个修改:
# 重写过滤链,解决项目设置了 server.servlet.context-path 导致 locator.enabled=true 默认路由策略404的问题
spring.cloud.gateway.discovery.locator.filters[0] = PreserveHostHeader
@Configuration
public class GatewayConfig
{
@Value ("${server.servlet.context-path}")
private String prefix;
/**
* 过滤 server.servlet.context-path 属性配置的项目路径,防止对后续路由策略产生影响,因为 gateway 网关不支持 servlet
*/
@Bean
@Order (-1)
public WebFilter apiPrefixFilter()
{
return (exchange, chain) ->
{
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getRawPath();
path = path.startsWith(prefix) ? path.replaceFirst(prefix, "") : path;
ServerHttpRequest newRequest = request.mutate().path(path).build();
return chain.filter(exchange.mutate().request(newRequest).build());
};
}
}
网关自动根据注册中心的服务名为每个服务创建一个router,将以服务名开头的请求路径转发到对应的服务。假设我们的服务提供者在 nacos 注册中心的服务名为 “gateway-provider”,这时我们只需要访问 “http://localhost:9023/gateway/gateway-provider/test”,就可以将请求成功转发过去了