二Gateway的项目搭建与配置
Posted 竹峰的风
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二Gateway的项目搭建与配置相关的知识,希望对你有一定的参考价值。
文章目录
Gateway的项目搭建与配置
工作流程
下图提供了 Spring Cloud Gateway 如何工作的高级概述:
核心概念:
客户端向 Spring Cloud Gateway 发出请求。如果Gateway Handler Mapping确定请求与路由匹配,则将其发送到Gateway Web Handler 处理程序。此处理程序通过特定于请求的Fliter链运行请求。Fliter被虚线分隔的原因是Fliter可以在发送代理请求之前(pre)和之后(post)运行逻辑。执行所有pre过滤器逻辑。然后发出代理请求。发出代理请求后,将运行“post”过滤器逻辑。
注意:在没有端口的路由中定义的uri对于HTTP和HTTPS uri分别获得默认端口值80和443。
过滤器作用:
- Filter在pre类型的过滤器可以做参数效验、权限效验、流量监控、日志输出、协议转换等。
- Filter在post类型的过滤器可以做响应内容、响应头的修改、日志输出、流量监控等
- 这两种类型的过滤器有着非常重要的作用
核心点
-
Route(路由)
路由是构建网关的基础模块,它由ID,目标URI,包括一些列的断言和过滤器组成,如果断言为true则匹配该路由
-
Predicate(断言)
参考的是Java8的java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),请求与断言匹配则进行路由
-
Filter(过滤)
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
三个核心点连起来:
当用户发出请求到达Gateway,Gateway会通过一些匹配条件,定位到真正的服务节点,并在这个转发过程前后,进行一些及细化控制。其中Predicate就是我们匹配的条件,而Filter可以理解为一个拦截器,有了这两个点,再加上目标URI,就可以实现一个具体的路由了。
总结
Gateway核心的流程就是:路由转发与执行过滤器链
Gateway配置路由的两种方式
(1)通过YML配置
项目搭建
注意:该项目需配合Nacos一起使用
- 新建一个Maven项目:spring-cloud-gateway,java版本选择8
- 删除项目的src目录, 并新建两个Module,分别为cloud-alibaba-gateway-9999和springcloudalibaba-nacos-9001
-
导入对应依赖,因为Gateway属于SpringCloud的,所以一定要注意版本关系:
版本对应地址:https://spring.io/projects/spring-cloud
父级项目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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.12.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>spring-cloud-gateway</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-cloud-gateway</name> <packaging>pom</packaging> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud-version>Hoxton.SR12</spring-cloud-version> <spring-cloud-alibaba-version>2.2.8.RELEASE</spring-cloud-alibaba-version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>$spring-cloud-version</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>$spring-cloud-alibaba-version</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <modules> <module>cloud-alibaba-gateway-9999</module> <module>springcloudalibaba-nacos-9001</module> <module>demo-9002</module> </modules> </project>
子级项目spring-cloud-gateway:
pom文件:
注意:引入Gateway一定要删除spring-boot-starter-web依赖,否则会有冲突无法启动
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- <relativePath/> <!– lookup parent from repository –>-->
</parent>
<groupId>com.example</groupId>
<artifactId>cloud-alibaba-gateway-9999</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cloud-alibaba-gateway-9999</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
由于此项目也是配合Nacos使用,所以启动器要加上@EnableDiscoveryClient注解:
application.yml:
server:
port: 9999
spring:
application:
name: cloud-gateway-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true #开启注册中心路由功能
routes: # 路由
- id: nacos-provider #路由ID,没有固定要求,但是要保证唯一,建议配合服务名
uri: http://localhost:9001/nacos-provider # 匹配提供服务的路由地址
predicates: # 断言
- Path=/demo/** # 断言,路径相匹配进行路由
注意:gateway.discovery.locator.enabled = true 是开启自动路由功能,根据服务名称自动创建routes,因为我们后面有配置routes,所以可以不要(不要的话默认为false)。routes中的uri其实最后是不需要服务名称的,这个位置其实只需要指定的localhost:9001即可
所以这个位置我们可以把当前的配置优化为以下配置,它是一样可以启动的(项目中为了更直观,还是采用了上面的配置):
server:
port: 9999
spring:
application:
name: cloud-gateway-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway: #(去掉了gateway.discovery.locator.enabled配置)
routes: # 路由
- id: nacos-provider #路由ID,没有固定要求,但是要保证唯一,建议配合服务名
uri: http://localhost:9001 # 匹配提供服务的路由地址(去掉了/nacos-provider)
predicates: # 断言
- Path=/demo/** # 断言,路径相匹配进行路由
子级项目springcloudalibaba-nacos-9001:
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- <relativePath/> <!– lookup parent from repository –>-->
</parent>
<groupId>com.son</groupId>
<artifactId>springcloudalibaba-nacos-9001</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springcloudalibaba-nacos-9001</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
由于此项目也是配合Nacos使用,所以启动器要加上@EnableDiscoveryClient注解:
application.yml:
server:
port: 9001
spring:
application:
name: nacos-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848
management:
endpoint:
web:
exposure:
include: '*'
DemoController.java:
package com.son.springcloudalibabanacos9001.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/demo") //添加路由路径
public class DemoController
@Value("$server.port")
private String serverPort;
@GetMapping(value = "/hello")
public String getServerPort()
return "Hello! port: "+ serverPort;
测试
- 启动Nacos(Windows环境)
本地Nacos启动命令(standalone代表着单机模式运行,非集群模式):
startup.cmd -m standalone
- 启动9999网关和springcloudalibaba-nacos-9001服务,同时查看Nacos控制台,发现Nacos控制台成功注册Gateway网关
- 启动完成之后,访问接口:http://localhost:9999/demo/hello ,此时接口访问成功
(2)通过代码的方式进行配置
通过配置类进行配置,通过@Bean注入一个RouteLocator:
package com.example.cloudalibabagateway9999.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GateWayConfig
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder)
// 构建多个路由routes
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
// 具体路由地址
routes.route("path_demo",r -> r.path("/demo/**").uri("http://localhost:9001/nacos-provider")).build();
// 返回所有路由规则
return routes.build();
yml文件routes配置屏蔽,并将gateway.discovery.locator.enabled设置为false
我们在9001的DemoController中添加一个测试方法
@GetMapping(value = "/code")
public String codeTest()
return "代码配置类网关配置测试";
启动9999网关和springcloudalibaba-nacos-9001服务,启动完成之后,访问接口:http://localhost:9999/demo/code ,此时接口就可以转发到9001中具体的接口中了
重学SpringCloud系列九微服务网关-GateWay
重学SpringCloud系列九微服务网关-GateWay
- 还有必要学习Zuul么?
- 简介与非阻塞异步IO模型
- GateWay概念与流程
- 新建一个GateWay项目
- 通用Predicate的使用
- 自定义PredicateFactory
- 编码方式构建静态路由
- Filter过滤器介绍与使用
- 自定义过滤器Filter
- 网关请求转发负载均衡
- 结合nacos实现动态路由配置
- 跨域访问配置
- 网关层面全局异常处理
还有必要学习Zuul么?
一、什么是API网关
在开始讲解Spring Cloud GateWay之前呢,有必要说明一下什么是API网关。网关这个词,最早是出现在网络设备中,比如在彼此隔离的两个局域网中间的起到路由功能、隔离功能、安全验证功能的网络设备,通常被称为“网关”。
在软件开发方面,网关通常是用来隔离用户端和服务端的软件应用,通常被称为API网关。
所以使用API的好处是:
- 面向前端开发人员更加友好,前端开发人员面向的入口减少,便于维护
- 服务访问的认证鉴权更加方便,可以放在API网关统一去做。避免分散造成的开发及维护成本。
- 访问日志、限流等公共服务也可以在网关上集中完成。避免分散造成的开发及维护成本。
说了API网关的这么多好处,那么有没有坏处呢?也是有的,而且很重要。
- 当你使用了API网关之后,所有的请求都要多一次转发,造成一定程度上的响应时长的延长
- 当你使用了API网关之后,意味着网关作为流量入口需要承担比微服务更多的流量负载。所以网关本身的架构性能及稳定性非常重要。
虽然我们可以在网关的前面再去加一层nginx或者haproxy等负载均衡器,但是仍旧很难改变网关在一定程度上的流量集中的问题。
所以,笔者在很多场合下呼吁不要滥用微服务网关。你要权衡一下你当前的架构是否真的需要一个网关。衡量性能、稳定性以及维护成本之间关系,去决定要不要使用服务网关。
二、聊一聊Zuul
正如笔者所说网关本身的架构性能及稳定性非常重要。然而性能就是Zuul的短板,因为它是基于servlet的阻塞IO模型开发的(下一节我会专门介绍Zuul和Spring Cloud GateWay IO模型的差异)。
- Zuul 1.0在netflix官方已经进入了维护阶段,netflix对Spring Cloud社区的支持也已经基本属于“88了您哎”的状态
- 虽然Netflix很早就宣称了要对zuul进行升级改造,也就是Zuul 2.0,但是目前与Spring Cloud的整合也处于难产状态
- 基于以上的原因,Spring 社区自己开发了Spring Cloud gateWay。采用了spring 官方的响应式非阻塞框架webflux。官网测试结果性能是Zuul的1.6倍。
综上所述:笔者觉得目前Zuul已经没有任何学习的必要了。
简介与非阻塞异步IO模型
一、简介
Spring Cloud GateWay 是由Spring 官方社区开发的API 服务网关,在新一代的开发技术中使用到了Spring WebFlux的全新的响应式的非阻塞IO框架。相对于Spring Cloud第一代的网关组件zuul,性能有了长足的进步(宣称性能提升1.6倍)。WebFlux底层是基于高性能的非阻塞IO通信框架Netty实现的。
核心功能特性
笔者在上一节已经为大家介绍过,API服务网关的主要作用有三个:
- 统一流量入口,面向前端更加友好。减少分散入口配置,降低客户端与服务端的耦合度。
- 统一认证鉴权,避免多个服务分散鉴权造成的维护与开发的成本升高
- 访问日志、限流、过滤、缓存、监控等公共服务也可以在网关上集中完成。避免分散造成的开发及维护成本
二、阻塞IO与异步非阻塞IO的区别
笔者用相对通俗的话为大家说明一下阻塞IO与非阻塞IO之间的区别。我们以软件开发团队的工作方式来做一个比喻。作为软件开发人员,我们肯定知道软件开发的基本流程:
- 项目立项与可行性研究
- 需求分析与设计
- 代码开发
- 迭代测试
- 上线及配置管理、运维
在以Spring MVC或者struct为代表的框架都是基于sevlet的,其底层IO模型是阻塞IO模型。这种模型就好像你是公司的一个开发人员,上面的所有的5项工作全都由你一个人完成。如果公司有10个人,最多就只能同时进行10个需求。客户需求增多了也没有办法,只能让他们等着。如下图:一个请求占用一个线程,当线程池内的线程都被占用后新来的请求就只能等待。
spring 社区为了解决Spring MVC的阻塞模型在高并发场景下的性能瓶颈的问题,推出了Spring WebFlux,WebFlux底层实现是久经考验的netty非阻塞IO通信框架。该框架的请求处理与线程交互关系图如下:
boosGroup用于Accetpt连接建立事件并分发请求, workerGroup用于处理I/O读写事件和业务逻辑
每个Boss NioEventLoop循环执行的任务包含3步:
- 1 轮询accept事件
- 2 处理accept I/O事件,与Client建立连接,生成NioSocketChannel,并将NioSocketChannel注册到某个Worker NioEventLoop的Selector上
- 3 处理任务队列中的任务,runAllTasks。任务队列中的任务包括用户调用eventloop.execute或schedule执行的任务,或者其它线程提交到该eventloop的任务。
每个Worker NioEventLoop循环执行的任务包含3步:
- 1 轮询read、write事件;
- 2 处I/O事件,即read、write事件,在NioSocketChannel可读、可写事件发生时进行处理
- 3 处理任务队列中的任务,runAllTasks。
如果通俗的将上图中的各个任务池、线程池的组合比做一个软件开发公司,那么:
- 项目立项及可研,由公司项目经理及顾问来完成
- 需求分析与设计,由产品经理和架构师来完成
- 代码研发,由项目经理带领开发人员来完成
- 迭代测试,由测试团队来完成
- 上线及配置管理、运维,可能由专门的devops团队来完成
这样一个公司内的所有人完成分工,就能在有限的资源的情况下,去接触更多的客户,谈更多的需求,合理的分配人力资源,达到并发处理能力最大化的极限水平。相比于一个员工从头到位的负责一个项目,它的组织性更强,分工更明确,合理的利用空闲资源,专业的人最专业的事。
这种人力资源的合理利用及组织方式和非阻塞IO模型有异曲同工之处,通过合理的将请求处理线程及任务进行分类,合理的利用系统的内存、CPU资源,达到单位时间内处理能力的最大化就是异步非阻塞IO的核心用意!
所以非阻塞IO模型的核心意义在于:提高了有限资源下的服务请求的并发处理能力,而不是缩短了单个服务请求的响应时长。 由于API 服务网关集中的承载了微服务系统内的流量进行转发,所以他的并发处理能力至关重要的原因,也是netflix Zuul被淘汰的根本原因!
GateWay概念与流程
一、Spring Cloud Gateway的处理流程
Spring Cloud的工作原理图如下:
- 客户端向Spring Cloud Gateway发送请求,当请求的路径与网关定义的路由映射规则相匹配。该请求就会被发送到网关的Web Handler进行处理,执行特定的过滤器链。
- 大家注意图中的虚线,虚线左侧表示在请求处理之前的执行逻辑(Pre过滤器),虚线右侧表示请求处理之后的执行逻辑(post过滤器)
二、核心概念
- route(路由):路由实网关的基础元素,由id、目标url、predicate和filter组成。当请求通过网关的时候,由Gateway Handler Mapping通过predicate判断是否与路由匹配,当predicate=true的时候,匹配到对应的路由。
- predicate(谓词逻辑):是java8中提供的一个函数,允许开发人员根据其定义规则匹配请求。比如根据请求头、请求参数来匹配路由。可以认为它就是一个匹配条件的定义。
国内有很多的人把这个翻译成“断言”,实际上这个词作为名词是“谓词”的意思,作为动词才是断言,官网上这个词是一个名词。
- filter(过滤器):对请求处理之前之后进行一些统一的业务处理、比如:认证、审计、日志、访问时长统计等。
新建一个GateWay项目
一、Gateway网关搭建
引入gateway依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
因为spring-cloud-starter-gateway包含spring-boot-starter-webflux,所以可以将项目中spring-boot-starter-webflux的maven坐标从pom文件中删除。
如果是新建的子模块module,做好父子项目的pom文件中的module配置关系。
项目的配置文件使用方法仍然和之前一致
server:
port: 8777
spring:
application:
name: zimug-server-gateway
cloud:
gateway:
routes:
- id: dhy # 路由 ID,唯一
uri: http://baidu.com/ # 目标 URI,路由到微服务的地址
predicates: # 请求转发判断条件
- Path=/baidu/** # 匹配对应 URL 的请求,将匹配到的请求追加在目标 URI 之后
- routes指的是配置路由转发规则,可以配置多个
- 每一个route有一个id,标识该路由的唯一性
- uri指的是请求转发的目标
- predicates是请求转发的判断条件,我们的例子使用的Path条件判断
上面的路由配置的含义是当我们访问:http://<gateway-ip>:8777/baidu/
的时候,请求被转发到http://baidu.com/baidu/
,其中**
匹配任意字符。
二、需要注意的点
我们搭建的Spring Cloud Gateway(dhy-server-gateway)项目虽然是一个web项目,但是底层已经使用的是spring-boot-starter-webflux,而不是spring-boot-starter-web。所以下面的这个坐标不要在引入gateway项目了,会报错!
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
并且之前在spring-boot-starter-web体系下做的代码集成工作,在spring-boot-starter-webflux基础上都会有所变化,涉及到我们再去讲。
三、路由转发测试
在上面的配置文件中,我们已经完成了一个简单的路由转发配置。含义是当我们访问:http://<gateway-ip>:8777/baidu/
的时候,请求被转发到http://baidu.com/baidu/
,其中**
匹配任意字符。
下面我们就来做一下实验,我们把gateway项目在本机启动,然后浏览器访问如下网址。
http://localhost:8777/baidu/course
然后请求被转发至如下的网址:
http://baidu.com/baidu/course
通用Predicate的使用
一、Predicate路由判断条件介绍
Predicate 来源于 Java 8,是 Java 8 中引入的一个函数,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性实现了各种路由匹配规则,有通过 Header、请求参数等不同的条件来进行作为条件匹配到对应的路由。
说白了 Predicate 就是为了实现一组匹配规则,方便让请求过来找到对应的 Route 进行处理,接下来我们接下 Spring Cloud GateWay 内置几种 Predicate 的使用。
二、Predicate路由判断条件的使用方法
在之前的章节举例中,我们已经介绍了Path Predicate的匹配条件决定路由的转发规则。下面我们为大家介绍其他的多种 Predicate的匹配条件!
2.1.通过日期时间条件匹配
After Route Predicate Factory使用的是时间作为匹配规则,只要当前时间大于设定时间,路由才会匹配请求。以下After规则配置:在东8区的2020-05-17T16:31:47之后,所有请求都转发到dhy.com
。
spring:
cloud:
gateway:
routes:
- id: after_route
uri: http://www.dhy.com
predicates:
- After=2020-05-17T16:31:47.789+08:00
Before Route Predicate Factory也是使用时间作为匹配规则,只要当前时间小于设定时间,路由才会匹配请求。以下Before规则配置:在东8区的2020-05-17T19:53:42之前,所有请求都转发到dhy.com
。
spring:
cloud:
gateway:
routes:
- id: before_route
uri: http://www.dhy.com
predicates:
- Before=2020-05-17T19:53:42.789+08:00
Between Route Predicate Factory也是使用两个时间作为匹配规则,只要当前时间大于第一个设定时间,并小于第二个设定时间,路由才会匹配请求。以下Between规则配置:在东8区的2020-05-17T16:31:47之后,2020-05-17T19:53:42之前的时间段内,所有请求都转发到dhy.com
。
spring:
cloud:
gateway:
routes:
- id: between_route
uri: http://www.dhy.com
predicates:
- Between=2020-05-17T16:31:47.789+08:00, 2020-05-17T19:53:42.789+08:00
2.2.通过 Cookie 匹配
Cookie Route Predicate 可以接收两个参数,一个是 Cookie name ,一个是正则表达式Cookie value,路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行。
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: http://www.dhy.com
predicates:
- Cookie=cookiename, cookievalue
使用 curl 测试,命令行输入:curl http://localhost:8777 --cookie "cookiename=cookievalue"
,则会返回zimug.com页面代码。也就是说当我们的请求携带了指定的cookie键值对的时候,请求才向正确的uri地址转发。
2.3.通过 Header 属性匹配
Header Route Predicate 和 Cookie Route Predicate 一样,也是接收 2 个参数,一个 header 中属性名称和一个正则表达式value,这个属性值和正则表达式value匹配的时候才进行路由转发。
spring:
cloud:
gateway:
routes:
- id: header_route
uri: http://www.dhy.com
predicates:
- Header=X-Request-Id, \\d+
上面的配置规则表示路由匹配存在名为X-Request-Id
,内容为数字的header的请求,将请求转发到 dhy.com
。
使用 curl 测试,命令行输入:curl http://localhost:8777 -H "X-Request-Id:88"
,则返回页面代码证明匹配成功。将参数-H "X-Request-Id:88"改为-H "X-Request-Id:somestr"再次执行时返回404证明没有匹配。
2.4.通过 Host 匹配
Host Route Predicate 接收一组参数,一组匹配的域名列表,这个模板是一个 ant 风格的模板,用逗号作为分隔符。它通过参数中的主机地址作为匹配规则。
spring:
cloud:
gateway:
routes:
- id: host_route
uri: http://www.dhy.com
predicates:
- Host=**.somehost.org,**.anotherhost.org
路由会匹配Http的Host诸如:www.somehost.org
或beta.somehost.org
或www.anotherhost.org
的请求。
2.5.通过请求Method匹配
通过HTTP的method是 POST、GET、PUT、DELETE 等不同的请求方式来进行路由。
spring:
cloud:
gateway:
routes:
- id: method_route
uri: http://www.dhy.com
predicates:
- Method=GET
以上规则决定:路由会匹配到所有GET方法的请求,其他的HTTP方法不做匹配。
使用 curl 测试,命令行输入:
- curl 默认是以 GET 的方式去请求,
curl http://localhost:8777
,测试返回页面代码,证明匹配到路由predicate规则 - 指定curl使用POST方法发送请求,
curl -X POST http://localhost:8777
,返回 404 没有找到,证明没有匹配
2.6.通过请求参数匹配
Query Route Predicate 支持传入两个参数,一个是属性名一个为属性值,属性值可以是正则表达式。
spring:
cloud:
gateway:
routes:
- id: query_route
uri: http://www.dhy.com
predicates:
- Query=foo, ba.
路由会匹配所有包含foo
,并且foo
的内容为诸如:bar
或baz
等符合ba.
正则规则的请求。
使用 curl 测试,命令行输入:curl localhost:8777?foo=bax
测试可以返回页面代码,将 foo的属性值改为 bazx再次访问就会报 404,证明路由需要匹配正则表达式才会进行路由。
2.7.通过请求 ip 地址进行匹配
Predicate 也支持通过设置某个 ip 区间号段的请求才会路由,RemoteAddr Route Predicate 接受 cidr 符号(IPv4 或 IPv6 )字符串的列表(最小大小为1),例如 192.168.0.1/16 (其中 192.168.0.1 是 IP 地址,16 是子网掩码)。
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://www.dhy.org
predicates:
- RemoteAddr=192.168.1.1/24
可以将此地址设置为本机的 ip (192.168.1.4)地址进行测试,curl localhost:8080
,则此路由将匹配。
2.8.通过权重分流匹配
通过权重weight分流匹配的predicate有两个参数:group
和weight
(一个int)。权重是按组计算的。以下示例配置权重路由谓词:
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
这条路线会将约80%的流量转发至weighthigh.org,并将约20%的流量转发至weightlow.org。
2.9.组合使用
spring:
cloud:
gateway:
routes:
- id: multi-predicate
uri: https://www.dhy.com
order: 0
predicates:
- Host=**.foo.org
- Path=/headers
- Method=GET
- Header=X-Request-Id, \\d+
- Query=foo, ba.
- Query=baz
- Cookie=chocolate, ch.p
各种 Predicates 同时存在于同一个路由时,请求必须同时满足所有的条件才被这个路由匹配。
自定义PredicateFactory
一、自定义Predicate Factory
虽然官方为我们提供了诸多的Predicate Factory(上一节介绍的),能够满足我们大部分的场景需求。但是不排除有些情况下,Predicate路由匹配条件比较复杂,这时就需要我们来自定义实现。
需求:
本节我们通过自定义实现一个简单的需求,所有Path以"/sysuser"、"/sysorg"、"/sysrole"、"/sysmenu"、"/sysdict"、"/sysapi"开头的Http请求都转发到本机的http://localhost:8401/
提供的aservice-rbac服务。
实现步骤
- 自定义路由predicate工厂需要继承 AbstractRoutePredicateFactory 类,重写 apply 方法的逻辑。
- 在 apply 方法中可以通过 exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息。
@Component
public class RbacAuthRoutePredicateFactory
extends AbstractRoutePredicateFactory<RbacAuthRoutePredicateFactory.Config>
public RbacAuthRoutePredicateFactory()
super(Config.class);
@Override
public Predicate<ServerWebExchange> apply(Config config)
return exchange ->
String requestURI = exchange.getRequest().getURI().getPath();
if (config.getFlag().equals("rbac")
&&(requestURI.startsWith("/sysuser")
||requestURI.startsWith("/sysorg")
||requestURI.startsWith("/sysrole")
||requestURI.startsWith("/sysmenu")
||requestURI.startsWith("/sysdict")
||requestURI.startsWith("/sysapi")))
return true; //表示匹配成功
return false; //表示匹配失败
;
//自定义参数args配置类
public static class Config
private String flag; //该参数对应配置文件的args
public String getFlag()
return flag;
public void setFlag(String flag)
this.flag = flag;
- 类的命名需要以 RoutePredicateFactory 结尾,比如 RbacAuthRoutePredicateFactory,那么在配置文件中使用该predicate的时候 RbacAuth就是这个路由predicate工厂的名称。
- 我们还可以为apply 方法传参数,如代码中的Config,flag字段和配置文件中的args字段名称是一一对应的。
spring:
application:
name: zimug-server-gateway
cloud:
gateway:
routes:
- id: rbsc-service
uri: http://localhost:8401/
predicates:
- name: RbacAuth
args:
flag: rbac
二、测试一下
前提:启动本机aservice-rbac服务及与其相关的其他公共Spring Cloud组件
访问http://127.0.0.1:8777/sysuser/pwd/reset
,请求正确的被gateway接收,并按照我们自定义的路由规则转发给本机的aservice-rbac服务。
编码方式构建静态路由
一、回顾一下配置文件的方式
server:
port: 8777
spring:
application:
name: dhy-server-gateway
cloud:
gateway:
routes:
- id: dhy # 路由 ID,唯一
uri: http://baidu.com/ # 目标 URI,路由到微服务的地址
predicates: # 请求转发判断条件
- Path=/baidu/** # 匹配对应 URL 的请求,将匹配到的请求追加在目标 URI 之后
- routes指的是配置路由转发规则,可以配置多个
- 每一个route有一个id,标识该路由的唯一性
- uri指的是请求转发的目标
- predicates是请求转发的判断条件,我们的例子使用的Path条件判断
上面的路由配置的含义是当我们访问:http://<gateway-ip>:8777/baidu/**
的时候,请求被转发到http://www.baidu.com/baidu/**
,其中**
匹配任意字符。
二、编码方式实现路由
下面的代码可以实现和配置文件实现方式一样的效果,所有的在配置文件中可以实现的predicates匹配规则,RouteLocatorBuilder 都有对应的api函数提供实现方法。
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder)
return builder.routes()
.route("dhy", r -> r.path("/baidu/**")
.uri("http://baidu.com"))
.build();
route方法的第一个参数是路由id,第二个参数是一个Function(函数式编程),通过传入lambda表达式来判断匹配规则。
编码方式实现Predicate路由匹配规则,比配置文件的方式更繁琐一些,但是它也不是一无是处!配置文件方式的多个predicates组合只能表达“and并且”的关系,而编码方式还可以表达“or或者”的关系。 如下图所示:
但是笔者一般工作中很少使用编码方式实现路由的配置,因为编码代表着“写死”,也就是静态的。我们更希望配置是可以动态更新的,配置文件的方式结合nacos可以实现路由配置的动态更新,后面的章节我们再去介绍!
Filter过滤器介绍与使用
一、过滤器简介
微服务网关经常需要对请求进行一些过滤操作,比如:鉴权之后添加Header携带令牌等。在过滤器中可以
- 为为请求增加请求头、增加请求参数 、增加响应头等等功能
- 鉴权、记录审计日志、统计请求响应时长等共性服务操作
微服务系统中有很多的服务,我们不希望在每个服务上都去开发鉴权、记录审计日志、统计请求响应时长等共性服务操作。所以对于这样的重复开发或继承类工作,放在gateway上面统一去做是最好不过了。
二、Filter的生命周期
Spring Cloud Gateway 的 Filter 的生命周期很简单,只有两个:“pre” 和 “post”。
- PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
三、Filter的分类
Spring Cloud Gateway 的 Filter 从作用范围可分为:
- GatewayFilter:应用到单个路由或者一个分组的路由上。
- GlobalFilter:应用到所有的路由上
笔者并不建议你去花很多的时间去学习下面的这些Filter都是如何使用,下面的这些Filter笔者几乎没有用到过。因为我已经介绍过了,Filter的作用就是在某些需求场景下去修改HTTP的请求头、路径、参数等等,只要你对HTTP协议足够的熟悉,所有的过滤器需求你都可以自定义实现,比起使用内置的Filter往往更加灵活。
3.1.Gateway filter
过滤器工厂 | 作用 | 参数 |
---|---|---|
AddRequestHeader | 为原始请求添加Header | Header的名称及值 |
AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 |
AddResponseHeader | 为原始响应添加Header | Header的名称及值 |
DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名称及去重策略 |
Hystrix | 为路由引入Hystrix的断路器保护 | HystrixCommand 的名称 |
FallbackHeaders | 为fallbackUri的请求头中添加具体的异常信息 | Header的名称 |
PrefixPath | 为原始请求路径添加前缀 | 前缀路径 |
PreserveHostHeader | 为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host | 无 |
RequestRateLimiter | 用于对请求限流,限流算法为令牌桶 | keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus |
RedirectTo | 将原始请求重定向到指定的URL | http状态码及重定向的url |
RemoveHopByHopHeadersFilter | 为原始请求删除IETF组织规定的一系列Header | 默认就会启用,可以通过配置指定仅删除哪些Header |
RemoveRequestHeader | 为原始请求删除某个Header | Header名称 |
RemoveResponseHeader | 为原始响应删除某个Header | Header名称 |
RewritePath | 重写原始的请求路径 | 原始路径正则表达式以及重写后路径的正则表达式 |
RewriteResponseHeader | 重写原始响应中的某个Header | Header名称,值的正则表达式,重写后的值 |
SaveSession | 在转发请求之前,强制执行WebSession::save 操作 | 无 |
SecureHeaders | 为原始响应添加一系列起安全作用的响应头 | 无,支持修改这些安全响应头的值 |
SetPath | 修改原始的请求路径 | 修改后的路径 |
SetResponseHeader | 修改原始响应中某个Header的值 | Header名称,修改后的值 |
SetStatus | 修改原始响应的状态码 | HTTP 状态码,可以是数字,也可以是字符串 |
StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的路径的数量 |
Retry | 针对不同的响应进行重试 | retries、statuses、methods、series |
RequestSize | 设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回 413 Payload Too Large | 请求包大小,单位为字节,默认值为5M |
ModifyRequestBody | 在转发请求之前修改原始请求体内容 | 修改后的请求体内容 |
ModifyResponseBody | 修改原始响应体的内容 | 修改后的响应体内容 |
Default | 为所有路由添加过滤器 | 过滤器工厂名称及值 |
每个过滤器工厂都对应一个实现类,并且这些类的名称必须以
GatewayFilterFactory
结尾,这是Spring Cloud Gateway的一个约定,例如AddRequestHeader
对应的实现类为AddRequestHeaderGatewayFilterFactory
。对源码感兴趣的小伙伴就可以按照这个规律拼接出具体的类名,以此查找这些内置过滤器工厂的实现代码
Filter并不如Predicate那么常用,更多的时候我们需要自定义Filter,所以官方内置的Filter我们就不一一介绍了。我们选两个例子来说明一下:
- AddRequestHeader GatewayFilter Factory
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: http://httpbin.org:80/get
filters:
- AddRequestHeader=X-Request-Foo, Bar
predicates:
- Method=GET
过滤器工厂会在匹配的HTTP的请求加上一个Header,名称为X-Request-Foo,值为Bar。
- RewritePath GatewayFilter Factory
在Nginx服务启中有一个非常强大的功能就是重写路径,Spring Cloud Gateway默认也提供了这样的功能。
spring:
cloud:
gateway:
routes:
- id: rewritepath_route
uri: http://httpbin.org
predicates:
- Path=/foo/**
filters:
- RewritePath=/foo/(Java之 Spring Cloud 微服务搭建网关SpringCloud Gateway微服务网关GateWay(第三个阶段)SpringBoot项目实现商品服务器端是调用
Java之 Spring Cloud 微服务搭建网关SpringCloud Gateway微服务网关GateWay(第三个阶段)SpringBoot项目实现商品服务器端是调用