在前面的文章中我们介绍了微服务网关的基础知识,了解了什么是网关,网关有什么作用,以及市面上有哪些成熟的网关产品,最后了解了网关的配置技巧。通过上篇文章,大家应该可以在微服务架构中完成网关的基本配置。 但是,文末最后也说了,自动路由可以解决简单的转发规则,但对于企业中遇到的复杂、特殊的路由转发规则,就不是自动路由能解决的了。SpringCloud GateWay项目中内置了强大的“谓词”系统,可以满足企业应用中的各种转发规则要求,下面我们就一起来看看吧!
一、谓词(Predicate)
在介绍前,我们先要了解一下网关的三个关键名词:路由(Route)、谓词(Predicate)、过滤器(Filter)。
路由(Route)是指一个完整的网关地址映射与处理过程。一个完整的路由包含两部分配置:谓词(Predicate)与过滤器(Filter)。前端应用发来的请求要被转发到哪个微服务上,是由谓词决定的;而转发过程中请求、响应数据被网关如何加工处理是由过滤器决定的。
我们通过实例来介绍,将上篇文章中的工程复制过来,修改一下application.yml文件:
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: 106.14.221.171:8848
username: nacos
password: nacos
gateway:
discovery:
locator:
enabled: false #不再需要Gateway路由转发
routes: #路由规则配置
#第一个路由配置,service-a路由规则
- id: service_a_route #路由唯一标识
#lb开头代表基于gateway的负载均衡策略选择实例
uri: lb://service-a
#谓词配置
predicates:
#Path路径谓词,代表用户端URI如果以/a开头便会转发到service-a实例
- Path=/a/**
#After生效时间谓词,2024年5月1日后该路由才能在网关对外暴露
- After=2024-15-01T00:00:00.000+08:00[Asia/Shanghai]
#谓词配置
filters:
#忽略掉第一层前缀进行转发
- StripPrefix=1
#为响应头附加X-Response=Blue
- AddResponseHeader=X-Response,Blue
#第二个路由配置,service-b路由规则
- id: service_b_route
uri: lb://service-b
predicates:
- Path=/b/**
filters:
- StripPrefix=1
server:
port: 80
management:
endpoints:
web:
exposure:
include: '*'
配置释义:在 2024 年 5 月 1 日后,当用户端发来/a/...开头的请求时,Spring Cloud Gateway 会自动获取 service-a 可用实例,默认采用轮询方式将URI附加至实例地址后,形成新地址,service-a处理后 Gateway 网关自动在响应头附加 X-Response=Blue。至于第二个 service_b_route,比较简单,只说明当用户访问/b开头 URL 时,转发到 service-b 可用实例。
这里提供一个完整的路由配置固定格式:
spring:
gateway:
discovery:
locator:
enabled: false #不再需要Gateway路由转发
routes:
- id: xxx #路由规则id
uri: lb://微服务id #路由转发至哪个微服务
predicates:
//具体的谓词
filters:
//具体的过滤器
其中 predicates 是重点,说明路由生效条件,这里我们将常见的谓词使用形式举例出来:
1、After
After代表在指定时间点后路由规则生效
predicates:
- After=2024-05-01T00:00:00.000+08:00
2、Before
Before代表在指定时间点前路由规则生效
predicates:
- Before=2024-05-01T17:42:47.789-07:00[America/Denver]
3、Path
Path 代表 URI 符合映射规则时生效
predicates:
- Path=/b/**
4、Header
Header 代表包含指定请求头时生效
predicates:
- Header=X-Request-Id, \d+
5、Method
Method 代表要求 HTTP 方法符合规定时生效
predicates:
- Method=GET
谓词是 Gateway 网关中最灵活的部分,上面列举的是最常用的谓词,还有很多谓词是在文中没有提到,有兴趣可以到官网中学习。
二、过滤器(Filter)
过滤器(Filter)可以对请求或响应的数据进行额外处理,下面我们也来列举几个常用的过滤器
1、AddRequestParameter
AddRequestParameter 是对所有匹配的请求添加一个查询参数
filters:
- AddRequestParameter=id,2 #在请求参数中追加id=2
2、AddResponseHeader
AddResponseHeader 会对所有匹配的请求,在返回结果给客户端之前,在 Header 中添加响应的数据
#在Response中添加Header头,key=token,Value=123。
filters:
- AddResponseHeader=token,123
3、Retry
Retry 为重试过滤器,当后端服务不可用时,网关会根据配置参数来发起重试请求
filters:
#涉及过滤器参数时,采用name-args的完整写法
- name: Retry #name是内置的过滤器名
args: #参数部分使用args说明
retries: 3
status: 503
# 含义为,当后端服务返回 503 状态码的响应后,Retry 过滤器会重新发起请求,最多重试 3 次
三、GateWay的执行原理
在了解了SpringCloud GateWay配置和谓词用法后,我们再来看一下GateWay的底层实现。
执行流程图如下:
-
1、Spring Cloud Gateway 启动时基于 Netty Server 监听指定的端口(该端口可以通过 server.port 属性自定义)。当前端应用发送一个请求到网关时,进入 Gateway Handler Mapping 处理过程,网关会根据当前 Gateway 所配置的谓词(Predicate)来决定是由哪个微服务进行处理。
-
2、确定微服务后,请求向后进入 Gateway Web Handler 处理过程,该过程中 Gateway 根据过滤器(Filters)配置,将请求按前后顺序依次交给 Filter 过滤链进行前置(Pre)处理,前置处理通常是对请求进行前置检查,例如:判断是否包含某个指定请求头、检查请求的 IP 来源是否合法、请求包含的参数是否正确等。
-
3、当过滤链前置(Pre)处理完毕后,请求会被 Gateway 转发到真正的微服务实例进行处理,微服务处理后会返回响应数据,这些响应数据会按原路径返回被 Gateway 配置的过滤链进行后置处理(Post),后置处理通常是对响应进行额外处理,例如:将处理过程写入日志、为响应附加额外的响应头或者流量监控等。
可以看到,在整个处理过程中谓词(Predicate)与过滤器(Filter)起到了重要作用,谓词决定了路径的匹配规则,让 Gateway 确定应用哪个微服务,而 Filter 则是对请求或响应作出实质的前置、后置处理。
在项目中功能场景多种多样,像日常的用户身份鉴权、日志记录、黑白名单、反爬虫等基础功能都可以通过自定义 Filter 为 Gateway 进行功能扩展。
四、自定义全局过滤器实战
在 Spring Cloud Gateway 中,自定义过滤器分为两种:全局过滤器与局部过滤器。两者唯一的区别是:全局过滤器默认应用在所有路由(Route)上,而局部过滤器可以为指定的路由绑定。下面通过“计时过滤器”这个案例介绍全局过滤器的配置。所谓计时过滤器是指任何从网关访问的请求,都要在日志中记录下从请求进入到响应退出的执行时间,通过这个时间运维人员便可以收集并分析哪些功能进行了慢处理,以此为依据进行进一步优化。下面是计时过滤器的代码:
package com.example.gateway.filter;
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.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component //自动实例化并被Spring IOC容器管理
//全局过滤器必须实现两个接口:GlobalFilter、Ordered
//GlobalFilter是全局过滤器接口,实现类要实现filter()方法进行功能扩展
//Ordered接口用于排序,通过实现getOrder()方法返回整数代表执行当前过滤器的前后顺序
public class ElapsedFilter implements GlobalFilter, Ordered {
//基于slf4j.Logger实现日志输出
private static final Logger logger = LoggerFactory.getLogger(ElapsedFilter.class);
//起始时间属性名
private static final String ELAPSED_TIME_BEGIN = "elapsedTimeBegin";
/**
* 实现filter()方法记录处理时间
* @param exchange 用于获取与当前请求、响应相关的数据,以及设置过滤器间传递的上下文数据
* @param chain Gateway过滤器链对象
* @return Mono对应一个异步任务,因为Gateway是基于Netty Server异步处理的,Mono对就代表异步处理完毕的情况。
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//Pre前置处理部分
//在请求到达时,往ServerWebExchange上下文环境中放入了一个属性elapsedTimeBegin,保存请求执行前的时间戳
exchange.getAttributes().put(ELAPSED_TIME_BEGIN, System.currentTimeMillis());
//chain.filter(exchange).then()对应Post后置处理部分
//当响应产生后,记录结束与elapsedTimeBegin起始时间比对,获取RESTful API的实际执行时间
return chain.filter(exchange).then(
Mono.fromRunnable(() -> { //当前过滤器得到响应时,计算并打印时间
Long startTime = exchange.getAttribute(ELAPSED_TIME_BEGIN);
if (startTime != null) {
logger.info(exchange.getRequest().getRemoteAddress() //远程访问的用户地址
+ " | " + exchange.getRequest().getPath() //Gateway URI
+ " | cost " + (System.currentTimeMillis() - startTime) + "ms"); //处理时间
}
})
);
}
//设置为最高优先级,最先执行ElapsedFilter过滤器
//return Ordered.LOWEST_PRECEDENCE; 代表设置为最低优先级
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
运行后通过 Gateway 访问任意微服务便会输出日志:
2024-03-1 12:36:01.765 INFO 14052 --- [ctor-http-nio-4] com.example.gateway.filter.ElapsedFilter : /0:0:0:0:0:0:0:1:57873 | /test-service/test | cost 821ms
以上就是全局过滤器的开发方法,至于局部过滤器的配置方法与全局过滤器极为相似,有兴趣可以通过官方文档了解更详细的内容。
下一篇开始,我们来介绍一下微服务环境下如何通过服务降级、熔断等机制来保护我们的微服务架构,避免雪崩效应的发生。感兴趣的小伙伴可以持续关注。
文章将持续更新,欢迎关注公众号:服务端技术精选。欢迎点赞、关注、转发。