目录
一、微服务网关简介
1.1 网关的作用
1.2 常用网关
1.2.1 传统网关
1.2.2 云原生网关
二、gateway网关介绍
2.1 问题起源
2.2 引发的问题
2.2.1 重复造轮子
2.2.2 调用低效
2.2.3 重构复杂
2.3 gateway改进
三、Spring Cloud Gateway 介绍
3.1 Gateway 概述
3.2 Gateway 功能特性
3.3 Gateway 核心概念
3.3.1 路由(route)
3.3.2 断言(predicates)
3.3.3 过滤器(Filter)
3.4 Gateway 工作原理
四、Gateway快速使用
4.1 操作步骤
4.1.1 导入maven依赖
4.1.2 添加配置文件
4.1.3 启动服务并测试
4.2 整合nacos
4.2.1 引入nacos依赖
4.2.2 修改配置文件
4.2.3 效果测试
4.3 整合nacos自动服务发现
五、Gateway路由断言工厂
5.1 常用的路由断言工厂
5.1.1 基于Datetime类型的断言工厂
5.1.2 基于远程地址的断言工厂
5.1.4 基于Header的断言工厂
5.1.5 基于host的断言工厂
5.1.6 基于Method请求方法的断言工厂
5.1.7 基于Path请求路径的断言工厂
5.1.8 基于Query请求参数的断言工厂
5.1.9 基于路由权重的断言工厂
5.2 路由断言工厂使用
5.2.1 时间断言
5.2.2 header断言
5.2.3 Query请求参数断言
5.3 自定义路由断言工厂
5.3.1 自定义路由断言工厂类
5.3.2 配置自定义类到配置文件
5.3.3 效果测试
六、Gateway 过滤器工厂
6.1 Gateway过滤器使用
6.1.1 请求头使用
6.1.2 请求头参数使用
6.2 自定义过滤器工厂
6.2.1 自定义过滤器类
6.2.2 修改配置文件
6.2.3 接口测试
6.3 全局过滤器
6.3.1 概述
6.3.2 全局过滤器分类
6.3.3 全局过滤器使用
6.3.4 接口测试
七、Gateway 跨域设置
7.1 操作流程
7.1.1 修改配置文件
八、写在文末
一、微服务网关简介
在一个成熟稳定的微服务架构中,为了保护后端接口安全,避免暴露真实的接口地址,通常在请求到达接口前,会通过一层叫做“网关”的服务,经过网关的代理和转发,再到后端,这就是网关的作用。比如大家熟悉的nginx,gateway,zuul等,可以说在互联网公司产品中都有使用,网关有哪些作用呢?
1.1 网关的作用
可以说,网关在一个系统中承载着非常重要的作用:
- 对外隐藏真实的API地址,保护API资源安全;
- 识别与拦截恶意请求,提前对请求进行过滤与审计;
- 承载流量入口,根据系统负载,对请求流量进行转发;
1.2 常用网关
随着微服务架构越来越普遍,网关在整改架构中的地位也越来越重要,伴随着云原生的兴起与大规模落地实践,也逐渐开始出现各种新型的网关。
1.2.1 传统网关
springcloud微服务体系下,仍然可以考虑传统的网关,如下:
- nginx;
- zuul;
- gateway;
1.2.2 云原生网关
随着k8s的大规模运用实践,一些与之相关的网关也开始出现,比如:
- ingress;
- istio(envoy);
- api gateway;
- kong;
- apisix…
二、gateway网关介绍
2.1 问题起源
在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去用。
2.2 引发的问题
上述这样的架构,会存在着诸多的问题:
2.2.1 重复造轮子
每个业务都需要单独进行鉴权、限流、权限校验、跨域等逻辑,每个业务都各自为战,自己造轮子实现一遍;
2.2.2 调用低效
如果业务量比较简单的话,暂时还不会有什么问题,随着业务越来越复杂,一个复杂的页面可能会涉及到数百个微服务协同工作,如果每个微服务都分配一个域名的话,一方面客户端代码会很难维护,涉及到数百个域名,另一方面是连接数的瓶颈,想象一下你打开一个APP,通过抓包发现涉及到了数百个远程调用,这在移动端下会显得非常低效;
2.2.3 重构复杂
后期如果需要对微服务进行重构的话,也会变的非常麻烦,需要客户端配合你一起进行改造,比如商品服务,随着业务变的越来越复杂,后期需要进行拆分成多个微服务,这个时候对外提供的服务也需要拆分成多个,同时需要客户端配合你进行改造,非常蛋疼;
2.3 gateway改进
针对上面存在的这些问题,可以借助API网关来解决
所谓API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。
添加上API网关之后,系统的架构图变成了如下所示
三、Spring Cloud Gateway 介绍
3.1 Gateway 概述
- 是Spring Cloud官方推出的第二代网关框架,定位于取代 Netflix Zuul1.0。相比 Zuul 来说,Spring Cloud Gateway 提供更优秀的性能,更强大的有功能;
- 是由 WebFlux + Netty + Reactor 实现的响应式的 API 网关。它不能在传统的 servlet 容器中工作,也不能构建成 war 包;
- 旨在为微服务架构提供一种简单且有效的 API 路由的管理方式,并基于 Filter 的方式提供网关的基本功能,例如说安全认证、监控、限流等等;
3.2 Gateway 功能特性
相比第一代网关zuul,Gateway提供了丰富而强大的功能;
1)基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;
2)动态路由:能够匹配任何请求属性;
3)支持路径重写;
4)集成 Spring Cloud 服务发现功能(Nacos,Eureka);
5)可集成流控降级功能(Sentinel、Hystrix);
6)可以对路由指定易于编写的 Predicate(断言)和 Filter(过滤器);
3.3 Gateway 核心概念
3.3.1 路由(route)
路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和 配置的路由匹配。
3.3.2 断言(predicates)
Java8中的断言函数,SpringCloud Gateway中的断言函数类型是Spring5.0框架中的ServerWebExchange。断言函数允许开发者去定义 匹配Http request中的任何信息,比如请求头和参数等。
3.3.3 过滤器(Filter)
SpringCloud Gateway中的filter分为Gateway FilIer和Global Filter。Filter可以对请求和响应进行处理。
3.4 Gateway 工作原理
根据上图,gateway的工作流程大致如下:
- Gateway Client向Gateway Server发送请求;
- 请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文;
- 然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给RoutePredicateHandlerMapping;
- RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用;
- 如果过断言成功,由FilteringWebHandler创建过滤器链并调用;
- 请求会一次经过PreFilter–微服务–PostFilter的方法,最终返回响应;
四、Gateway快速使用
接下来通过一个案例快速体验下gateway的使用
4.1 操作步骤
4.1.1 导入maven依赖
创建一个新的工程,只需要导入这一个依赖即可
org.springframework.cloud
spring-cloud-starter-gateway
4.1.2 添加配置文件
参考配置文件中的注释进行理解,gateway功能点核心就在于配置文件的使用,通过声明式的配置灵活的控制路由;
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
#当前路由的唯一标识,路由到哪个服务上面去
- id: order-route
uri: http://localhost:8083
predicates:
#即所有以order-serv开头的请求都转发到这个微服务处理
- Path=/order-serv/**
filters:
#访问的时候,Gateway会自动的把order-serv过滤掉,从而转发到对应微服务的正确路径上
#比如客户端访问的是 http://localhost:8088/order-serv/order/add,通过gateway之后实际访问的是http://localhost:8088/order/add
- StripPrefix=1
4.1.3 启动服务并测试
@SpringBootApplication
public class GatewayApp01 {
public static void main(String[] args) {
SpringApplication.run(GatewayApp01.class,args);
}
}
启动之前的order模块和stock模块,在order模块中有一个 /order/add接口,按照上述的配置,我们访问需要经过gateway的转发,所以完整的请求地址为:http://localhost:8088/order-serv/order/add,如果访问这个地址能够像访问:http://localhost:8083/order/add 那样的效果,说明gateway的配置生效了,浏览器访问接口效果如下:
4.2 整合nacos
在上述配置文件中可以发现,直接在配置文件中写死了gateway转发时路径的地址, 这种做法是不规范同时会引发很多问题的,正确而合理的做法是通过指定的服务名称去转发,接下来使用nacos作为注册中心,从注册中心获取此地址;
4.2.1 引入nacos依赖
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
4.2.2 修改配置文件
主要有两处,一是添加nacos的地址,而是将转发的uri地址修改为nacos上面的服务名称
server:
port: 8088
spring:
application:
name: api-gateway
# nacos注册中心地址
cloud:
nacos:
discovery:
server-addr: localhost:8848 #服务注册中心地址
gateway:
routes:
#当前路由的唯一标识,路由到哪个服务上面去
- id: order-route
uri: lb://order-service #需要转发的服务地址,nacos上注册的那个服务地址
predicates:
#即所有以order-serv开头的请求都转发到这个微服务处理
- Path=/order-serv/**
filters:
#访问的时候,Gateway会自动的把pay过滤掉,从而转发到对应微服务的正确路径上
#比如客户端访问的是 http://localhost:8088/order-serv/order/add,实际访问的是http://localhost:8088/order/add
- StripPrefix=1
4.2.3 效果测试
启动几个模块的服务之后,再次调用相同的接口,与上面的效果一致
4.3 整合nacos自动服务发现
基于整合nacos基础上,gateway的配置文件还可以进一步简化成下面这样,通过这样的配置,当请求地址为: /order-service/order/add接口时,会自动去nacos中查找是否有这样的服务存在;
server:
port: 8088
spring:
application:
name: api-gateway
# nacos注册中心地址
cloud:
nacos:
discovery:
server-addr: localhost:8848 #服务注册中心地址
gateway:
discovery:
locator:
enabled: true #是否启动自动发现nacos上面的服务
启动服务后再次测试,仍然可以得到正确的返回结果;
五、Gateway路由断言工厂
Gateway提供了路由断言工厂的配置,使得对于请求的处理更加的丰富和灵活,开发者只需要在配置文件中根据业务场景做一些规则的设置即可。SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配,如下:
5.1 常用的路由断言工厂
5.1.1 基于Datetime类型的断言工厂
此类型的断言根据时间做判断,主要有三个:
- AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期;
- BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期;
- BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内
‐ After=2022‐12‐29T23:59:59.789+08:00[Asia/Shanghai]
5.1.2 基于远程地址的断言工厂
RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中
‐ RemoteAddr=192.168.1.1/24
5.1.3 基于cookie的断言工厂
CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求cookie是否具有给定名称且值与正则表达式匹配
‐Cookie=china, ch
5.1.4 基于Header的断言工厂
HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否具有给定名称且值与正则表达式匹配
‐Header=X‐Request‐Id, d+
5.1.5 基于host的断言工厂
HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则
‐Host=**.testhost.org
5.1.6 基于Method请求方法的断言工厂
MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配
‐
Method
=
GET
5.1.7 基于Path请求路径的断言工厂
PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则
‐Path=/foo/{segment}
5.1.8 基于Query请求参数的断言工厂
QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配
‐Query=baz, ba.
5.1.9 基于路由权重的断言工厂
WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发
routes:
‐id: weight_route1
uri: host1
predicates:
‐Path=/product/**
‐Weight=group3, 1
‐id: weight_route2
uri: host2
predicates:
‐Path=/product/**
‐Weight= group3, 9
5.2 路由断言工厂使用
5.2.1 时间断言
添加如下配置文件,意思是在满足路由转发的规则前提下,还需要时间在配置的时间之后;
server:
port: 8088
spring:
application:
name: api-gateway
# nacos注册中心地址
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
routes:
- id: order-route
uri: lb://order-service
predicates:
- Path=/order/**
- After=2022-12-29T23:59:59.789+08:00[Asia/Shanghai]
再次启动并访问接口,看的如下效果
5.2.2 header断言
添加如下配置,意思是请求的header中还需要携带一个key为username,值为jerry的参数
server:
port: 8088
spring:
application:
name: api-gateway
# nacos注册中心地址
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
routes:
- id: order-route
uri: lb://order-service
predicates:
- Path=/order/**
- After=2022-12-29T23:59:59.789+08:00[Asia/Shanghai]
- Header=username,jerry
通过postman发起请求,可以看到如下效果
如果去掉header中的参数或者写错了,将会出现404;
5.2.3 Query请求参数断言
添加如下配置,即请求中必须携带name参数
server:
port: 8088
spring:
application:
name: api-gateway
# nacos注册中心地址
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
routes:
- id: order-route
uri: lb://order-service
predicates:
- Path=/order/**
- After=2022-12-29T23:59:59.789+08:00[Asia/Shanghai]
#- Header=username,jerry
- Query=name
再次请求接口,如果不携带name,将会看的请求404
携带name之后
5.3 自定义路由断言工厂
如果自带的路由断言工厂仍然不能满足你的个性化需求的时候,就可以考虑自定义路由断言工厂。
自定义路由断言工厂需要继承 AbstractRoutePredicateFactory 类,重写 apply 方法的逻辑。在 apply 方法中可以通过 exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息。
自定义路由断言工厂注意点:
- 必须为spring组件bean;
- 类必须加上RoutePredicateFactory作为结尾;
- 必须继承AbstractRoutePredicateFactory;
- 必须声明静态内部类 ,声明属性来接收 配置文件中对应的断言的信息;
- 需要结合shortcutFieldOrder进行绑定;
- 通过apply进行逻辑判断, true就是匹配成功 , false匹配失败;
5.3.1 自定义路由断言工厂类
@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory {
public CheckAuthRoutePredicateFactory() {
super(CheckAuthRoutePredicateFactory.Config.class);
}
public List shortcutFieldOrder() {
return Arrays.asList("name");
}
public Predicate apply(CheckAuthRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
public boolean test(ServerWebExchange exchange) {
if(config.getName().equals("hello")){
return true;
}
return false;
}
};
}
//接受配置文件中中的参数
@Validated
@Data
public static class Config {
private String name;
}
}
5.3.2 配置自定义类到配置文件
5.3.3 效果测试
启动相关的模块服务后再次调用接口,看的如下效果
上面即是说明配置文件中的参数与断言工厂中匹配上之后可以正常路由,如果将配置文件中的参数修改一下
再次测试接口,可以看到下面的效果
六、Gateway 过滤器工厂
从文章开头关于gateway的执行流程图中可以看到,Gateway 内置了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加剔除响应头,添加去除参数等,gateway过滤器详细说明:gateway过滤器说明
过滤器的存在与使用,可以对来源请求进行更加细致的控制,以达到对服务端接口资源进行防护的目的。
6.1 Gateway过滤器使用
6.1.1 请求头使用
作用:为原始请求添加Header,对应的过滤器工厂:AddRequestHeader,修改配置文件
server:
port: 8088
spring:
application:
name: api-gateway
# nacos注册中心地址
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
routes:
- id: order-route
uri: lb://order-service
predicates:
- Path=/order/**
- After=2022-12-29T23:59:59.789+08:00[Asia/Shanghai]
#- Header=username,jerry
#- Query=name
#- CheckAuth=jerry
filters:
- AddRequestHeader=X-Request-color,red
在OrderController中添加如下测试接口
@GetMapping("/header")
public String header(@RequestHeader("X-Request-color") String color){
return color;
}
浏览器请求接口,可以看到如下效果,说明从gateway中转发过来的请求带上了参数 red;
6.1.2 请求头参数使用
添加如下配置参数
OrderController添加一个测试接口
@GetMapping("/request-param")
public String testRequestParam(@RequestParam("color")String color) throws Exception {
logger.info("获取请求参数:" + color);
return color;
}
测试接口,可以看到如下效果
6.2 自
6.2 自定义过滤器工厂
某些情况下,如果内置的gateway过滤器还不能满足要求的情况下,可以考虑自定义过滤器,使用也比较简单,自定义类,并继承AbstractNameValueGatewayFilterFactory,且自定义类名称必须要以GatewayFilterFactory结尾并交给spring管理;
6.2.1 自定义过滤器类
@Component
public class CheckAuthGatewayFilterFactory extends AbstractGatewayFilterFactory {
public CheckAuthGatewayFilterFactory() {
super(CheckAuthGatewayFilterFactory.Config.class);
}
public List shortcutFieldOrder() {
return Arrays.asList("name");
}
public GatewayFilter apply(CheckAuthGatewayFilterFactory.Config config) {
return (exchange, chain) -> {
String paramValue = exchange.getRequest().getQueryParams().getFirst("name");
if (StringUtils.isNotEmpty(paramValue) && paramValue.equals("jerry")) {
return chain.filter(exchange);
} else {
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
return exchange.getResponse().setComplete();
}
};
}
@Data
public static class Config {
private String name;
}
}
6.2.2 修改配置文件
6.2.3 接口测试
启动相关的模块服务,通过gateway调用 /order/add接口,如果name参数传入并且value值正确
如果value值不正确,将出现下面的效果
6.3 全局过滤器
上面的过滤器按照分类来说可认为是局部过滤器,局部过滤器针对某个路由, 需要在路由中进行配置,但是如果实际业务中需要控制的路由比较多的情况下,就需要写很多局部过滤器了,尤其是那些具有相同的需要过滤的场景这样就显得冗余了,此时可以考虑使用全局过滤器
6.3.1 概述
针对所有路由请求,一旦定义就会全局生效,GlobalFilter 接口和 GatewayFilter 有一样的接口定义,只不过, GlobalFilter 会作用于所有路由。
6.3.2 全局过滤器分类
如下列举了官方常用的gateway的全局过滤器
6.3.3 全局过滤器使用
自定义一个类,实现GlobalFilter接口
@Component
@Slf4j
public class LogFilter implements GlobalFilter {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info(exchange.getRequest().getPath().value());
return chain.filter(exchange);
}
}
6.3.4 接口测试
启动相关的模块服务之后,访问上面的接口,可以看到控制台就输出了请求的路径信息;
七、Gateway 跨域设置
跨越这个概念对很多同学来说不陌生,解决跨域的方案有很多种,比如在nginx中进行设置就是比较常见的一种,在Gateway中同样提供了关于解决跨域的方案。
7.1 操作流程
官方文档:gateway跨域配置手册
7.1.1 修改配置文件
添加跨域相关的配置信息
server:
port: 8088
spring:
application:
name: api-gateway
# nacos注册中心地址
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
# ----------- 跨域相关的配置
globalcors:
cors-configurations:
'[/**]': #拦截的请求
allowedOrigins: #允许跨域的请求来源
- "http://localhost:8080"
allowedMethods: #运行跨域的请求方式
- "GET"
- "POST"
routes:
- id: order-route
uri: lb://order-service
predicates:
- Path=/order/**
- After=2022-12-29T23:59:59.789+08:00[Asia/Shanghai]
#- Header=username,jerry
#- Query=name
#- CheckAuth=jerry
filters:
#- AddRequestHeader=X-Request-color,red
#- AddRequestParameter=color,blue
- CheckAuth=name,jerry
八、写在文末
本文通过较大的篇幅详细说明了gateway的使用,更多关于gateway的使用可以参考官网文档进行学习,比如使用gateway进行统一的鉴权、限流等,作为一款优秀的可编程式的服务网关,有必要对其进行深入的学习和了解,才能在架构的设计中合理的进行使用。