Gateway网关

发布时间 2023-07-04 11:01:27作者: lwx_R

1.Spring Cloud Gateway

Spring Cloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Netflix Zuul,其不仅提供统-的路由方式, 并且还基
于Filter链的方式提供了网关基本的功能。目前最新版Spring Cloud中引用的还是Zuul 1.x版本,而这个版本是基于过滤器的,是
阻塞10,不支持长连接。
Zuul2.x版本一直跳票,2019年5月,Netflix 终于开源了支持异步调用模式的Zuul2.0版本,真可谓千呼万唤始出来。但是
Spring Cloud已经不再集成Zuul2.x了,那么是时候了解一下 Spring Cloud Gateway了。
Spring Cloud Gateway是基于Spring生态系统之上构建的API网关,包括: Spring5, Spring Boot 2和Project Reactor. Spring
Cloud Gateway旨在提供一种简单而有效的方法来路由到API,并为它们提供跨领域的关注点,例如:安全性,监视/指标,限流
等。由于Spring 5.0支持Netty, Http2, 而Spring Boot 2.0支持Spring 5.0,因此Spring Cloud Gateway支持Netty和Http2顺理成
章.

2.服务网关

API Gateway (APIGW/API网关) ,顾名思义,是出现在系统边界上的一个面向API的、串行集中式的强管控服务,这里的边
界是企业IT系统的边界,可以理解为企业级应用防火墙,主要起到隔离外部访问与内部系统的作用。在微服务概念的流行:之
前,API网关就已经诞生了,例如银行、证券等领域常见的前置机系统,它也是解决访问认证、报文转换、访问统计等问题的。
API网关的流行,源于近几年来移动应用与企业间互联需求的兴起。移动应用、企业互联,使得后台服务支持的对象,从以前
单一的Web应用,扩展到多种使用场景,且每种使用场景对后台服务的要求都不尽相同。这不仅增加了后台服务的响应量,还增加
了后台服务的复杂性。随 着微服务架构概念的提出,API网关成为了微服务架构的一个标配组件.
API网关是一个服务器,是系统对外的唯一入口。 API 网关封装了系统内部架构,为每个客户端提供定制的API。所有的客户端
和消费端都通过统一的网关接入微服务,在网关层处理所有非业务功能。API 网关并不是微服务场景中必须的组件,如下图,不管
有没有API网关,后端微服务都可以通过API很好地支持客户端的访问。
但对于服务数量众多、复杂度比较高、规模比较大的业务来说,引入API网关也有一系列的好处:

  • 聚合接口使得服务对调用者透明,客户端与后端的耦合度降低
  • 聚合后台服务,节省流量,提高性能,提升用户体验
  • 提供安全、流控、过滤、缓存、计费、监控等API管理功能

3.为什么要使用网关

|- 单体应用:浏览器发起请求到单体应用所在的机器,应用从数据库查询数据原路返回给浏览器,对于单体应用来说是不需要网
关的。

  • 微服务的应用可能部署在不同机房,不同地区,不同域名下。此时客户端(浏览器/手机/软件工具)想要请求对应的
    服务,都需要知道机器的具体IP或者域名URL,当微服务实例众多时,这是非常难以记忆的,对于客户端来说也太复杂难以
    维护。此时就有了网关,客户端相关的请求直接发送到网关,由网关根据请求标识解析判断出具体的微服务地址,再把请求转
    发到微服务实例。这其中的记忆功能就全部交由网关来操作了。
    因此,我们需要网关介于客户端与服务器之间的中间层,所有外部请求率先经过微服务网关,客户端只需要与网关交互,只需要知
    道网关地址即可。这样便简化了开发且有以下优点:
  • 易于监控,可在微服务网关收集监控数据并将其推送到外部系统进行分析
  • 易于认证,可在微服务网关上进行认证,然后再将请求转发到后端的微服务,从而无需在每个微服务中进行认证
  • 减少了客户端与各个微服务之间的交互次数

4.解决问题


网关具有身份认证与安全、审查与监控、动态路由、负载均衡、缓存、请求分片与管理、静态响应处理等功能。当然最主要的
职责还是与“外界联系”。
总结一下,网关应当具备以下功能:

  • 性能: API高可用,负载均衡,容错机制。
  • 安全:权限身份认证、脱敏,流量清洗,后端签名(保证全链路可信调用),黑名单(非法调用的限制)。
  • 日志:日志记录,一旦涉及分布式,全链路跟踪必不可少。
  • 缓存:数据缓存。
  • 监控:记录请求响应数据,API耗时分析,性能监控。
  • 限流:流量控制,错峰流控,可以定义多种限流规则。
  • 灰度:线上灰度部署,可以减小风险。
  • 路由:动态路由规则。

5.常用网关解决万案

5.1 Nginx + Lua

Nginx是由lgorSysoev为俄罗斯访问量第二的Rambler.ru 站点开发的,一个高性能的HTTP和反向代理服务器。Ngnix 一方面
可以做反向代理,另外-方面做可以做静态资源服务器。

  • Nginx适合做门户网关,是作为整个全局的网关,对外的处于最外层的那种;而Gateway属于业务网关,主要用来对应
    不同的客户端提供服务,用于聚合业务。各个微服务独立部署,职责单一, 对外提供服务的时候需要有一个东西把业务
    聚合起来。
  • Gateway可以实现熔断、重试等功能,这是Nginx不具备的。

5.2 Kong

Kong是Mashape提供的一款API管理软件,它本身是基于Ngnix+ Lua的,但比Nginx提供了更简单的配置方式,数据采用了
ApacheCassandra/PostgreSQL存储,并且提供了-些优秀的插件,比如验证,日志,调用频次限制等。Kong 非常诱人的地方就是
提供了大量的插件来扩展应用,通过设置不同的插件可以为服务提供各种增强的功能。
优点:基于Nginx所以在性能和稳定性上都没有问题。Kong 作为一款商业软件,在Nginx.上做了很扩展工作,而且还有很多
付费的商业插件。Kong 本身也有付费的企业版,其中包括技术支持、使用培训服务以及API分析插件。
缺点:如果你使用Spring Cloud, Kong 如何结合目前已有的服务治理体系?

5.3 Traefik

Traefik是一个开源的 GO语言开发的为了让部署微服务更加便捷而诞生的现代HTTP反向代理、负载均衡工具。它支持多种后
台(Docker, Swarm, Kubernetes, Marathon, Mesos, Consul, Etcd, Zookeeper, BoltDB, Rest API, fl..来自动化、动态的应用它
的配置文件设置。Traefik 拥有一个基于 AngularJS编写的简单网站界面,支持RestAPI,配置文件热更新,无需重启进程。可用集群模式等。
相对Spring Cloud和Kubernetes而言,目前比较适合Kubernetes。

5.4 Spring Cloud Netflix Zuul

Zuul是Netlix公司开源的一个APL网关组件,Spring Cloud对其进行次基于Spring Boot的注解式封装做到开箱即用。目前
来说,结合Sring Cloud提供的服务治理体系,可以做到请求转发,根据配置或者默认的路由规则进行路由和Load Balance,无缝
集成Hystrix.
虽然可以通过自定义Filter 实现我们想要的功能,但是由于Zuul本身的设计是基于单线程的接收请求和转发处理,是阻塞IO,不支持长连接。
目前来看Zuul就显得很鸡肋,随着Zuul2.x一直跳票(2019年5月发布了Zuul2.0 版本),Spring Cloud
推出自己的Spring Cloud Gateway.
大意就是: Zuul 已死,Spring Cloud Gateway永生(手动狗头)。

6.nginx实现

# 路由到商品服务
 location /api-product {
     proxy_pass   http://localhost:7070/;
 }

# 路由到订单服务
 location /api-order {
     proxy_pass   http://localhost:9090/;
 }

7.Gateway实现API网关

7.1 核心概念

  • 路由(Route) :路由是网关最基础的部分,路由信息由ID、目标URI、 - -组断言和一组过滤器组成。如果断言路由为真,则
    说明请求的URI和配置匹配。
  • 断言(Predicate) : Java8 中的断言函数。Spring Cloud Gateway中的断言函数输入类型是Spring 5.0框架中的
    ServerWebExchange. Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自于Http Request中的任何信息,比如请求头和
    参数等。
  • 过滤器(Filter) : -个标准的Spring Web Filter. Spring Cloud Gateway中的Filter 分为两种类型,分别是Gateway Filter和
    Global Filter.过滤器将会对请求和响应进行处理。

7.2 原理

客户端向LSpring Cloud Gateway 发出请求。再由网关处理程序Gateway Handler Mapping 映射确定与
请求相匹配的路由,将其发送到网关Web处理程序Gateway Web Handler 。该处理程序通过指定的过滤器链将请求发送到我们
实际的服务执行业务逻辑,然后返回。过滤器由虚线分隔的原因是,过滤器可以在发送代理请求之前和之后运行逻辑。所有pre
过滤器逻辑均被执行。然后发出代理请求。发出代理请求后,将运行post过滤器逻辑。

7.3 实现

  • pom.xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
  • 配置文件
server:
  port: 9000

spring:
  application:
    name: gatewayServer
#路由规则
  cloud:
    gateway:
      routes:
        - id: product-server #唯一id
          uri: http://localhost:7070/ #目标URI
          predicates: #断言(判断条件)
            - Path=/product/**    

7.4 根据服务名称从注册中心获取服务请求地址

uri: lb://provider

eureka:
  instance:
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/, http://localhost:8792/eureka/ 

7.5 服务发现

    gateway:
      discovery:
        # 是否与服务发现组件进行结合,通过serviceId转发到具体服务
        locator:
          enabled: true
          lower-case-service-id: true #是否将服务名称转小写

8.路由规则

Spring Cloud Gateway创建Route对象时,使用RoutePredicateFactory 创建Predicate对象,Predicate 对象可以赋值给Route.

  • Spring Cloud Gateway包含许多内置的Route Predicate Factories.
  • 所有这些断言都匹配HTTP请求的不同属性。
  • 多个Route Predicate Factories可以通过逻辑与(and) 结合起来-起使用。
    路由断言厂RoutePredicateFactory 包含的主要实现类如图所示,包括Datetime、请求的远端地址、 路由权重、 请求头
    Host地址、请求方法、 请求路径和请求参数等类型的路由断言。
         predicates: #断言(判断条件)
            - Path=/product/**
            - Query=token, abc. # 匹配请求参数中包含token并且其参数满足正则表达式abc.的请求
                                product/1?token=abc1
            - Method=GET #只匹配Get方法
            - After=2023-02-02T20:20:20.000+08:00[Asia/Shanghai] #匹配上海2023年2月2日 20点20分20秒之后的请求
            - RemoteAddr=192.168.1.2/0 #匹配地址为这个的请求 子网掩码为0
            - Header=X-Request-Id, \d+ #匹配请求头包含这个且其值为正则\d+的请求

9.网关过滤器 GatewayFilter

网关过滤器用于拦截并链式处理Web请求,可以实现横切与应用无关的需求,比如:安全、访问超时的设置等。修改传入的
HTTP请求或传出HTTP响应。Spring Cloud Gateway包含许多内置的网关过滤器工厂一共有22个,包括头部过滤器、路径类过滤
器、Hystrix 过滤器和重写请求URL的过滤器,还有参数和状态码等其他类型的过滤器。根据过滤器工厂的用途来划分,可以分为
以下几种: Header、 Parameter、 Path、 Body、 Status、 Session、 Redirect、 Retry、 RateLimiter 和Hystrix.

predicates: 
#            - Path=/product/**, /api-gateway/** 1
#            - Path=/** 2 3
#            - Path=/api/product/{segment} 4
#            - Path=/api-gateway/** 56
filters:
         # /api-gateway/product/1 重写为/product/1
     - RewritePath=/api-gateway(?<segment>/?.*), $\{segment}
     - PrefixPath=/product # 将/1重写为/product
     - StripPrefix=2 #分割前俩个路径 将/api/123/product/1 重写为/product/1
     - SetPath=/product/{segment} #/api/product/1 重写为 /product/1
     - AddRequestParameter=flag, 1 #参数中添加flag=1
     - SetStatus=404 #任何情况下响应状态为404

9.1 自定义网关过滤器

//自定义网关过滤器
public class MyGatewayFilter implements GatewayFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange); //继续向下执行
    }

    //过滤器执行顺序 数字越小 优先级越高
    @Override
    public int getOrder() {
        return 0;
    }   
}
@Configuration
public class GatewayRoutersConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder){
        return builder.routes().route(r -> r
                //断言
                .path("/product/**")
                .uri("lb://provider")
                .filters(new MyGatewayFilter())
                .id("gatewayServer"))
                .build();
    }
}

10.全局过滤器

全局过滤器不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识
别的过滤器,它是请求业务以及路由的URI转换为真实业务服务请求地址的核心过滤器,不需要配置系统初始化时加载,并作用在
每个路由上。

10.1 自定义全局过滤器

//自定义全局过滤器
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

11.统一鉴权

@Component
public class AccessFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
       //获取请求参数
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if(token == null){
            ServerHttpResponse response = exchange.getResponse();
            //响应类型
            response.getHeaders().add("Content-Type", "application/json; charset=utf-8");
            //响应状态码
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            //响应内容
            String message = "{\"message\":\"" + HttpStatus.UNAUTHORIZED.getReasonPhrase() + "\"}";
            DataBuffer buffer = response.bufferFactory().wrap(message.getBytes());
            //请求结束
            return response.writeWith(Mono.just(buffer));
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

12.网关限流

顾名思义,限流就是限制流量,就像你宽带包有1个G的流量,用完了就没了。通过限流,我们可以很好地控制系统的QPS,
从而达到保护系统的目的。

12.1 为什么需要限流

比如Web服务、对外API,这种类型的服务有以下几种可能导致机器被拖垮:

  • 用户增长过快(好事)
  • 因为某个热点事件(微博热搜)
  • 竞争对象爬虫
  • 恶意的请求
    这些情况都是无法预知的,不知道什么时候会有10倍甚至20倍的流量打进来,如果真碰上这种情况,扩容是根本来不及的。

12.2 限流算法

常见的限流算法有:

  • 计数器算法
    计数器算法是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于A接口来说,我们1分钟的访问次数不能超
    过100个。那么我们可以这么做:在一开始的时候, 我们可以设置一个计数器 counter,每当一个请求过来的时候, counter 就加
    1,如果counter的值大于100并且该请求与第一个请求的间隔时间还在1分钟之内,触发限流;如果该请求与第一个请求的间隔
    时间大于1分钟,重置counter重新计数,具体算法的示意图如下:

    这个算法虽然简单,但是有一个十分致命的问题, 那就是临界问题,我们看下图:

    从上图中我们可以看到,假设有一个恶意用户, 他在0:59时,瞬间发送了100个请求,并且1:00又瞬间发送了100个请求,
    那么其实这个用户在1秒里面,瞬间发送了200个请求。我们刚才规定的是1分钟最多100个请求,也就是每秒钟最多1.7 个请
    求,用户通过在时间窗口的重置节点处突发请求,可以瞬间超过我们的速率限制。用户有可能通过算法的这个漏洞,瞬间压垮我
    们的应用。

  • 漏桶(Leaky Bucket)算法
    漏桶算法其实也很简单,可以粗略的认为就是注水漏水的过程,往桶中以任意速率流入水,以-定速率流出水,当水超过桶流
    量则丢弃,因为桶容量是不变的,保证了整体的速率。
    基于队列实现

  • 令牌桶(Token Bucket) 算法
    令牌桶算法是对漏桶算法的一种改进,漏桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还
    允许一定程度的突发调用。在令牌桶算法中,存在一个桶, 用来存放固定数量的令牌。算法中存在一种机制,以-定的速率往桶中
    放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放
    令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌。
    场景大概是这样的:桶中一直有大量的可用令牌,这时进来的请求可以直接拿到令牌执行,比如设置QPS为100/s,那么限
    流器初始化完成一秒后,桶中就已经有100个令牌了,等服务启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请
    求。当桶中没有令牌时,请求会进行等待,最后相当于以一定的速率执行。
    Spring Cloud Gateway内部使用的就是该算法,大概描述如下:

  • 所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;

  • 根据限流大小,设置按照一定的速率往桶里添加令牌;

  • 桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;

  • 请求到达后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;

  • 令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流。
    漏桶算法主要用途在于保护它人,而令牌桶算法主要目的在于保护自己,将请求压力交由目标服务处理。假设突然进来很多请
    求,只要拿到令牌这些请求会瞬时被处理调用目标服务.

12.3 Gateway限流实现

  • pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
  • 配置文件
spring:
  redis:
    timeout: 10000
    host: 127.0.0.1
    port: 6379
    password:
    database: 0
    lettuce:
      pool:
        max-active: 1024
        max-wait: 10000
        max-idle: 200
        min-idle: 5
  • 配置类
@Configuration
public class KeyResolverConfiguration {

    //URI限流规则 该方法只能一个
//    @Bean
    public KeyResolver pathKeyResolver(){
        return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
    }

    //参数限流 url带userId=1
    @Bean
    public KeyResolver parameterKeyResolver(){
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
    }

    //IP限流
    @Bean
    public KeyResolver ipKeyResolver(){
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
    }
}
  • 配置文件
filters:
            #限流过滤器
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充速率
                redis-rate-limiter.burstCapacity: 2 # 令牌桶总容量
#                key-resolver: "#{@pathKeyResolver}"
                key-resolver: "#{@parameterKeyResolver}"#使用SpEl表达式按名称引用bean

12.4 Sentinel过滤器