六、SpringCloud --网关GateWay

x33g5p2x  于2021-12-30 转载在 Spring  
字(12.2k)|赞(0)|评价(0)|浏览(365)

1.微服务网关GateWay

Zuul 1.x 是一个基于阻塞IO的API Gateway以及Servlet;直到2018年5月,Zuul 2.x(基于 Netty,也是非阻塞的,支持长连接)才发布,但Spring Cloud暂时还没有整合计划。Spring CloudGateway 比Zuul 1.x系列的性能和功能整体要好。

1.1GateWay简介

1.1.1简介

        Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,旨在为微服务架构提供一种简单而有效的统一的 API路由管理方式,统一访问接口。SpringCloud Gateway 作为Spring Cloud生态系中的网关,目标是替代Netflflix ZUUL,其不仅提供统一的路由方式,并且基于 Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。它是基于Nttey的响应式开发模式。

1.1.2核心概念

**路由(route) **:路由是网关最基础的部分,路由信息由一个ID、一个目的URL、一组断言工厂和一组Filter组成。如果断言为真,则说明请求URL和配置的路由匹配。

**断言(predicates):**Java8中的断言函数,Spring Cloud Gateway中的断言函数输入类型是 Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自Http Request中的任何信息,比如请求头和参数等。
过滤器(fifilter:一个标准的Spring webFilter,Spring Cloud Gateway中的Filter分为两种类型,分别是Gateway Filter和Global Filter。过滤器Filter可以对请求和响应进行处理。

1.2入门案例

1.2.1入门案例

**创建工程导入依赖:**在项目中添加新的模块shop_gateway_server,并导入依赖。

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

        注意SpringCloud Gateway使用的web框架为webflflux,和SpringMVC不兼容。引入的限流组件是hystrix。redis底层不再使用jedis,而是lettuce。
配置启动类:

@SpringBootApplication
public class GatewayServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayServerApplication.class, args);
    }
}

**编写配置文件:**创建application.yml配置文件。

server:
  port: 8080 #服务端口 
spring: 
  application: 
    name: api-gateway #指定服务名 
  cloud: 
    gateway: 
      routes: 
        - id: product-service 
          uri: http://127.0.0.1:9002 
          predicates: 
            - Path=/product/**
  • id:我们自定义的路由ID,保持唯一
  • uri:目标服务地址
  • predicates:路由条件,Predicate接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)。
  • fifilters:过滤规则,暂时没用。

        上面这段配置的意思是,配置了一个 id为product-service的路由规则,当访问网关请求地址以product 开头时,会自动转发到地址:http://127.0.0.1:9002/。配置完成启动项目即可在浏览器 访问进行测试,当我们访问地址 http://localhost:8080/product/1时会展示页面展示。

1.2.2路由规则

Spring Cloud Gateway 的功能很强大,前面我们只是使用了predicates进行了简单的条件匹配,其实Spring Cloud Gataway 帮我们内置了很多Predicates功能。在Spring Cloud Gateway中Spring利用Predicate 的特性实现了各种路由匹配规则,有通过Header、请求参数等不同的条件来进行作为条件匹配到对应的路由。

示例 

#路由断言之后匹配 
spring: 
    cloud: 
    gateway: 
    routes: 
    - id: after_route 
      uri: https://xxxx.com #路由断言之前匹配 
      predicates: 
        - After=xxxxx 
#路由断言之前匹配 
spring: 
    cloud: 
    gateway: 
    routes: 
    - id: before_route 
      uri: https://xxxxxx.com 
      predicates: 
        - Before=xxxxxxx 
#路由断言之间 
spring: 
    cloud: 
    gateway: 
    routes: 
    - id: between_route 
      uri: https://xxxx.com 
      predicates: 
      - Between=xxxx,xxxx 
#路由断言Cookie匹配,此predicate匹配给定名称(chocolate)和正则表达式(ch.p) 
spring: 
    cloud: 
        gateway: 
            routes: 
            - id: cookie_route 
              uri: https://xxxx.com 
              predicates: 
              - Cookie=chocolate, ch.p 
#路由断言Header匹配,header名称匹配X-Request-Id,且正则表达式匹配\d+
spring: 
    cloud: 
        gateway: 
            routes: 
            - id: header_route 
              uri: https://xxxx.com 
              predicates: - Header=X-Request-Id, \d+ 
#路由断言匹配Host匹配,匹配下面Host主机列表,**代表可变参数 
spring: 
    cloud: 
        gateway: 
            routes: 
            - id: host_route 
              uri: https://xxxx.com 
              predicates: - Host=**.somehost.org,**.anotherhost.org 
#路由断言Method匹配,匹配的是请求的HTTP方法 
spring: 
    cloud: 
        gateway: 
            routes: 
            - id: method_route 
              uri: https://xxxx.com 
              predicates: - Method=GET 
#路由断言匹配,{segment}为可变参数 
spring: 
    cloud: 
        gateway: 
            routes: 
            - id: host_route 
              uri: https://xxxx.com 
              predicates: - Path=/foo/{segment},/bar/{segment} 
#路由断言Query匹配,将请求的参数param(baz)进行匹配,也可以进行regexp正则表达式匹配 (参数包含 foo,并且foo的值匹配ba.) 
spring: 
    cloud: 
        gateway: 
            routes: 
                - id: query_route 
                  uri: https://xxxx.com 
                  predicates: - Query=baz 或 Query=foo,ba. 
#路由断言RemoteAddr匹配,将匹配192.168.1.1~192.168.1.254之间的ip地址,其中24为子网掩码位 数即255.255.255.0 
spring: 
    cloud: 
      gateway: 
        routes: 
        - id: remoteaddr_route 
          uri: https://example.org 
          predicates: 
            - RemoteAddr=192.168.1.1/24

1.2.3动态路由

和zuul网关类似,在SpringCloud GateWay中也支持动态路由:即自动的从注册中心中获取服务列表并访问。

添加注册中心依赖:在工程的pom文件中添加注册中心的客户端依赖(这里以Eureka为例)

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

**        配置动态路由:**修改 application.yml配置文件,添加eureka注册中心的相关配置,并修改访问映射的URL为服务名称。

server: 
    port: 8080 #服务端口 
spring: 
    application: 
        name: api-gateway #指定服务名 
        cloud: 
            gateway: 
                routes: 
                - id: product-service 
                  uri: lb://shop-service-product 
                  predicates: 
                    - Path=/product/** 
eureka: 
    client: 
        serviceUrl: 
            defaultZone: http://127.0.0.1:8761/eureka/ 
            registry-fetch-interval-seconds: 5 # 获取服务列表的周期:5s 
instance: 
    preferIpAddress: true 
    ip-address: 127.0.0.1

uri:uri以lb://开头(lb代表从注册中心获取服务),后面接的就是你需要转发到的服务名称

1.2.4重写转发路径

在SpringCloud Gateway中,路由转发是直接将匹配的路由path直接拼接到映射路径(URI)之后,那么在微服务开发中往往没有那么便利。这里就可以通过RewritePath机制来进行路径重写。
案例改造: 修改application.yml,将匹配路径改为/product-service/**

        重新启动网关,我们在浏览器访问http://127.0.0.1:8080/product-service/product/1,会抛出404这是由于路由转发规则默认转发到商品微服务( http://127.0.0.1:9002/product-service/product/1 )路径上,而商品微服务又没有product-service对应的映射配置。

**添加****RewritePath重写转发路径:**修改application.yml,添加重写规则。

spring: 
    application: 
        name: api-gateway #指定服务名 
    cloud: 
        gateway: 
            routes:
             - id: product-service 
               uri: lb://shop-service-product 
               predicates: 
               - Path=/product-service/** 
               filters: 
               - RewritePath=/product-service/(?<segment>.*), /$\{segment}

通过RewritePath配置重写转发的url,将/product-service/(?.*),重写为{segment},然后转发到订单微服务。比如在网页上请求http://localhost:8080/product-service/product,此时会将请求转发到http://127.0.0.1:9002/product/1(值得注意的是在yml文档中$要写成$\)

1.3过滤器

Spring Cloud Gateway除了具备请求路由功能之外,也支持对请求的过滤。通过Zuul网关类似,也是通过过滤器的形式来实现的。那么接下来我们一起来研究一下Gateway中的过滤器。

1.3.1过滤器基础

**        过滤器的生命周期**: Spring Cloud Gateway的Filter的生命周期不像Zuul的那么丰富,它只有两个:“pre”和“post”。

  • PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

**过滤器类型:**Spring Cloud Gateway的Filter从作用范围可分为另外两种GatewayFilter与GlobalFilter。  

  • GatewayFilter:应用到单个路由或者一个分组的路由上。
  • GlobalFilter:应用到所有的路由上。

1.3.2局部过滤器

局部过滤器(GatewayFilter),是针对单个路由的过滤器。可以对访问的URL过滤,进行切面处理。在Spring Cloud Gateway中通过GatewayFilter的形式内置了很多不同类型的局部过滤器。这里简单将Spring Cloud Gateway内置的所有过滤器工厂整理成了一张表格,虽然不是很详细,但能作为速览使用。如下:

| <br>过滤器工厂<br> | <br>作用<br> | <br>参数<br> |
| <br>AddRequestHeader<br> | <br>为原始请求添加Header<br> | <br>Header的名称及值<br> |
| <br>AddRequestParameter<br> | <br>为原始请求添加请求参数<br> | <br>参数名称及值<br> |
| <br>AddResponseHeader<br> | <br>为原始响应添加Header<br> | <br>Header的名称及值<br> |
| <br>DedupeResponseHeader<br> | <br>易IJ除响应头中重复的值<br> | <br>需要去重的Header名 称及去重策略<br> |
| <br>Hystrix<br> | <br>为路由弓lAHystrix的断路器保护<br> | <br>Hystri xCommand的名 称<br> |
| <br>FallbackHeaders<br> | <br>为fallbackUri的请求头中添加具 体的异常信息<br> | <br>Header的名称<br> |
| <br>PrefixPath<br> | <br>为原始请求路径添加前缀<br> | <br>前嚇径<br> |
| <br>Preserve HostHeader<br> | <br>为请求添加一个preserveHostHeader=true白勺属 性,路由过滤器会检查该属性以 决定是否要秘齢的Host<br> | <br>无<br> |
| <br>RequestRateLimiter<br> | <br>用于对请求限流,限流算法为令 牌桶<br> | <br>keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus<br> |
| <br>RedirectTo<br> | <br>将原始请求重定向到指定的URL<br> | <br>http状态码及重定向的<br><br>url<br> |
| <br>RemoveHopByHopHeadersFilter<br> | <br>为原始请求删除1ETF组织规定的 —系列Header<br> | <br>默认就会启用,可以通 过配置指定仅删除哪些Header<br> |
| <br>Remove RequestHeader<br> | <br>为原始请求删除某个Header<br> | <br>Header名称<br> |
| <br>Remove ResponseHeader<br> | <br>为原始响应删除某个Header<br> | <br>Header名称<br> |
| <br>RewritePath<br> | <br>重写齢的请求略<br> | <br>原始路径正则表达式以 及重写后路径的正则表 达式<br> |
| <br>RewriteResponseHeader<br> | <br>重写原始响应中的某个Header<br> | <br>Header名称,值的正 则表达式,重写后的值<br> |
| <br>SaveSession<br> | <br>晚发请求之前,强制执行WebSession: :save操作<br> | <br>无<br> |
| <br>secureHeaders<br> | <br>为原始响应添加一系列起安全作 用的响应头<br> | <br>无,支持修改这些安全 响应头的值<br> |
| <br>SetPath<br> | <br>修改原始的请求路径<br> | <br>修改后的路径<br> |
| <br>SetResponseHeader<br> | <br>修改原始响应中某个Header的值<br> | <br>Header名称,修改后 的值<br> |

| <br>SetStatus<br> | <br>修改原始响应的状态码<br> | <br>HTTP状态码,可以是 数字,也可以是字符串<br> |
| <br>StripPrefix<br> | <br>用于截断原始请求的路径<br> | <br>使用数字表示要截断的 路径的数量<br> |
| <br>Retry<br> | <br>针对不同的响应进行重试<br> | <br>retries、statuses、methods、series<br> |
| <br>RequestSize<br> | <br>设置允许接收最大请求包的大 小。如果请求包大小超过设置的 值,则返回413Payl oad Too Large<br> | <br>请求包大小,单位为字 节,默认值为5M<br> |
| <br>ModifyRequestBody<br> | <br>晚发请求之前修改原始请求体 内谷<br> | <br>修改后的请求体内容<br> |
| <br>ModifyResponseBody<br> | <br>修改原始响应体的内容<br> | <br>修改后的响应体内容<br> |

        每个过滤器工厂都对应一个实现类,并且这些类的名称必须以 GatewayFilterFactory结尾,这是Spring Cloud Gateway的一个约定,例如AddRequestHeader对应的实现类为 AddRequestHeaderGatewayFilterFactory 。对于这些过滤器的使用方式可以参考官方文档。

1.3.3全局过滤器

全局过滤器(GlobalFilter)作用于所有路由,Spring Cloud Gateway定义了Global Filter接口,用户可以自定义实现自己的Global Filter。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能,并且全局过滤器也是程序员使用比较多的过滤器。 Spring Cloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理如下:

1.4统一鉴权

内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的权限校验。

1.4.1鉴权逻辑

开发中的鉴权逻辑:

  • 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
  • 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
  • 以后每次请求,客户端都携带认证的token
  • 服务端对token进行解密,判断是否有效。

        如上图,对于验证用户是否已经登录鉴权的过程可以在网关层统一检验。检验的标准就是请求中是否携带token凭证以及token的正确性。

1.4.2代码实现

下面的我们自定义一个GlobalFilter,去校验所有请求的请求参数中是否包含“token”,如何不包含请求参数“token”则不转发路由,否则执行正常的逻辑。

@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //String url = exchange.getRequest().getURI().getPath(); 
        // 忽略以下url请求 
        // if(url.indexOf("/login") >= 0){ 
        // return chain.filter(exchange); 
        // } 
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (StringUtils.isBlank(token)) {
            log.info("token is empty ...");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
  • 自定义全局过滤器需要实现GlobalFilter和Ordered接口;
  • 在fifilter方法中完成过滤器的逻辑判断处理;
  • 在getOrder方法指定此过滤器的优先级,返回值越大级别越低;
  • ServerWebExchange就相当于当前请求和响应的上下文,存放着重要的请求-响应属性、请求实 例和响应实例等等。一个请求中的request,response都可以通过ServerWebExchange获取;
  • 调用chain.filter继续向下游执行。

1.5网关限流

1.5.1常见的限流算法

计数器:计数器限流算法是最简单的一种限流实现方式。其本质是通过维护一个单位时间内的计数器,每次请求计数器加1,当单位时间内计数器累加到大于设定的阈值,则之后的请求都被拒绝,直到单位时间已经过去,再将计数器重置为零。

漏桶算法:漏桶算法可以很好地限制容量池的大小,从而防止流量暴增。漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶(包缓存)溢出,那么数据包会被丢弃。 在网络中,漏桶算法可以控制端口的流量输出速率,平滑网络上的突发流量,实现流量整形,从而为网络提供一个稳定的流量。

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

1.5.2基于Filter的限流

1.5.3基于Sentinel的限流(省)

3.6网关高可用

高可用****HA(High Availability)是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。我们都知道,单点是系统高可用的大敌,单点往往是系统高可用最大的风险和敌人,应该尽量在系统设计的过程中避免单点。方法论上,高可用保证的原则是“集群化”,或者叫“冗余”:只有一个单点,挂了服务会受影响;如果有冗余备份,挂了还有其他backup能够顶上。

我们实际使用 Spring Cloud Gateway 的方式如上图,不同的客户端使用不同的负载将请求分发到后端的 Gateway,Gateway再通过HTTP调用后端服务,最后对外输出。因此为了保证Gateway的高可用性,前端可以同时启动多个 Gateway实例进行负载,在Gateway的前端使用Nginx或者F5进行负载转发以达到高可用性。
准备多个GateWay工程: 修改shop_gateway_server的application.yml。添加如下配置

spring: 
    application: 
        name: api-gateway #指定服务名 
    cloud: 
        gateway: 
            routes: 
            - id: product-service 
              uri: lb://shop-service-product 
              predicates:
              - Path=/product-service/** 
              filters: 
              - RewritePath=/product-service/(?<segment>.*), /$\{segment} 
eureka: 
    client: 
        serviceUrl: 
            defaultZone: http://eureka1:8761/eureka/ 
            registry-fetch-interval-seconds: 5 # 获取服务列表的周期:5s 
    instance: 
        preferIpAddress: true 
        ip-address: 127.0.0.1 
--- 
spring:
    profiles: gateway01 
server: 
    port: 8080 #服务端口 
--- 
spring: 
    profiles: gateway02 
server: 
    port: 8081 #服务端口

通过不同的profifiles配置启动两个网关服务,请求端口分别为8080和8081。浏览器验证发现效果是一致的。

**配置ngnix:**找到ngnix添加负载均衡配置。

#配置多台服务器(这里只在一台服务器上的不同端口) 
upstream gateway {
 server 127.0.0.1:8081;
 server 127.0.0.1:8080; 
}
#请求转向mysvr 定义的服务器列表 
location / {
 proxy_pass http://gateway; 
}

        在浏览器上通过访问http://localhost/order-service/order/buy/1请求的效果和之前是一样的。这次关闭一台网关服务器,还是可以支持部分请求的访问。

3.7执行流程分析

Spring Cloud Gateway 核心处理流程如上图所示,Gateway的客户端向Spring Cloud Gateway发送请求,请求首先被 HttpWebHandlerAdapter 进行提取组装成网关上下文,然后网关的上下文会传递到 DispatcherHandler。DispatcherHandler是所有请求的分发处理器,DispatcherHandler主要负责分发请求对应的处理器。比如请求分发到对应的RoutePredicateHandlerMapping(路由断言处理映射器)。路由断言处理映射器主要作用用于路由查找,以及找到路由后返回对应的FilterWebHandler 。FilterWebHandler主要负责组装Filter链并调用Filter执行一系列的Filter处理,然后再把请求转到后端对应的代理服务处理,处理完毕之后将Response返回到Gateway客户端。

相关文章