Gateway网关
1、网关的位置与作用
Gateway是一个web项目,本质也是微服务(可当做过滤器使用),也是微服务的入口。
Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等
使用要求
基于Spring5,SpringBoot2
SpringCloud Gatway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通讯框架Netty
Spring Cloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架
统一的路由方式且基于Filter链
网关的位置
nginx做负载均衡,nginx去找网关。
网关的作用:负载均衡、过滤请求、验证令牌(统一鉴权)、 全局熔断
2、依赖与配置
创建新的工程:cloud-gateway-gateway9527,又是那无聊至极的3步
1、引入依赖
注意版本对应(在这里吃大亏了我~~~)
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> <spring-cloud.version>2.2.6.RELEASE</spring-cloud.version> </properties> <!--gateway网关--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <version>${spring-cloud.version}</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </exclusion> </exclusions> </dependency>
3、YML方式路由
网关做路由映射,就在配置文件中
server:
port: 8000
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:9001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
路由是构建网关的基本模块,它由4个属性组成
- 一个ID,保持唯一
- 一个目标URI
- 一系列断言
- 一组过滤器
如果请求与断言规则相匹配则进行路由
@RestController
public class PaymentController {
@GetMapping(value = "provider/payment/get/{id}")
public String getPaymentById(@PathVariable("id") Long id) {
return "根据id:" + id + ",查询订单成功";
}
}
4、代码方式路由
在代码中注入RouteLocator的Bean
@SpringBootApplication
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class, args);
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
// 构建多个路由routes
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
// 具体路由地址
routes.route("payment_path",r -> r.path("/payment/get/**").uri("http://localhost:9001")).build();
// 返回所有路由规则
return routes.build();
}
}
5、Nacos动态路由
新增以下依赖
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2.2.6.RELEASE</spring-cloud.version>
</properties>
<!--nacos:注册中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${spring-cloud.version}</version>
</dependency>
<!-- 路由策略使用lb的方式是,这个依赖一定要有 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>${spring-cloud.version}</version>
</dependency>
配置文件也需要变变
需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
server:
port: 8000
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://nacos-payment-provider #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
动态路由(通过服务名实现)默认情况下Gateway会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
uri的值lb://nacos-payment-provider,后面的nacos-payment-provider就是在nacos注册的服务名:
注意之前的路由配置,目标地址是IP+端口:
再次测试:没啥问题
过滤和断言
1、断言(Predicate)
Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合
Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://CLOUD-PAYMENT-SERVICE
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
#- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] 没到时间进行测试会报错
#- Cookie=username,xiaoyumao #并且Cookie是username=xiaoyumao才能访问
#- Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Method=GET
#- Query=username, \d+ #要有参数名称并且是正整数才能路由
各种 Predicates 同时存在于同一个路由时,请求必须同时满足所有的条件才被这个路由匹配。
2、内置路由过滤器
断言(predicates):满足指定的要求就开始工作;
路由(Route):满足断言的条件就路由到这个地方;
过滤器(Filter):到达指定地方前后由所有过滤器一起工作;
使用过滤器,可以在请求被路由前或者之后对请求进行修改
SpringCloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。
内置过滤器都是动态为请求添加一些行为
在路由配置中加入过滤器,这个演示的路由过滤器会给请求加一个请求头
当然是可以同时存在的,断言是判断请求对不对,过滤器是动态添加请求头
我们在8001服务的controller中获取并打印一下
String header = request.getHeader("X-Request-red"); System.out.println("header = "+header);
3、自定义全局过滤器
实现 GlobalFilter 和 Ordered,重写相关方法,加入到spring容器管理即可,无需配置,全局过滤器对所有的路由都有效。
@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("username");
if(StringUtils.isEmpty(uname)){
log.info("*****用户名为Null 非法用户,(┬_┬)");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0; //对多个过滤器排序
}
}
过滤器链是按org.springframework.core.Ordered
接口排序的,您可以通过实现方法来设置getOrder()
此时,再次访问相同的请求 : http://localhost:9527/payment/get/35
当你加一个请求参数username,访问的结果就又不一样了
此时:不管是自定义的过滤器还是内置的过滤器,都是会走的,只是在俩个不同的控制台,一个在9527,一个在8001。。。我当时还纠结了好久怎么只有其中一个过滤器生效呢,服了
高级
1、Gateway实现限流
1. Spring Cloud Gateway使用令牌桶算法实现限流。
2. Spring Cloud Gateway默认使用redis的Ratelimter限流算法来实现。所以我们要使用首先需要引入redis的依赖。
3.使用过程中,我们主要配置 令牌桶填充的速率,令牌桶的容量,指定限流的Key。
4.限流的Key,可以根据用户来限流 、IP限流、接口限流等等。
============
令牌桶算法(Token Bucket)
随着时间流逝系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token;如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务
"令牌桶算法”在能够限制数据的平均传输速率外,还允许某种程度的突发传输。在"令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,所以它适合于具有突发特性的流量。
以redis限流IP为例
1,引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
2,IP限流的keyResolver
@Bean public KeyResolver ipKeyResolver() { return exchange -> Mono. just(exchange. getRequest(). getRemoteAddress(). getHostName()); }
3,配置文件
filters : - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 key-resolver: "#{@ipKeyResolver}"
flter名称必须是RequestRateLimiter
redis-rate-limiter.replenishRafe :允许用户每秒处理多少个请求
redis-rate-limiter.burstCapacity :令牌桶的容量,允许在-秒钟内完成的最大请求数
key-resolver :使用SpEL按名称引用bean
=============================
漏桶(Leaky Bucket)算法
水(请求)先进入到漏桶内,桶以一定的速度出水(接口的响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率
2、GateWay工作原理
网关原理:经过各个过滤器的过滤之后,将满足指定断言规则的请求路由到指定位置
1.客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求匹配的路由, 将其发送到Gateway Web Handler. 2.Handler再通过指定的过滤器链来将请求发送给我们实际的服务执行业务逻辑,然后返回。
上图中过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前("pre")或之后("post")执行业务逻辑。
Filter在"pre"类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等, 在"post"类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量控制等有着非常重要的作用
WebFlux
1、webflux
在webflux中,我们以操作符(operator)串起来的流来定义我们想让程序如何执行,但这只是一个声明,符合“在订阅(subscribe)之前,什么都不会发生”。
webflux很适合IO密集型的应用,比如spring-cloud-gateway就是基于webflux实现的,作为一个网关型应用,gateway是典型的网络IO密集型应用,每一个请求的处理过程中有大量的IO阻塞(等待各个微服务请求返回)。
2、Flux和Mono
可以把Flux和Mono想象成一个水管,它包裹着我们实际要处理的元素,我们以流式编程(stream)的方式(类似java8的stream)对其中的元素做处理,Flux和Mono最主要的区别就是可以包裹的元素个数不同。
Flux是一个发出(emit)0-N个元素组成的异步序列的Publisher,可以被onComplete信号或者onError信号所终止。在响应流规范中存在三种给下游消费者调用的方法 onNext, onComplete, 和onError。
Mono 是一个发出(emit)0-1个元素的Publisher,可以被onComplete信号或者onError信号所终止。
Flux和Mono都是数据流的发布者,使用Flux和Mono都可以发出三种数据信号:
- 元素值,
- 错误信号,终止数据流的同时把错误信息传递给订阅者,
- 完成信号, 错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了。
三种信号的特点:
1) 错误信号和完成信都是终止信号,不能共存
2) 如果没有发送任何元素值,而是直接发送错误信号或者完成信号,表示是空数据流
3) 如果没有错误信号,没有完成信号,表示是无限数据流
3、操作符
Flux和Mono支持很多个操作符(operator),这些操作符好像是流水线上的一个个工作台,每一个操作符加上我们自己在这个操作符上定义的行为,定义了这个工作台对元素进行的处理。
两个常见操作符:
1) map
元素映射为新元素
2) flatMap
元素映射为流
做法:把每个元素先转换为流,再把转换之后的多个流合成一个大的流
4、请求上下文
在webflux中,面对保存【某次请求特有数据】这种需求的时候,我们是不能使用ThreadLocal的,因为一个请求可能是好几个线程来完成的,请求处理过程中的每一次阻塞都有可能导致线程的切换。Reactor提供了Context这个工具来帮我们实现这种需求,这里的Context,指的就是请求的上下文。
5、基于注解的响应式组件
WebFlux 支持两种编程模式:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>3.1.2</version>
</dependency>
Mono
封装了一个 Employee
资源,因为最多只能返回一个 Employee
@GetMapping("/{id}")
public Mono<Employee> getEmployeeById(@PathVariable String id) {
return employeeRepository.findEmployeeById(id);
}
对于集合资源,使用 Employee
类型的 Flux
,因为它是 0...n
个元素的发布者。
@GetMapping
public Flux<Employee> getAllEmployees() {
return employeeRepository.findAllEmployees();
}
6、WebClient
Spring 5 中引入的 WebClient,是一种支持响应式流的非阻塞客户端。
使用 WebClient
创建一个客户端,以从 EmployeeController
提供的端点检索数据。
WebClient client = WebClient.create("http://localhost:8080");
从端点 /employee/{id}
获取 Mono
类型的单个资源:
Mono<Employee> employeeMono = client.get()
.uri("/employees/{id}", "1")
.retrieve()
.bodyToMono(Employee.class);
employeeMono.subscribe(System.out::println);
同样,从端点 /employees
获取 Flux
类型的集合资源:
Flux<Employee> employeeFlux = client.get()
.uri("/employees")
.retrieve()
.bodyToFlux(Employee.class);
employeeFlux.subscribe(System.out::println);
1