官网:Spring Cloud Gateway
Geteway是Zuul的替代,
Zuul网关采用同步阻塞模式不符合要求。
Spring Cloud Gateway基于Webflux,比较完美地支持异步非阻塞编程,很多功能实现起来比较方便。一、相同点:
1、底层都是servlet
2、两者均是web网关,处理的是http请求
二、不同点:
1、内部实现:
gateway对比zuul多依赖了spring-webflux,在spring的支持下,功能更强大,内部实现了限流、负载均衡等,扩展性也更强,但同时也限制了仅适合于Spring Cloud套件
zuul则可以扩展至其他微服务框架中,其内部没有实现限流、负载均衡等。
2、是否支持异步
zuul仅支持同步
gateway支持异步。理论上gateway则更适合于提高系统吞吐量(但不一定能有更好的性能),最终性能还需要通过严密的压测来决定
3、框架设计的角度
gateway具有更好的扩展性,并且其已经发布了2.0.0的RELESE版本,稳定性也是非常好的
4、性能
WebFlux 模块的名称是 spring-webflux,名称中的 Flux 来源于 Reactor 中的类 Flux。Spring webflux 有一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务,在伸缩性方面表现非常好。使用非阻塞API。 Websockets得到支持,并且由于它与Spring紧密集成,所以将会是一个更好的 开发 体验。
Zuul 1.x,是一个基于阻塞io的API Gateway。Zuul已经发布了Zuul 2.x,基于Netty,也是非阻塞的,支持长连接,但Spring Cloud暂时还没有整合计划。三、总结
总的来说,在微服务架构,如果使用了Spring Cloud生态的基础组件,则Spring Cloud Gateway相比而言更加具备优势,单从流式编程+支持异步上就足以让开发者选择它了。
对于小型微服务架构或是复杂架构(不仅包括微服务应用还有其他非Spring Cloud服务节点),zuul也是一个不错的选择。
Gateway是一个web项目,本质也是微服务(可当做过滤器使用),也是微服务的入口。
Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等
使用要求
基于Spring5,SpringBoot2
SpringCloud Gatway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通讯框架Netty
Spring Cloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架
统一的路由方式且基于Filter链
网关的位置
nginx做负载均衡。(nginx没法写业务)nginx去找网关。
网关的作用:负载均衡、过滤请求、验证令牌(统一鉴权)、 全局熔断
网关原理:经过各个过滤器的过滤之后,将满足指定断言规则的请求路由到指定位置
1.客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求匹配的路由, 将其发送到Gateway Web Handler. 2.Handler再通过指定的过滤器链来将请求发送给我们实际的服务执行业务逻辑,然后返回。
上图中过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前("pre")或之后("post")执行业务逻辑。
Filter在"pre"类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等, 在"post"类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量控制等有着非常重要的作用
路由是构建网关的基本模块(路由是一个动作),它由4个属性ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
创建新的工程:cloud-gateway-gateway9527,又是那无聊至极的3步
1、引入依赖
org.springframework.cloud spring-cloud-starter-gateway 2、配置文件
server: port: 9527 spring: application: name: cloud-gateway eureka: instance: hostname: cloud-gateway-service client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7001/eureka
3、主启动
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); } }
网关做路由映射,就在配置文件中
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:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
按道理说,网关后面应该跟的是消费端,现在咱不那么麻烦,直接让网关路由到提供端8001。
启动测试:
当然,你要是直接访问8001也是好使的,老师所说的隐藏我也不觉得安全啊!,照样能访问
动态路由
默认情况下Gateway会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能
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/** #断言,路径相匹配的进行路由
使用网关访问,
第一次
第二次
如果请求与断言规则相匹配则进行路由
Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合
Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理
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+ #要有参数名称并且是正整数才能路由
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,引入依赖
org.springframework.boot spring-boot-starter-data-redis-reactive 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)算法
水(请求)先进入到漏桶内,桶以一定的速度出水(接口的响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率
断言(predicates):满足指定的要求就开始工作;
路由(Route):满足断言的条件就路由到这个地方;
过滤器(Filter):到达指定地方前后由所有过滤器一起工作;
使用过滤器,可以在请求被路由前或者之后对请求进行修改
SpringCloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。
内置过滤器都是动态为请求添加一些行为
在路由配置中加入过滤器,这个演示的路由过滤器会给请求加一个请求头
当然是可以同时存在的,断言是判断请求对不对,过滤器是动态添加请求头
我们在8001服务的controller中获取并打印一下
String header = request.getHeader("X-Request-red"); System.out.println("header = "+header);
自定义全局过滤器 implements GlobalFilter, Ordered
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono 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; //对多个过滤器排序
}
}
此时,再次访问相同的请求 : http://localhost:9527/payment/get/35
当你加一个请求参数username,访问的结果就又不一样了
此时:不管是自定义的过滤器还是内置的过滤器,都是会走的,只是在俩个不同的控制台,一个在9527,一个在8001。。。我当时还纠结了好久怎么只有其中一个过滤器生效呢,服了