文章目录
Spring Cloud Gateway
1. 问题
前面我们的程序所有微服务的接口都是直接对外暴露的.可以直接对外访问.
但是为了保证对外服务的安全性,服务端实现的微服务接口通常带有一定的权限校验机制.
由于使用了微服务,原本一个应用的多个模块拆分成了多个应用,导致我们不得不实现多个校验逻辑.
此时带来了开发以及维护的麻烦
一个常用的解决方法就是使用API网关
2. 什么是API网关
API网关也是一个服务,通常是后端服务的唯一入口. 它的定义类似于设计模式中的Facade模式(门面模式).
也就是说,API网关就是整个微服务架构的门面,所有外部客户端访问,都需要经过它来进行调度和过滤
网关的核心功能
- 权限控制: 作为微服务的入口,对用户进行权限校验,如果拦截失败则进行拦截
- 动态路由: 一切请求先经过网关,但网关不处理业务,只是通过某种规则,把请求转发到某个微服务
- 负载均衡:当路由的目标有多个的时候,需要负载均衡
- 限流: 请求流量过大时,按照网关中配置微服务的能够接受的流量进行放行,避免服务压力过大
3. 常见的网关实现
Zuul
Zuul是NetFlix公司开源的一个API网关,是Spring Cloud NetFlix子项目的核心组件之一.它可以和Eureka,Ribbon,Hystrix等组件配合使用
但是现在Zuul等组件进入了维护状态,不再进行新特性的研发
Spring Cloud Gateway
Spring Cloud Gateway是Spring Cloud 的一个全新的api网关项目,基于Spring + Spring Boot等技术开发,替换掉了Zuul,旨在为微服务架构提供一种全新而有效的途径来转发请求,并为他们提供了横切关注点,如安全性,监控和弹性
在性能方面经过测试,优于Zuul
4. Spring Cloud Gateway快速上手
创建网关模块
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 基于nacos实现服务器发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
编写启动类
@SpringBootApplication
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class,args);
}
}
添加网关的路由配置
server:
port: 10030
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
routes: # 网关路由配置
- id: product-service # 这里是自定义的 只要求不重复即可
uri: lb://product-service
predicates:
- Path=/product/**
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
- id: 自定义路由ID.保持唯一
- uri: 目标服务地址,支持普通URI以及
lb://应用注册服务名称
,lb代表负载均衡,使用lb://
方式表示从注册中心获取服务地址 - predicates:路由条件,根据匹配的结果决定是否要执行该路由请求
测试
5. Route Predicate Factories
5.1 Predicate
Predicate是java8提供的一个函数式编程接口,它接收参数并返回一个布尔值,用于条件过滤,请求参数的校验
- 定义一个Predicate
public class StringPredicate implements Predicate<String> {
@Override
public boolean test(String s) {
return s == null || s.isEmpty();
}
}
- 使用
public class TestString {
@Test
public void t1 () {
Predicate<String> predicate = new StringPredicate();
String s1 = "";
String s2 = "11";
System.out.println(predicate.test(s1));
System.out.println(predicate.test(s2));
}
}
- 执行结果
也可以使用内置函数或者Lambda
@Test
public void t1 () {
Predicate<String> predicate = s -> s != null && !s.isEmpty();
System.out.println(predicate.test("abc"));
}
Predicate的其他方法
// and
@Test
public void t2 () {
Predicate<String> predicate = s -> s != null && !s.isEmpty();
Predicate<String> predicate1 = s -> s.chars().allMatch(Character::isDigit);
String s1 = "1111";
String s2 = "asddd";
System.out.println(predicate.and(predicate1).test(s1));
System.out.println(predicate.and(predicate1).test(s2));
}
// negate
@Test
public void t3 () {
Predicate<String> predicate = s -> s != null && !s.isEmpty();
String s = "11";
System.out.println(predicate.negate().test(s));
}
// or
@Test
public void t4 () {
Predicate<String> predicate = s -> s != null && !s.isEmpty();
Predicate<String> predicate1 = s -> s.chars().allMatch(Character::isDigit);
String s = "aaaa";
System.out.println(predicate.or(predicate1).test(s));
}
5.2 Route Predicate Factories
Route Predicate Factories (路由断言工厂),在Spring Cloud GateWay中,Predicate提供了路由规则的匹配机制
我们在配置文件中写的断言规则只是字符串,这些字符串会被Route Predicate Factory读取并处理,转变为判断的条件
例如这个Path=/product/**
,就是通过Path属性来匹配URL前缀是/product
的请求
这个规则是由org.springframework.cloud.gateway.handle.predicate.PathRoutePredicateFactory
来实现的
Spring Cloud Gateway默认提供了很多Route Predicate Factory,这些Predicate会分别匹配HTTP请求的不同属性,并且多个Predicate可以通过and逻辑进行组合
After
这个工厂需要一个日期时间(java的ZoneDateTime对象),匹配指定日期之后的请求
predicates:
- Path=/product/**
- After=2025-10-10T10:59:52.139671+08:00[Asia/Shanghai]
此时由于这个时间还未到,因此访问是失败的
Before
匹配指定日期之前的请求,与After一样,需要一个日期时间(java的ZoneDateTime对象)
predicates:
- Before=2025-10-10T10:59:52.139671+08:00[Asia/Shanghai]
Between
匹配两个指定日期之间的请求,datetime2
的参数必须在datetime1
之后
predicates:
- Between=2024-10-09T10:59:52.139671+08:00[Asia/Shanghai],2024-10-10T11:50:52.139671+08:00[Asia/Shanghai]
Cookit
请求中包含指定的Cookie,且该Cookie值指定的正则表达式
predicates:
- Cookie=test,test1
此时一旦请求不包含cookie,就会无法访问
加上Cookie即可
Header
请求中包含指定的Header,且该Header值符合指定的正则表达式
Host
请求必须是访问某个host(根据请求中的Host字段进行匹配)
predicates:
- Host=**.hostname1.org,**.hostname2.org
Method
匹配指定的请求方法
predicates:
- Method=GET,POST
Path
匹配指定路径的规则
predicates:
- Path=/product/getProductById
RemoteAddr
请求者的IP必须为指定范围
predicates:
- RemoteAdd=127.0.0.1/8080
6. Gateway Filtee Factories(网关过滤器工厂)
Predicate决定了请求由哪一个路由处理.如果在请求处理前后需要加一些逻辑,那就是Filter(过滤器)的范畴了
Filter过滤器分为两种:Pre类型和Post类型
**Pre类型过滤器:**路由处理之前执行(请求转发到后端服务之前执行),在Pre类型过滤器中可以做鉴权,限流等
**Post类型过滤器:**请求执行完后,将结果返回给客户端之前执行
Spring Cloud Gateway中内置了很多Filter,用于拦截和链式处理web请求,比如权限校验,访问超时等设定
Spring Cloud Gateway从作用范围上,把Filter分为GatewayFilter和GlobalFilter
GatewayFilter:应用到单个路由或者一个分组的路由上
GlobalFilter:应用到所有的路由上,也就是对所有的请求生效
6.1 GatewayFilter
GatewayFilter
同Predicate
类似,都是在application
配置文件中进行配置
每个过滤器的功能都是固定的,比如AddRequestParamterGatewayFilterFactory
只需要在配置文件中写AddRequestParameter
,就可以为所有的请求添加一个参数
predicates:
filters:
- AddRequestParameter=userName,zhangsan
我们在product-service中接受到请求的时候,将请求参数打印出来
public ProductInfo getProductById(@RequestParam int id,@RequestParam("userName") String userName) {
log.info("接收到请求参数"+userName);
log.info("getProductById");
return productService.getProductById(id);
}
进行访问:
观察日志:
Spring Cloud Gateway 提供的Filter非常多,以下是一些常见的:
AddRequestHeader
为当前请求添加Header
- AddRequestHeader=X-Request-red, blue
AddRequestParameter
为当前请求添加请求参数
- AddRequestParameter=red, blue
AddResponseHeader
为响应结果添加Header
- AddResponseHeader=X-Response-Red, Blue
RemoveRequestHeader
从当前请求删除某个Header
- RemoveRequestHeader=X-Request-Foo
RemoveResponseHeader
从响应结果删除某个Header
- RemoveResponseHeader=X-Response-Foo
RequestRateLimiter
为当前网关的所有请求执行限流过滤,如果被限流,默认会响应HTTP429-TooManyRequests
默认提供了RedisRateLimiter的限流实现,采用令牌桶算法实现限流功能
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
redis-rate-limiter.requestedTokens: 1
redis-rate-limiter.replenishRate
:令牌填充速度,即每秒钟允许多少个请求(不丢弃任何请求)
redis-rate-limiter.burstCapacity
:;令牌桶容量,即每秒用户最大能够执行的请求数量(不丢弃任何请求).将此值设置成0将阻止所有请求
redis-rate-limiter.requestedTokens
:每次请求占用几个令牌,默认为1
Retry
针对不同的响应进行重试,当后端服务器不可用时,网关会根据配置参数来发起重试请求
filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY
retries
:重试次数,默认为3
status
:HTTP请求返回的1状态码,针对指定状态码进行重试,对应org.springframework.http.HttpStatus
RequestSize
设置允许接收请求的最大请求包的大小,如果请求包大小超过设置的值,则返回413 PayLoad Too Large.
请求包大小,单位为字节,默认值为5M
filters:
- name: RequestSize
args:
maxSize: 5000000
默认过滤器Default Filters
添加一个filter并将其用于所有的路由,这个属性需要一个filter的列表
配置:
spring:
cloud:
gateway:
default-filters:
- AddResponseHeader=X-Response-Default-Red, Default-Blue
- PrefixPath=/httpbin
6.2 GlobalFilter
GlobalFilter 是Spring Cloud Gateway中的全局过滤器,它和GatewayFilter的作用是相同的
GlobalFilter会应用到所有的路由请求上,全局过滤器通常用于实现与安全性,性能监控和日志记录等相关的全局功能
Spring Cloud Gateway内置的全局过滤器也有很多,比如:
- Gateway Metrics Filter:网关指标,提供监控指标
- Forward Routing Filter:用于本地forward,请求不转发到下游服务器
- LoadBalance Client Filter:针对下游服务器,实现负载均衡
- …
快速上手
- 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 添加配置
spring:
gateway:
metrics:
enabled: true
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
shutdown:
enabled: true
- 测试
访问http://127.0.0.1:10030/actuator,就能看到所有监控的信息链接
7. 过滤器执行顺序
当一个项目里面,既有GatewayFilter,又有GlobalFilter时,请求路由后,网关会把当前项目中的GatewayFilter和GlobalFilter合并到一个过滤器链中,并进行排序,依次执行过滤器
每个过滤器都必须指定一个int类型的order值,默认值为0,表示该过滤的优先级,order值越小,优先级越高,执行顺序越靠前
- Filter通过实现Order接口或者添加@Order注解来指定order值
- Spring Cloud Gateway提供的Filter由Spring 指定,用户也可以自定义Filter,由用户自己指定
- 当过滤器的order值一样时,会按照defaultFilter > GatewayFilter > GlobalFilter的顺序执行
8. 自定义过滤器
Spring Cloud Gateway提供了过滤器的拓展功能,开发者可以根据业务来自定义过滤器,同样自定义过滤器也支持GatewayFilter和GlobalFilter两种
8.1 自定义GatewayFilter
实现对应的接口GatewayFilterFactory
,Spring Boot默认帮我们实现的2抽象类是AbstractGatewayFilterFactory
定义GatewayFilter
@Slf4j
@Component
public class CustomGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomConfig> implements Ordered {
public CustomGatewayFilterFactory() {
super(CustomConfig.class);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE; // 最低级
}
@Override
public GatewayFilter apply(CustomConfig config) {
return new GatewayFilter() {
/*
ServerWebExchange -> 提供了对HTTP请求和响应的访问
GatewayFilterChain: 过滤器链
Mono: Reactor的核心类,数据流发布者,Mono最多只能触发一个事件,可以把Mono用在异步完成任务时候,发出通知
chain.filter(exchange) 执行请求
Mono.fromRunnable() 创建一个包含Runnable元素的数据流
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("Pre Filter,config:{}",config); // Pre类型过滤器
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("Post Filter,config:{}",config);
}));
}
};
}
}
注意:
- CustomConfig是一个配置类,该类的属性就是后面从yaml配置文件中提取的配置
- 类名统一以GatewayFilterFactory结尾,因为默认情况下,过滤器的name会采用该定义类的前缀 如在本例中的custom
- 该类需要交给Spring 管理,需要添加注解如
@Component
配置
filters:
- AddRequestParameter=userName,zhangsan
- name: Custom
args:
name: custom filter
测试
访问接口后
8.2 自定义GlobalFilter
GlobalFilter的实现比较简单,不需要额外的配置,只需要实现GlobalFilter接口
定义GlobalFilter
@Component
@Slf4j
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("Pre Custom Global Filter");
return chain.filter(exchange).then(Mono.fromRunnable(()-> {
log.info("Post Custom Global Filter");
}));
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}