Bootstrap

服务网关--Spring Cloud GateWay

1 概述
Spring Cloud Gateway是在Spring生态系统之上构建的API网关服务,
它旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断、限流、重试等。

Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。

Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的 Zuul 2.0之前的非Reactor模式的老版本。 而为了提升网关的性能,SpringCloud Gateway是基于 WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

Spring Cloud Gateway的目标提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

Spring Cloud Gateway之所以性能好,因为底层使用WebFlux,而WebFlux底层使用netty通信(NIO)

2 使用在哪里

在这里插入图片描述
3 特性
Spring Cloud Gateway 具有如下特性:

基于Spring Framework 5、Project Reactor 和 Spring Boot 2.0 进行构建;
动态路由:能够匹配任何请求属性;
可以对路由指定 Predicate(断言)和 Filter(过滤器);
集成Hystrix的断路器功能;
集成 Spring Cloud 服务发现功能;
易于编写的 Predicate(断言)和 Filter(过滤器);
请求限流功能;
支持路径重写。

4 GateWay和Zuul的区别

在这里插入图片描述
zuul1 模型及特点
在这里插入图片描述

5 什么是WebFlux

是一个非阻塞的web框架,类似springmvc这样的
在这里插入图片描述
6 GateWay的一些概念

Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由;–就是根据某些规则,将请求发送到指定服务上

Predicate(断言):指的是Java 8 的 Function Predicate。输入类型是Spring框架中的ServerWebExchange。这使开发人员可以匹配HTTP请求中的所有内容,例如请求头或请求参数。如果请求与断言相匹配,则进行路由;—就是判断,如果符合条件就是xxxx,反之yyyy

Filter(过滤器):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前后对请求进行修改。–路由前后,过滤请求

7 GateWay的原理
在这里插入图片描述

客户端发送一个请求到Spring Cloud Gateway,如果这个Gateway Handler Mapping中找到与请求匹配的路由,那么将其发送到Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
Filter在"pre"类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等。
在"post"类型的过滤器中可以做响应内容、响应头的修改、日志输出、流量监控等

8 使用GateWay

1)引入架包

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

但是需要删除一下架包,因为gateway不需要会报错

<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>

2)编写application.yml

Spring Cloud GateWay 帮我们内置了很多 Predicates功能,实现了各种路由匹配规则
在这里插入图片描述
在以下配置类中列出了一部分:

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/**         # 断言,路径相匹配的进行路由
            #After 可以指定,只有在指定时间后,才可以路由到指定微服务
            - After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]  
            #            - Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]   #在指定时间之前的才可以访问
            #            - Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] ,  2020-03-08T10:59:34.102+08:00[Asia/Shanghai]  #需要指定两个时间,在他们之间的时间才可以访问
            # Cookie 只有包含某些指定cookie(key,value),的请求才可以路由
            #            curl http://localhost:9527/payment/lb --cookie "username=zzyy"  
            #            - Cookie=username,zzyy   #Cookie=cookieName,正则表达式   
            # Header 只有包含指定请求头的请求,才可以路由。请求头要有X-Request-Id属性并且值为整数的正则表达式 
            #curl http://localhost:9527/payment/lb --cookie "username=zzyy" -H "X-Request-Id:11"   
#            - Header=X-Request-Id, \d+
#Host  只有指定的主机才可以访问
#            - Host=**.atguigu.com  
# curl http://localhost:9527/payment/lb -H "Host:afae.atguigu.com"

3)Filter 过滤器

有两种:
GateWayFilter ,单一的过滤器,与断言类似,有很多过滤参数,以下只列出来一个的写法,其他的参考官网

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 #匹配后提供服务的路由地址
          filters:
            - AddRequestParamer=X-Request-Id,1024  #会在匹配的请求头上加上一对请求头,名为X-Request-Id值为1024
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由

GlobalFilter 全局过滤器,自定义过滤器,实现两个接口,
可实现实现IP访问限制(黑白名单),携带参数等

@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("***********come in MyLogGateWayFilter:  " + new Date());

        // 获取请求参数中的 uname
        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);
    }

    /**
     * 加载过滤器顺序,数字越小优先级越高
     *
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

4)也可以使用硬编码配置GateWay

@Configuration
public class GateWayConfig {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        routes.route("path_route_atguigu", r -> r.path("/guoji").uri("http://news.baidu.com/guonei"))
                .build();
        return routes.build();
    }
}

9 GateWay的限流
常见的限流算法:
计数器算法
以QPS(每秒查询率Queries-per-second)为100举例
算法的实现思路就是从第一个请求进来开始计时,在接下去的1s内,每来一个请求,就把计数加1,如果累加的数字达到了100,那么后续的请求就会被全部拒绝。等到1s结束后,把计数恢复成0,重新开始计数。
弊端:
如果我在单位时间1s内的前10ms,已经通过了100个请求,那后面的990ms,只能眼巴巴的把请求拒绝,我们把这种现象称为“突刺现象”

漏桶算法
漏桶算法可以解决"突刺现象",
漏桶算法这个名字就很形象,算法内部有一个容器,类似生活用到的漏斗,当请求进来时,相当于水倒入漏斗,然后从下端小口慢慢匀速的流出。不管上面流量多大或者桶里有多少水,下面流出的速度始终保持不变。既然是个桶,肯定是有容量上限,如果桶满了(突然到进来大量水),那么新进来的请求就丢弃。
在这里插入图片描述
在算法实现方面,可以准备一个队列,用来保存请求,另外通过一个线程池(ScheduledExecutorService)来定期从队列中获取请求并执行,可以一次性获取多个并发执行。
这种算法,在使用过后也存在弊端:无法应对短时间的突发流量。

令牌桶算法
从某种意义上讲,令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。
在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。

在这里插入图片描述
实现思路:可以准备一个队列,用来保存令牌,另外通过一个线程池定期生成令牌放到队列中,每来一个请求,就从队列中获取一个令牌,并继续执行。

具体实现:
但是限流作为网关最基本的功能,Spring Cloud Gateway官方就提供了RequestRateLimiterGatewayFilterFactory这个类,适用Redis和lua脚本实现了令牌桶的方式。

server:
  port: 8081
spring:
  cloud:
    gateway:
      routes:
      - id: limit_route
        uri: http://httpbin.org:80/get
        predicates:
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]
        filters:
        - name: RequestRateLimiter
          args:
            key-resolver: '#{@hostAddrKeyResolver}'#使用SpringEL表达式,从Spring容器中找对象,并赋值
            redis-rate-limiter.replenishRate: 1#生产令牌的速度,每秒多少个令牌
            redis-rate-limiter.burstCapacity: 3 #令牌桶容量
以下只是通过ip,也可以设置uri,用户
@Component
public class HostAddrKeyResolver implements KeyResolver {

    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }

}
 }

;