服务网关
类比医院的分诊台
一、Zuul
由Netflix团队研发,不再使用
官网:https://github.com/Netflix/zuul/wiki
1. Zuul1.x模型
Springcloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是传统的Servlet I0处理模型。
学过尚硅谷web中期课程都知道一个 题目,Servlet的生命周期?servlet由servlet container进行生命周期管理。
container启动时构造servlet对象并调用servlet init()进行初始化;
container运行时接受请求,并为每个请求分配一个线程 (一般从线程池中获取空闲线程) 然后调用service().
container关闭时调用servlet destory()销毁servlet;
上述模式的缺点:
servlet是一个简单的网络IO模型,当请求进入servlet container时,servlet container就会为其绑定一个线程, 在并发不高的场景下这种模型是适用的。但是一旦高并发(比如用jemeter压测),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个request分配一个线程, 只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势
所以Zuul 1.X是基于servlet之上的一个阻塞式处理模型,即spring实现了 处理所有request请求的一个servlet (DispatcherServlet) 并由该servlet阻塞式处理。所以Springcloud Zuul无法摆脱servlet模型的弊端.
二、Spring Cloud Gateway
由Spring团队研发,用到了Netty、Spring WebFlux等新技术
1. 概述简介
官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/
1.1 是什么
Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;但在2.x版本中,zuul的升级一直跳票 ,SpringCloud最后自己研发 了一个网关替代Zuul。
一句话: SpringCloud Gateway是原zuul1.x版的替代
![](https://gitee.com/honourer/picturebed/raw/master/SpringCloud/图像 (22).png)
Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5, Spring Boot 2和Project Reactor等技术。
Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如: 熔断、限流、重试等
SpringCloud Gateway是Spring Cloud的一个全新项目,基于Spring 5.0+ Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式。
SpringCloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
Spring Cloud Gateway的目标提供统一的路由方式且基于Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
Spring Cloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架【对于高并发、非阻塞式通信的场景非常有优势】
1.2 能干嘛
反向代理
鉴权
流量控制
熔断
日志监控
。。。。。。
网关可以帮我们动态的路由到各个服务,同时能够通过注册中心实时感知服务的状态,保证能够把请求路由到工作正常的服务上
另外,鉴权、监控等功能各个服务都要有,不适合放到每个服务中去做一遍
而是应该统一交给网关,降低代码冗余
1.3 微服务架构中的网关
1.4 为什么选择Gateway?
商业原因
-
neflix不太靠谱,zuul2.0一直跳票,迟迟不发布
一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖。而且很多功能ZuuI都没有用起来,Gateway要比Zuul更加简单便捷。
Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的Zuul 2.x,但Spring Cloud貌似没有整合计划。而且Netflix相关组件 都宣布进入维护期;不知前景如何?
多方面综合考虑Gateway是很理想的网关选择。
技术原因
-
SpringCloud Gateway具有如下特性
基于Spring Framework 5, Project Relactor和Spring Boot 2.0进行构建;
动态路由:能够匹配任何请求属性;
可以对路由指定Predicate (断言)和Filter (过滤器) ;
集成Hystrix的断路器功能;
集成Spring Cloud服务发现功能;
易于编写的Predicate (断言)和Filter (过滤器) ;
请求限流功能;
支持路径重写。 -
SpringCloud Gateway与Zuul的区别
在SpringCloud Finchley 正式版之前, Spring Cloud推荐的网关是Netflix提供的Zuul:
- Zuul 1.x, 是一个基于阻塞I/0的API Gateway
- Zuul 1.x基于Servlet 2. 5使用阻塞架构它不支持任何长连接(如WebSocket) Zuul的设计模式和Nginx较像,每次I/0操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul 用Java实现,而JVM本身会有第一次加载较慢的情况,使得Zuul的性能相对较差。
- Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接(如WebSocket) ,但SpringCloud目前还没有整合。Zuul 2.x的性能较Zuul 1.x 有较大提升。在性能方面,根据官方提供的基准测试, Spring Cloud Gateway的RPS (每秒请求数)是Zuul的1.6倍。
- Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot2之上,使用非阻塞API。
- Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有 更好的开发体验
1.5 Gateway模型
传统的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来实现响应式流规范。
Spring WebFlux:SpringMVC的小弟弟
2. 三大核心概念
-
Route(路由)
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
路由的构成:ID,目标URI,一系列的断言和过滤器
-
Predicate(断言)
参考的是java8的java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
请求发给API网关,网关要不要路由到目标URI,需要经过一个条件的判断,这个条件判断就是断言,断言的依据是HTTP请求中的任意内容,比如请求头的内容、请求参数的内容
-
Filter(过滤)
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
总结:请求来到网关,网关利用断言来判定这次请求是否符合某个路由规则,如果符合了,就会经过过滤器的处理最终到达目标的URI
web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
predicate就是我们的匹配条件;而filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri就可以实现一个具体的路由了
3. Gateway工作流程
客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler.
Gateway Web Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前( "pre" )或之后( "post" )执行业务逻辑。
Filter在"pre" 类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等;在"post"类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
核心逻辑:路由转发+执行过滤器链
4. 入门配置
项目架构
-
建Module【cloud-gateway-gateway9527】
-
改POM
注:网关作为一种微服务,也要注册到服务注册中心,所以也要引入eureka-client
<?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.atguigu.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-gateway-gateway9527</artifactId> <dependencies> <!--网关不需要引入spring-boot-starter-web,否则启动会报错--> <!--<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> <!--gateway--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <!--网关作为一种微服务,也要注册到服务注册中心--> <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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
-
写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
-
业务类【无】
-
主启动
package com.atguigu.springcloud; 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); } }
-
YML新增网关配置
匹配cloud-provider-payment8001中的两个接口地址/payment/get/{id}和/payment/lb,cloud-gateway-gateway9527的YML改为如下内容
server: port: 9527 spring: application: name: cloud-gateway cloud: # 网关配置 gateway: routes: - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: http://localhost:8001 #匹配后提供服务的路由地址 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由 - id: payment_routh2 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
-
测试
启动EurekaMain7001
启动PaymentMain8001
启动GateWayMain9527
访问http://localhost:8001/payment/get/1和http://localhost:9527/payment/get/1都能请求到服务
-
Gateway网关路由的两种配置方式
①【配置文件】YML配置,如:
server: port: 9527 spring: application: name: cloud-gateway cloud: # 网关配置 gateway: routes: - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: http://localhost:8001 #匹配后提供服务的路由地址 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由 - id: payment_routh2 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
②【编码】注入RouteLocator的Bean
以一个例子进行说明,现在要通过cloud-gateway-gateway9527访问到百度新闻【https://news.baidu.com/】
在cloud-gateway-gateway9527中创建配置类GateWayConfig
package com.atguigu.springcloud.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 builder){ RouteLocatorBuilder.Builder routes = builder.routes(); // 添加路由转发规则,访问http://localhost:9527/documentation.html时会转发到http://redis.cn/documentation.html // 涉及Lambda表达式和应用型函数【Java 8新特性】 routes.route("path_route_atguigu", r -> r.path("/documentation.html") .uri("http://redis.cn")).build(); return routes.build(); } }
访问http://localhost:9527/documentation.html成功跳转到http://redis.cn/documentation.html
5. 通过微服务名实现动态路由
我们在4.入门配置中,服务提供者的访问路径写死了,导致无法实现负载均衡。
默认情况下Gateway会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
这里用到了单机版Eureka服务注册中心,集群版服务提供者cloud-provider-payment8001和cloud-provider-payment8002
将cloud-gateway-gateway9527的YML改为如下配置
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
# 网关配置
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
# uri的协议为lb,表示启用Gateway的负载均衡功能。
# lb://serviceName是spring cloud gateway在微服务中自动为我们创建的负载均衡uri
# serviceName要写cloud-provider-payment8001和cloud-provider-payment8002的微服务名
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2
# 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
启动EurekaMain7001
启动PaymentMain8001
启动PaymentMain8002
启动GateWayMain9527
多次访问http://localhost:9527/payment/lb
PaymentMain8001和PaymentMain8002轮流服务,实现了负载均衡
6. Predicate的使用
cloud-gateway-gateway9527启动时会有这样的信息
我们在配置网关时,只用到了一种
6.1 Route Predicate Factories是什么
![](https://gitee.com/honourer/picturebed/raw/master/SpringCloud/图像 (23).png)
Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。
Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个RoutePredicate工厂可以进行组合
Spring Cloud Gateway创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate 对象可以赋值给Route。Spring Cloud Gateway包含许多内置的Route Predicate Factories。
所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。
6.2 常用的Route Predicate
-
After Route Predicate
配置示例:
- After=2020-02-21T15:51:37.485+08:00[Asia/Shanghai]
设置的值是日期时间的一种格式,表示指定日期时间之后断言才为真
那这种日期格式如何生成呢?参考如下代码
import java.time.ZonedDateTime; public class Tc { public static void main(String[] args) { ZonedDateTime now = ZonedDateTime.now(); //2023-01-12T19:42:48.671+08:00[Asia/Shanghai] System.out.println(now); } }
-
Before Route Predicate
配置示例:
- Before=2020-02-21T15:51:37.485+08:00[Asia/Shanghai]
设置的值是日期时间的一种格式,表示指定日期时间之前断言才为真
-
Between Route Predicate
配置示例:
- Between=2020-02-21T15:51:37.485+08:00[Asia/Shanghai],2020-05-21T15:51:37.485+08:00[Asia/Shanghai]
设置的值是日期时间的一种格式,表示指定日期时间之间断言才为真
-
Cookie Route Predicate
配置示例:
-
Cookie=username,zzyy
Cookie Route Predicate需要两个参数,一个是Cookie name ,一个是正则表达式。 只有在请求时携带了指定的Cookie,并且Cookie的值满足指定的正则表达式断言才为真
curl不带cookies访问
curl带cookies访问
如果通过curl进行测试时返回结果中包含中文乱码,参考如下网址
https://blog.csdn.net/leedee/article/details/82685636
-
Header Route Predicate
配置示例:
- Header=A,\d+
只有在请求时请求头中携带了指定的属性,并且属性值满足指定的正则表达式断言才为真
curl不带请求头访问
curl带请求头访问
-
Host Route Predicate
配置示例:
- Host=**.atguigu.com,**.kk.com
只有请求头中Host属性的属性值满足指定的规则断言才为真
curl不指定Host访问
curl指定Host访问
-
Method Route Predicate
配置示例:
- Method=POST
只有通过指定的方式进行请求断言才为真
-
Path Route Predicate
配置示例:
- Path=/payment/lb/**
只有请求路径为指定的路径断言才为真
-
Query Route Predicate
配置示例:
- Query=username, \d+
只有请求路径中包含指定的参数,并且参数值满足指定的规则断言才为真
说白了,Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理
微服务架构中进行测试的辅助工具:JMeter【压测】、Postman、curl【相当于Postman的命令行界面】
7. Filter的使用
7.1 是什么
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。
Spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生
7.2 生命周期(Only Two)
pre:在业务逻辑之前
post:在业务逻辑之后
7.3 种类(Only Two)
GatewayFilter:单一
GlobalFilter:全局
7.4 自定义GlobalFilter
7.4.1 能干嘛
全局日志记录
统一网关鉴权
。。。。。。
7.4.2 实例
【cloud-gateway-gateway9527】中添加如下类
package com.atguigu.springcloud.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.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Date;
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("**********come in MyLogGateWayFilter:" + new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname == null){
log.info("*******用户名为null,非法用户,o(╥﹏╥)o");
// 设置响应状态码
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
// 沿着过滤器链传递给下一个过滤器进行处理
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 加载过滤器的顺序,一般数字越小,优先级越高
return 0;
}
}
启动EurekaMain7001
启动PaymentMain8001
启动PaymentMain8002
启动GateWayMain9527
访问http://localhost:9527/payment/lb?uname=z3,正常返回
访问http://localhost:9527/payment/lb ,406
标签:网关,系列,SpringCloud,gateway,cloud,Spring,Gateway,路由 From: https://www.cnblogs.com/wzzzj/p/18039254