Spring cloud - gateway
什么是Spring Cloud Gateway
先去看下官网的解释:
This project provides an API Gateway built on top of the Spring Ecosystem, including: Spring 6, Spring Boot 3 and Project Reactor. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them such as: security, monitoring/metrics, and resiliency.
Spring提供了一个API网关,基于Spring 6、Spring Boot 3以及Project Reactor。目标是提供一个简单、有效的方式进行API路由,以及安全、监控等众多项目的公共需求。
这个描述其实很简单,但是我们需要了解一个背景知识:安全、权限、监控、系统弹性等需求是每一个系统或模块的公共需求,这类需求的解决方案可以是在每一个模块中处理,但显而易见的弊端是相同的处理逻辑会在每一个模块中重复实现,增加系统的复杂性,降低系统的可维护性。
Spring Cloud Gateway可以完美解决以上问题,集中处理鉴权、安全、监控、流量监控等公共需求,并可实现对最终用户隐藏真实的业务调用接口,避免业务接口的细节的对外暴露。
术语
- route:路由,是网关的基本概念,由路由ID、目标URI、一组断言以及一组过滤器组成,断言判断为true则表示当前路由被匹配。
- Predicate:断言,是一个java 8函数式断言接口,输入是Spring framework的ServerWebExchange。由于ServerWebExchange包含了http request以及http response,所以,断言可以基于任何http request设置,比如http header、http请求参数等等。
- Filter:过滤器,Spring Cloud Gateway基于GatewayFilter接口、通过特定的过滤器工厂创建的一系列实例,在请求发送给网关后的服务前、或者调用网关后面的服务后执行,从而可以实现修改请求参数及返回信息等增强功能。
Spring Cloud Gateway如何工作
下图展示了Spring Cloud网关的工作原理:
客户端发送请求给spring cloud网关而不是真实的业务服务端(这种情况下网关的作用类似于反向代理nginx),网关的Gateway Handle Mapping判断Predicate符合的话在转发请求给Gateway Web Handle,之后Gateway Web Handle调用Filters处理后将请求发送给被代理的各业务服务(网关uri定义的)。
有没有发现上图比较眼熟?是不是和Servlet+Spring MVC处理请求的过程很类似?
Filters组成filters chain,像剥洋葱一样逐个调用,先执行各过滤器的before逻辑,调用完Proxied Service之后再执行after逻辑。
引入Spring Cloud Gateway
我们在前面案例的基础上,增加Spring Cloud Gateway模块。
新建一个Springgateway模块:
项目中引入Spring Cloud Gateway,pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springCloud</artifactId>
<groupId>com.example</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springgateway</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
</project>
注意Spring Cloud Gateway不能是SpringMVC项目,所以pom文件中不能引入spring-boot-starter-web,否则项目启动不了。
然后配置yam文件:
spring:
application:
name: spring-gateway
cloud:
gateway:
routes:
- id: path_route
uri: http://127.0.0.1:9090
predicates:
- Path=/order/*
eureka:
client:
service-url: # eureka service
defaultZone: http://127.0.0.1:10086/eureka/
server:
port: 9095
在applicaton.yml文件中配置一个route:
id: 任意一个名字就可以,但是需要唯一。
uri:断言判断匹配成功之后的路由地址,指向proxied service
predicates:断言,配置为Path断言,判断请求路径中是否包含/order
Spring Cloud Gateway提供了很多断言工厂:
我们的案例使用path断言工厂,path断言工厂负责根据请求路径进行匹配。
我们把Spring Cloud Gateway也配置为Eureka客户端,放在注册中心统一管理,后面我们可以看到,Spring Cloud Gateway支持通过注册中心进行路由。
上面的配置很简单,判断请求路径中如果包含/order的话,将请求路由到http://127.0.0.1:9090去处理。
验证
依次启动Eureka、orderservice、orderservice以及springGateway service:
gateway启动在端口9095,端都9090是orderservice。
看一下10086端口的Eureka注册中心:
发现orderservice、userservice以及spring-gateway都已经在Eureka注册中心完成注册。
接下来访问9095端口的gateway服务:
说明Spring Cloud Gateway已经开始正常工作了,能够路由的orderservice服务。
改造orderservice服务
上面路由配置直接指向了 http://127.0.0.1:9090,只能访问到指定的应用,无法访问到spring loadbalance提供的负载均衡服务。
下面我们尝试通过注册中心访问服务、并享受到spring loadbalance提供的负载均衡服务。
前面的案例访问orderservice后,会通过orderservice访问userservice的服务。现在我们是为了验证gateway,简单起见,我们暂时去掉userservice,通过网关gateway访问orderservice、由orderservice直接提供服务,不再访问userservice了。
为此,简单改造一下orderservice的orderController,不再调用userservice了,直接返回当前orderservice的端口:
package com.example.controller;
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@Value("${server.port}")
private String serverPort;
@Autowired
OrderService orderService;
@Autowired
FallbackHandle fallbackHandle;
@GetMapping("/getOrder")
@HystrixCommand(fallbackMethod = "fallback",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")
})
public String getOrder(){
log.info("Come here to get Order....123===");
return "order service from:"+serverPort;
// return orderService.getOrder();
}
public String fallback(){
return "orderService服务异常";
}
}
重新启动服务,访问网关:
说明网关服务、以及改造后的orderservice都能正常工作了。
增加一个orderservice服务
copy一份orderservice的配置,在9091端口再启动一个orderservice服务:
启动服务后,检查Eureka注册中心:
发现9090/9091端口分别由一个orderservice注册到了Eureka注册中心。
通过注册中心配置网关
下面我们修改一下网关服务的配置:
spring:
application:
name: spring-gateway
cloud:
gateway:
routes:
- id: path_route
uri: lb://orderservice
predicates:
- Path=/order/*
eureka:
client:
service-url: # eureka service
defaultZone: http://127.0.0.1:10086/eureka/
server:
port: 9095
断言匹配成功后,通过Eureka注册中心以及loadbalance路由到orderservice服务。
启动服务,访问验证:
可以发现,不需要太多的配置,Spring Cloud Gateway已经可以通过Spring Eureka注册中心获取到注册的服务、并通过Spring LoadBalance负载均衡策略访问到服务了。
通过简单的配置,我们现在的应用其实已经具备了微服务的注册中心、负载均衡、网关等强大能力了!
过滤器
过滤器的作用原理我们在开篇说过了,过滤器可以修改请求的request或response,或者对请求进行某种控制,从而实现众多附加功能,比如修改头信息、修改请求参数、权限验证、超时控制等等。
Spring Cloud Gateway的过滤去分为路由过滤器和全局过滤器,路由过滤器作用范围是当前匹配到的路由,全局过滤器的作用范围是全局(所有路由)。
Spring Cloud Gateway提供了众多内置的路由过滤器:
以及内置的全局过滤器:
当请求匹配之后,路由过滤器以及全局过滤器会被装配成filterChain,filterChain装配的过程中,过滤器会按照org.springframework.core.Ordered接口进行排序,按顺序调用。
As Spring Cloud Gateway distinguishes between “pre” and “post” phases for filter logic execution (see How it Works), the filter with the highest precedence is the first in the “pre”-phase and the last in the “post”-phase.
高优先级的过滤器意味着早一些被 pre-phase阶段调用、晚一些被post-phase阶段调用。也就是说,高优先级针对的是请求服务之前的处理,和Servlet的过滤器的优先顺序类似。
除了Spring Cloud Gateway提供的内置过滤器之外,我们还可以实现自己的过滤器。
实现自己的过滤器
我们可以通过实现接口GatewayFilterFactory从而实现自己的路由过滤器。
Spring Cloud Filter提供了一个虚拟类AbstractGatewayFilterFactory,我们可以扩展该类实现自己的路由过滤器,比如我们创建一个MyGatewayFilterFactory :
package com.example.filter;
@Component
@Slf4j
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {
public MyGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
// grab configuration from Config object
log.info("filter:"+config.getName());
return (exchange, chain) -> {
//If you want to build a "pre" filter you need to manipulate the
//request before calling chain.filter
//获取request
ServerHttpRequest request = exchange.getRequest();
//从request中获取用户信息,进行用户鉴权......之后
log.info("This is pre filter===");
return chain.filter(exchange).then(Mono.fromRunnable(()-> {
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("Auth","xxxx.yyyy.zzzz");
log.info("this is after filter");
}));
};
}
public static class Config {
//Put the configuration properties for your filter here
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
需要通过@Component注解将过滤器加入到Spring容器中。
通过Config接收一个name参数,在apply方法中可以实现路由前置、以及路由后置过滤逻辑。我们的MyGatewayFilterFactory 同时实现了前置逻辑和后置逻辑,前置逻辑中可以添加用户鉴权等代码,简单起见,我们只是log打印一句话。后置逻辑中同样很简单,打印一句话,设置一个reponse header。
配置路由过滤器
上面我们实现的是漏油过滤器,所以只有配置在某一路由上才会生效:
spring:
application:
name: spring-gateway
cloud:
gateway:
routes:
- id: path_route
# uri: http://127.0.0.1:9090
uri: lb://orderservice
predicates:
- Path=/order/*
filters:
- name: My
args:
name: My own pre-filter
eureka:
client:
service-url: # eureka service
defaultZone: http://127.0.0.1:10086/eureka/
server:
port: 9095
启动服务,测试:
首先从前端请求中发现response header已经成功添加。然后看一下后台log:
我们自己的路由过滤器已经可以正常工作了。
添加自己的全局过滤器
全局过滤器需要实现GlobalFilter 接口:
package com.example.filter;
@Component
@Slf4j
public class MyGlobalGatewayFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("Here i can do anything to exchage");
return chain.filter(exchange).then(Mono.just(exchange)).map(serverWebExchange->{
serverWebExchange.getResponse().getHeaders().add("Global-Filter","Global filter OK");
return serverWebExchange;
}).then();
}
}
同样,我们添加的MyGlobalGatewayFilter 同时实现前置和后置过滤逻辑,不过都非常简单,前置过滤逻辑只是打印一段话,后置过滤逻辑添加一个response header。
全局过滤器是不需要额外配置的,对所有router都生效。
重新启动gateway,测试:
response header已经成功添加。
Ok,我们已经完成了对Spring Cloud Gateway的配置和使用,初步了解了Spring Cloud Gateway的用法,其实使用和配置起来一点都不复杂。
Thanks!