Gateway网关
gitee:springcloud_study: springcloud:服务集群、注册中心、配置中心(热更新)、服务网关(校验、路由、负载均衡)、分布式缓存、分布式搜索、消息队列(异步通信)、数据库集群、分布式日志、系统监控链路追踪。
1. 概述简介
官网:Spring Cloud Gateway
Gateway该项目提供了一个构建在Spring生态系统之上的API网关,包括:Spring 5、Spring Boot 2和project Reactor。Spring Cloud Gateway旨在提供一种简单而有效的方法来路由到API,并为它们提供跨领域的关注点,例如:安全性、监控/度量和弹性。SpringCloud Gateway作为 Spring Cloud 生态系统中的网关,目标是替代zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。 Netty异步非阻塞
Gateway能做反向代理、鉴权、流量控制、熔断、日志监控...
Gateway特性:
基于Spring Framework 5, Project Reactor和Spring Boot 2.0进行构建; 动态路由:能够匹配任何请求属性; 可以对路由指定Predicate (断言)和 Filter (过滤器);集成Hystrix的断路器功能; 集成 Spring Cloud服务发现功能; 易于编写的 Predicate (断言)和Filter(过滤器); 请求限流功能; 支持路径重写。
Gateway与Zuul区别:
在SpringCloud Finchley 正式版之前,Spring Cloud推荐的网关是Netflix提供的Zuul: 1、Zuul 1.x,是一个基于阻塞!/O的API Gateway 2、Zuul 1.基于Servlet 2.5使用阻塞架构它不支持任何长连接(如WebSocket)zuul的设计模式和Nginx较像,每次VО操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Ngink用C++实现,Zuul用Java实现,而JVM本身会有第一次加载较慢的情况,使得Zuul的性能相对较差。 3、Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul .x的性能较Zul 1.x有较大提升。在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS(每秒请求数)是Zuul的1.6倍。 4、Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot 2之上,使用非阻塞API。 5、Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验
SpringMVC与Spring WebFlux
传统的Web框架,比如说: struts2,springmvc等都是基于Servlet API与Servlet容器基础之上运行的。但是 在Servlet3.1之后有了异步非阻寒的支持。而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring5必须让你使用java8) Spring WebFlux是Spring 5.0 引入的新的响应式框架,区别于Spring MVC,它不需要依赖Servlet API,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范。
2. 三大核心概念
Router(路由)
Route: The basic building block of the gateway. It is defined by an ID, a destination URI, a collection of predicates, and a collection of filters. A route is matched if the aggregate predicate is true. 路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
Predicate(断言)
Predicate: This is a Java 8 Function Predicate. The input type is a Spring Framework ServerWebExchange. This lets you match on anything from the HTTP request, such as headers or parameters. 参考的是Java8的java.util.function.Predicate 开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
Filter(过滤)
Filter: These are instances of Spring Framework GatewayFilter that have been constructed with a specific factory. Here, you can modify requests and responses before or after sending the downstream request. 指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
工作流程原理:
客户端向 Spring Cloud 网关发出请求。如果网关处理程序映射确定请求与路由匹配,则会将其发送到网关 Web 处理程序。 此处理程序通过特定于请求的筛选器链运行请求。 筛选器用虚线划分的原因是筛选器可以在发送代理请求之前和之后运行逻辑。 执行所有“预”过滤器逻辑。然后发出代理请求。发出代理请求后,将运行“post”筛选器逻辑。核心逻辑就是路由转发+执行过滤器链
3. 入门配置
新建cloud-gateway-gateway9527项目
-
pom文件
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
-
application.yaml
server:port: 9527
spring:application:name: cloud-gateway
eureka:instance:hostname: cloud-gateway-serviceclient:service-url:defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eurekaregister-with-eureka: truefetch-registry: true
-
主启动类
@SpringBootApplication
@EnableEurekaClient
public class GatewayMain9527 {public static void main(String[] args) {SpringApplication.run(GatewayMain9527.class,args);}
}
-
9527做网关的路由映射,增加yaml(考虑像eureka一样直接一个集群注入,不用每一项写入)
spring:application:name: cloud-gatewaycloud:gateway:routes:- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名uri: http://localhost:8001 #匹配后提供服务的路由地址predicates: #断言,路径相匹配的进行路由- Path=/payment/get/**- id: payment_routh2uri: http://localhost:8001predicates:- Path=/payment/lb/**
-
gateway网关不需要web的jar,去除:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
测试:
代码中注入RouteLocator的Bean
上面是在yaml中配置,截下来是在代码中直接注入:
@Configuration
public class GatewayConfig {@Beanpublic RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){RouteLocatorBuilder.Builder builder=routeLocatorBuilder.routes();builder.route("path_route_yicai",r->r.path("/qq_53957101/article/details/130376180").uri("https://blog.csdn.net/")).build();return builder.build();}
}
测试:
后面不稳定一下就跳动,建议可以使用自己的项目来做映射。
4. 通过微服务名实现动态路由
默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能。
yaml文件修改:
spring:application:name: cloud-gatewaycloud:gateway:discovery:locator:enabled: true #开启从注册中心动态创建路由的功能,利用徼服务名进行路由routes:- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址uri: lb://cloud-payment-service #匹配后提供服务的路由地址predicates: #断言,路径相匹配的进行路由- Path=/payment/get/**- id: payment_routh2
# uri: http://localhost:8001uri: lb://cloud-payment-servicepredicates:- Path=/payment/lb/**
测试:
5. Predicate的使用
1.路由后谓词工厂采用一个参数,即日期时间。 此谓词匹配在指定日期时间之后发生的请求。 2.路由之前谓词工厂采用一个参数 . 此谓词匹配在指定的。 3.路由谓词工厂采用两个参数,和 。 此谓词匹配在 之后和之前发生的请求。参数必须位于之后。 4.cookie 路由谓词工厂采用两个参数,即 cookie 名称和正则表达式。 此谓词匹配具有给定名称且其值与正则表达式匹配的 Cookie。 5.标头路由谓词工厂采用两个参数,即标头名称和正则表达式。 此谓词与具有给定名称的标头匹配,该标头的值与正则表达式匹配。 ...
操作:
日期
-
闯将一个测试类T2
public class T2 {public static void main(String[] args) {ZonedDateTime zbj=ZonedDateTime.now();System.out.println(zbj);}
}
-
修改配置yaml
- id: payment_routh2
# uri: http://localhost:8001uri: lb://cloud-payment-servicepredicates:- Path=/payment/lb/**- After=2023-07-09T10:44:16.126+08:00[Asia/Shanghai]
测试: 一个刚刚:
一个几小时后:
Cookie
-
修改yaml
- id: payment_routh2
# uri: http://localhost:8001uri: lb://cloud-payment-servicepredicates:- Path=/payment/lb/**- Cookie=username,zzyy
使用Curl测试:
Header
-
修改yaml
- id: payment_routh2
# uri: http://localhost:8001uri: lb://cloud-payment-servicepredicates:- Path=/payment/lb/**- Header=X-Request-Id,\d+
使用curl测试:
6. Filter的使用
官网自带
生命周期:pre/post 种类:GatewayFilter/GlobalFilter
-
yaml修改,添加AddRequestParameter
filters:- AddRequestParameter=X-Request-Id,1024 #过滤器工厂会在阵单的请求头加上一对请求头,名称为X-Request-Id值为1024
自定义过滤器(自定义全局GlobalFilter)
@Component
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter, Ordered {
@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {log.info("*********come in MyLogGlobalGatewayFilter:"+new Date());String uname=exchange.getRequest().getQueryParams().getFirst("uname");if(uname==null){log.info("非法");exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);return exchange.getResponse().setComplete();}return chain.filter(exchange);}
@Overridepublic int getOrder() {return 0;}
}
测试: