在cloud中还有一个服务网关Zuul,不过现在已经过时了,如果使用的是F之前的版本的cloud或者还在使用zuul的话,可以看我的另一篇文章
1. 概述
1.1 简介
Gateway是在Spring生态系统之上构建的API网关服务,基于Spring5,Spring Boot2和Project Reactor等技术。
SpringCloud Gateway 是Spring Cloud的一个全新项目,基于Spring5.0+SpringBoot2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。
SpringCloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Zuul,在Spring Cloud2.0以上版本中,没有对新版本Zuul2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.X非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
Spring Cloud Gateway的目标提供统一的路由方式且基于Filter链的方提供了网关基本的功能,例如:安全,监控/指标,和限流。
SpringCloud Gateway使用的是WebFlux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架,对于高并发与非阻塞式有很大的优势
1.2 作用
1)反向代理
2)鉴权
3)流量控制
4)熔断
5)日志监控
1.3 微服务架构中网关所处的位置
1.4 Gateway的特性
1)基于Spring Framework5,Project Reactor和spring Boot2.0进行构建
2)动态路由:能够匹配任何请求属性
3)可以对路由指定Predicate(断言)和Filter(过滤器)
4)集成Hystrix的断路器功能
5)集成spring Cloud服务发现功能
6)易于编写的Predicate(断言)和Filter(过滤器)
7)请求限流功能
8)支持路径重写
1.5 Webflux简介
传统的web框架,比如说:Struts2,SpringMVC等都是基于Servlet APi与servlet容器基础上运行的,但是,在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型的非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty、Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(spring5.0要求JDK1.8以上)
Spring WebFlux是spring5.0引入的新的响应式框架,区别于Spring MVC,它不需要依赖Servlet API,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范。
2. GateWay三大核心概念
WEB请求通过一些匹配条件,定位到真正的服务节点,并在这个转发过程的前后,进行一些精细化控制,predicate就是我们的匹配条件,filter可以理解为一个无所不能的拦截器,有了这两个元素,再加上目标URI,就可以实现一个具体的路由了
2.1 Route(路由)
路由是构建网关的基本模块,由ID、目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
2.2 Predicate(断言)
参考的是JDK8的java.util.function.Predicate,开发人员可以匹配HTTP请求中所有的内容(例如:请求头或请求参数),如果请求与断言相匹配则进行路由
2.3 Filter(过滤)
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改
3. GateWay的工作流程
客户端向Spring Cloud Gateway发出请求,然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler。Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间可以在发送代理请求之前(pre)或者之后(post)执行业务逻辑,Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
路由转发+执行过滤器链
4. 入门配置
新建一个工程cloud-gateway-gateway9527
4.1 pom文件
gateway坐标
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
完整文件
<?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>cloud2020</artifactId>
<groupId>com.bjc.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-gateway-gateway9527</artifactId>
<dependencies>
<!-- Hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 引入eureka客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 一般通用配置 -->
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.bjc.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
注意:在gateway中,不需要web启动类和actuator,即不需要配置以下内容
<!-- 添加bootweb启动依赖与健康监控依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
4.2 yml配置文件
server:
port: 9527
spring:
application:
name: cloud-gateway
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
4.3 启动类
package com.bjc.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class GatewayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GatewayMain9527.class,args);
}
}
4.4 yml新增网关配置
在yml中新增如下配置
完整的yml
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则,但要求唯一
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/**
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
4.4 测试
1)分别启动eureka服务、provider(8001)服务和网关(9527)服务
2)使用网关访问
5. Gateway配置路由
5.1 静态路由配置的两种方式
5.1.1 在yml配置文件中配置
见上面的案例
5.1.2 代码中注入RouteLocator的Bean
package com.bjc.cloud.config;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.PredicateSpec;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.function.Function;
@Configuration
public class GatewayConfig {
/**
* 配置一个id为zoudm的路由规则,当访问地址 http://localhost:9527/guonei时会自动转发到地址 http://news.baidu.com/guonei
* */
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder){
RouteLocatorBuilder.Builder routes = builder.routes();
// http://news.baidu.com/guonei
routes.route("zoudm",r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
@Bean
public RouteLocator customRouteLocator1(RouteLocatorBuilder builder){
RouteLocatorBuilder.Builder routes = builder.routes();
// http://news.baidu.com/guonei
Function<PredicateSpec, Route.AsyncBuilder> r1 = a -> a.path("/guoji").uri("http://news.baidu.com/guoji");
// routes.route("zoudm",r -> r.path("/guoji").uri("http://news.baidu.com.guoji")).build();
routes.route("zoudm",r1).build();
return routes.build();
}
}
注意:这里定义了两个路由
测试:
5.2 通过微服务名实现动态路由
默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
1)修改yml配置文件
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则,但要求唯一
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/**
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
2)访问 http://localhost:9527/payment/lb
可以看到 8001/8002端口切换
在测试get,如图:
6. predicate的使用
我们先启动Gateway工程9527,观察控制台,如图:
这里打印出了一系列的RoutePredicateFactory,其中有一个Path,再看我们的配置文件,如图:
一个predicates下配置了一个Path,以此为突破口,我们来看看其他断言是什么玩意。
6.1 简介
SpringCloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。
SpringCloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个RoutePredicate工厂可以进行组合。
SpringCloud Gateway创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给Route,SpringCloud Gateway包含许多内置的Route Predicate Factories。
所有这些谓词都匹配HTTP请求的不同属性,多种谓词工厂可以组合并通过逻辑and。说白了,predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。
6.2 常用的Route Predicate
参看官网地址:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gateway-request-predicates-factories
6.2.1 After Route Predicate
在什么时间之后访问才能生效。官网给的案例配置如下:
这个时间是什么?我们怎么获取这个时间了?这个就需要用到JDK8给我们提供的一个类ZonedDateTime来获取了,例如:
ZonedDateTime zone = ZonedDateTime.now();// 默认时区
我们将zone打印出来,就可以得到如下字符串:2020-04-10T21:46:16.015+08:00[Asia/Shanghai]
然后将这个字符串复制到我们的yml配置文件中,如图:
配置的意思是,在2020-04-10 21:46:16之后访问才有效。
访问连接http://localhost:9527/payment/get/1,如图:
时间未到,所以不允许访问,当到了46分钟的时候,再次访问,如图:
6.2.2 Before Route Predicate与Between Route Predicate
与After类似,只需要根据官网的配置,在yml中配置一下即可
1)Before Route Predicate
2)Between Route Predicate
注意:两个时间之间用逗号分隔。
6.2.3 Cookie Route Predicate
Cookie Route Predicate需要两个参数,一个是cookie name,一个是正则表达式。路由规则会通过获取对应的cookie name值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配则不执行
官网示例:
这里我们不使用postman或者Jmeter工具了,转而使用另一种根据curl,首先访问不配置cookie,访问一下,如图:
然后,我们配一下cookie断言,如图:
1)不带cookie的访问
如图;报404
2)带cookie的访问
加上cookie参数,可以正常访问,如图:
6.2.4 Header Route Predicate
两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行
官网示例:
在9527配置如下:
测试:
6.2.5 Host Route Predicate
Host Route Predicate接收一组参数,一组匹配的域名列表,这个模板是一个ant分隔的模板,用.号作为分隔符,它通过参数中的主机地址作为匹配规则
官网示例:
表示请求url需要以.somehost.org结尾。
6.2.6 Method Route Predicate
6.2.7 Path Route Predicate
我们之前的配置中都使用了该配置的,官网实例如图;
同时,我们一般可以配置Method断言搭配使用,某某连接需要使用get方法才能访问等,例如:
6.2.8 Query Route Predicate
带查询条件的断言,支持传入参数,一个是属性名一个是属性值,属性值可以是正则表达式。例如:
访问:
7. 过滤器
路由过滤器可用于修改进入的http请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。Spring cloud Gateway内置了多种路由过滤器,他们都由Gatewayfilter的工厂类来产生。
7.1 内置的过滤器
7.1.1 生命周期
1)pre:业务逻辑之前
2)post:业务逻辑之后
7.1.2 种类
1)GatewayFilter:单一过滤器,官网地址:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories
官网默认给出的过滤器很多,可以参考官网给出的例子去做就行了,例如:
2)GlobalFilter:全局过滤器,参看官网例子:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#global-filters
7.2 自定义全局过滤器
我们可以通过自定义过滤器来实现全局日志记录、统一网关鉴权等功能
操作步骤
1)定义过滤器实现两个接口 GlobalFilter,Ordered
package com.bjc.cloud.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Date;
@Component
@Slf4j
public class LogGloableFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("**** come in LogGloableFilter: " + new Date());
// spring5.0 的语法,用于获取request对象
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if(StringUtils.isEmpty(uname)){
log.error("非法用户!");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);// 设置返回状态
return exchange.getResponse().setComplete();// 完成退出
}
return chain.filter(exchange); // 放行
}
/*
加载过滤器的顺序
* */
@Override
public int getOrder() {
return 0;
}
}
2)测试
启动微服务,访问连接http://localhost:9527/payment/lb?uname=123 如图:
后台打印信息:
然后将uname的值去掉,如图:
后台打印信息: