Spring Cloud之(十八)微服务网关GateWay

十八、微服务网关GateWay

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

18.1 GateWay简介

  • GateWay的简介

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

  • GaetWay与Zuul的对比

    组件RPS(request per second)
    Spring Cloud GateWayRequest/sec:3223.38
    Zuul1XRequest/sec:20800.13
  • GateWay的核心概念
    在这里插入图片描述

  1. 路由(route):路由是网关最基础的部分,路由信息由一个ID、一个目的URL、一组断言工厂和一组Filter组成。如果断言为真,则说明请求URL和配置的路由匹配。
  2. 断言(predicates):Java8中的断言函数,Spring Cloud Gateway中的断言函数输入类型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自Http Request中的任何信息,比如请求头和参数等。
  3. 过滤器(filter):一个标准的Spring webFilter,Spring Cloud Gateway中的Filter分为两种类型,分别是Gateway Filter和Global Filter。过滤器Filter可以对请求和响应进行处理。

18.2 搭建GateWay网关服务器

创建ebuy-gateway功能模块:

  • 导入依赖

    gateway模块中一定不要导入Spring MVC中的web依赖,否则会报错而无法启动!!!

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>ebuy-parent</artifactId>
            <groupId>cn.ebuy</groupId>
            <version>1.0-SNAPSHOT</version>
            <relativePath>../ebuy-parent/pom.xml</relativePath>
        </parent>
        <modelVersion>4.0.0</modelVersion>
        <groupId>cn.ebuy</groupId>
        <artifactId>ebuy-gateway</artifactId>
        <dependencies>
            <!-- gwteway网关的依赖 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
            <!-- eureka注册中心 客户端 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
        </dependencies>
    </project>
    

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

  • 配置启动类

    package cn.ebuy.gateway;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class GatewayServerApplication {
        public static void main(String[] args) {
            SpringApplication.run(GatewayServerApplication.class, args);
        }
    }
    
  • 修改配置文件

    server:
      port: 9091 #端口
    spring:   #http://127.0.0.1:9011/product/12123
      application:
        name: ebuy-gateway #服务名称
      cloud:
    # 根据路径请求
    #    gateway:
    #      routes:
    #      - id: ebuy-product  #自定义的路由id,保持唯一
    #        uri: http://127.0.0.1:9011  #目标服务地址
    #        predicates:  #路由条件,Predicates接受一个输入参数,返回一个布尔值结果,该接口包含多种默认方法来讲Predicate组合成其他复杂的逻辑
    #          - Path=/product/**
    #    gateway:
    #      routes:
    #        - id: ebuy-product
    #          uri: lb://ebuy-product
    #          predicates:
    #            - Path=/ebuy-product/**
        #根据服务名请求
        gateway:
          routes:
            - id: ebuy-product
              uri: lb://ebuy-product  #uri以lb://开头(lb代表从注册中心获取服务),后面接的就是你需要转发到的服务名称
              predicates:
                - Path=/ebuy-product/** #路由转发,直接将匹配的路由path直接拼接到映射路径url之后
              filters: #过滤规则
                #重写转发的url,将/ebuy-product/(?.*)重新写为{segment},然后转发到订单微服务
                - RewritePath=/ebuy-product/(?<segment>.*), /$\{segment}
            - id: ebuy-order
              uri: lb://ebuy-order
              predicates:
                - Path=/ebuy-order/**
              filters:
                - RewritePath=/ebuy-order/(?<segment>.*), /$\{segment}
    logging:
      level:
        cn.ebuy: DEBUG
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:8000/eureka/,http://127.0.0.1:9000/eureka/
      instance:
        prefer-ip-address: true   # 使用ip地址注册
    

    说明:

    1. id:自定义的路由id,保持唯一;
    2. uri:目标服务地址;
    3. predicates:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非);
    4. filters:过滤规则
      与zuul类似,我们可以配置固定路由,也可以配置动态路由,很明显,根据服务名请求的方式是开发中是最合适的一种请求方式。

    配置好gateway网关启动服务访问:
    在这里插入图片描述

18.3 路由规则

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

18.4 过滤器

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

  • 过滤器的生命周期

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

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

    Spring Cloud Gateway 的 Filter 从作用范围可分为另外两种: GatewayFilter 与 GlobalFilter。

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

    局部过滤器(GatewayFilter)是针对单个路由的过滤器。可以对访问的URL过滤,进行切面处理。在Spring Cloud Gateway中通过 GatewayFilter 的形式内置了很多不同类型的局部过滤器。这里简单将 Spring Cloud Gateway 内置的所有过滤器工厂整理成了一张表格,虽然不是很详细,但能作为速览使用。
    在这里插入图片描述
    在这里插入图片描述
    每个过滤器工厂都对应一个实现类,并且这些类的名称必须以GatewayFilterFactory结尾,这是Spring Cloud Gateway的一个约定,例如AddRequestHeader对应的实现类为AddRequestHeaderGatewayFilterFactory。对于这些过滤器的使用方式可以参考官方文档。

  • 全局过滤器

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

18.5 统一鉴权

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

开发中的鉴权逻辑:

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

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

package cn.ebuy.gateway.filter;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取请求对象
        ServerHttpRequest request = exchange.getRequest();
        //2.获取响应对象
        ServerHttpResponse response = exchange.getResponse();
        //3.判断当前的请求是否为登录请求,如果是,则直接放行
        if (request.getURI().getPath().contains("/user/login")||request.getURI().getPath().contains("/user/toLogin")||request.getURI().getPath().contains("/user/index")){
            //放行
            return chain.filter(exchange);
        }
        /*//4.获取当前的所有请求头信息
        HttpHeaders headers = request.getHeaders();
        //5.获取jwt令牌信息
        String jwtToken = headers.getFirst("token");
        //6.判断当前令牌是否存在,
        if (StringUtils.isEmpty(jwtToken)){
            //如果不存在,则向客户端返回错误提示信息
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        */
        // 判断当前令牌是否存在
        String token=request.getQueryParams().getFirst("token");
        if(StringUtils.isEmpty(token)){
            //如果不存在,则向客户端返回错误提示信息
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            System.out.println("过滤器正在工作...");
            return response.setComplete();
        }else {
            // 如果令牌合法,则放行
            return chain.filter(exchange);
        }
    }
    @Override
    public int getOrder() {
        return 0;
    }
}

在这里插入图片描述

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

18.6 网关限流

  • 常见的限流算法

  • 计数器

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

  • 漏桶算法

    漏桶算法可以很好地限制容量池的大小,从而防止流量暴增。漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶(包缓存)溢出,那么数据包会被丢弃。 在网络中,漏桶算法可以控制端口的流量输出速率,平滑网络上的突发流量,实现流量整形,从而为网络提供一个稳定的流量。
    在这里插入图片描述
    为了更好的控制流量,漏桶算法需要通过两个变量进行控制:一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate)。

  • 令牌桶算法

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

  • 基于Filter的限流

    SpringCloudGateway官方就提供了基于令牌桶的限流支持。基于其内置的过滤器工厂RequestRateLimiterGatewayFilterFactory实现。在过滤器工厂中是通过Redis和lua脚本结合的方式进行流量控制。

  1. 导入redis依赖

    <!-- gwteway网关的依赖 -->
    <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!-- 引入redis的reactive依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        <version>2.1.3.RELEASE</version>
    </dependency>
    
  2. 修改application.yml配置文件

    server:
      port: 9091 #端口
    spring:   #http://127.0.0.1:9011/product/12123
      application:
        name: ebuy-gateway #服务名称
      cloud:
        #根据服务名请求
        gateway:
          globalcors:
            cors-configurations:
              '[/**]': # 匹配所有请求
                allowedOrigins: "*" #跨域处理 允许所有的域
                allowedMethods: # 支持的方法
                  - GET
                  - POST
                  - PUT
                  - DELETE
          routes:
            - id: ebuy-product
              uri: lb://ebuy-product  #uri以lb://开头(lb代表从注册中心获取服务),后面接的就是你需要转发到的服务名称
              predicates:
                - Path=/ebuy-product/** #路由转发,直接将匹配的路由path直接拼接到映射路径url之后
              filters: #过滤规则
                - name: RequestRateLimiter #请求数限流 名字不能随便写
                  args:
                    #key-resolver: "#{@ipKeyResolver}"
                    key-resolver: "#{@pathKeyResolver}"
                    #key-resolver: "#{@userKeyResolver}"  #使用SpEL从容器中获取对象
                    redis-rate-limiter.replenishRate: 1 #令牌桶每秒填充的平均速率
                    redis-rate-limiter.burstCapacity: 1 #令牌桶总容量
                #重写转发的url,将/ebuy-product/(?.*)重新写为{segment},然后转发到订单微服务
                - RewritePath=/ebuy-product/(?<segment>.*), /$\{segment}
            - id: ebuy-order
              uri: lb://ebuy-order
              predicates:
                - Path=/ebuy-order/**
              filters:
                - RewritePath=/ebuy-order/(?<segment>.*), /$\{segment}
    
      redis:
        host: 127.0.0.1
        port: 6379
        password: 123456
    logging:
      level:
        cn.ebuy: DEBUG
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:8000/eureka/,http://127.0.0.1:9000/eureka/
      instance:
        prefer-ip-address: true   # 使用ip地址注册
    

    在application.yml中添加了redis的信息,并配置了RequestRateLimiter的限流过滤器:

    1. burstCapacity,令牌桶总容量。
    2. replenishRate,令牌桶每秒填充平均速率。
    3. key-resolver,用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
  3. 配置KeyResolver

    为了达到不同的限流效果和规则,可以通过实现 KeyResolver 接口,定义不同请求类型的限流键。

    注意:这里的@bean只允许有一个!

    package cn.ebuy.gateway.util;
    
    import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    @Configuration
    public class KeyResolverConfiguration {
        /**
         * 基于请求路径限流
         * @return
         */
        @Bean
        public KeyResolver pathKeyResolver(){
            return new KeyResolver() {
                @Override
                public Mono<String> resolve(ServerWebExchange exchange) {
                    return Mono.just(exchange.getRequest().getPath().toString());
                }
            };
        }
    
        /**
         * 基于请求ip地址的限流
         * @return
         */
    //    @Bean
        public KeyResolver ipKeyResolver() {
            return new KeyResolver() {
                @Override
                public Mono<String> resolve(ServerWebExchange exchange) {
                    return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
                }
            };
        }
        /**
         * 基于用户的限流
         * @return
         */
    //    @Bean
        public KeyResolver userKeyResolver(){
            return new KeyResolver() {
                @Override
                public Mono<String> resolve(ServerWebExchange exchange) {
                    return Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
                }
            };
        }
    }
    
  4. 测试

    配置的是一秒刷新一次,当连续刷新一时,会出现限流。在这里插入图片描述

  • 基于Sentinel的限流

    Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流的 API Gateway 进行限流。
    在这里插入图片描述
    从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:

    1. route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId
    2. 自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组Sentinel 1.6.0 引入了 Sentinel API Gateway Adapter Common 模块,此模块中包含网关限流的规则和自定义 API 的实体和管理逻辑:
      ① GatewayFlowRule:网关限流规则,针对 API Gateway 的场景定制的限流规则,可以针对不同 route 或自定义的 API 分组进行限流,支持针对请求中的参数、Header、来源 IP 等进行定制化的限流。
      ② ApiDefinition:用户自定义的 API 定义分组,可以看做是一些 URL 匹配的组合。比如我们可以定义一个 API 叫 my_api,请求 path 模式为 /foo/ ** 和 /baz/ ** 的都归到 my_api 这个 API 分组下面。限流的时候可以针对这个自定义的 API 分组维度进行限流。
  1. 导入依赖

    导入Sentinel的相应依赖

    <!-- 导入Sentinel 的相应依赖 -->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
        <version>1.6.3</version>
    </dependency>
    
  2. 编写配置类

    GatewayConfiguration.java

    package cn.ebuy.gateway.util;
    import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
    import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
    import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
    import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
    import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
    import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
    import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
    import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
    import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
    import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
    import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
    import org.springframework.beans.factory.ObjectProvider;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.Ordered;
    import org.springframework.core.annotation.Order;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.codec.ServerCodecConfigurer;
    import org.springframework.web.reactive.function.BodyInserters;
    import org.springframework.web.reactive.function.server.ServerResponse;
    import org.springframework.web.reactive.result.view.ViewResolver;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    import javax.annotation.PostConstruct;
    import java.util.*;
    
    @Configuration
    public class GatewayConfiguration {
        private final List<ViewResolver> viewResolvers;
        private final ServerCodecConfigurer serverCodecConfigurer;
        public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                    ServerCodecConfigurer serverCodecConfigurer) {
            this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
            this.serverCodecConfigurer = serverCodecConfigurer;
        }
        /**
         * 配置限流的异常处理器:SentinelGatewayBlockExceptionHandler
         */
        @Bean
        @Order(Ordered.HIGHEST_PRECEDENCE)
        public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
            return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
        }
        /**
         * 配置限流过滤器
         */
        @Bean
        @Order(Ordered.HIGHEST_PRECEDENCE)
        public GlobalFilter sentinelGatewayFilter() {
            return new SentinelGatewayFilter();
        }
    
        /**
         * 配置初始化的限流参数
         */
        @PostConstruct
        public void initGatewayRules() {
            Set<GatewayFlowRule> rules = new HashSet<>();
    		rules.add(
    			new GatewayFlowRule("product-api") //资源名称
    					.setCount(1) // 限流阈值
    					.setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
    		);
    
            GatewayRuleManager.loadRules(rules);
        }
    	
    	// 自定义异常提示
        @PostConstruct
        public void initBlockHandlers() {
            BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
                public Mono<ServerResponse> handleRequest(ServerWebExchange
                                                                  serverWebExchange, Throwable throwable) {
                    Map map = new HashMap<>();
                    map.put("code", 001);
                    map.put("message", "对不起,接口限流了");
                    return ServerResponse.status(HttpStatus.OK).
                            contentType(MediaType.APPLICATION_JSON_UTF8).
                            body(BodyInserters.fromObject(map));
                }
            };	        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
        }
    
        /**
         * 自定限流组(可选)
         */
        @PostConstruct
        private void initCustomizedApis() {
            Set<ApiDefinition> definitions = new HashSet<>();
    
            ApiDefinition api1 = new ApiDefinition("product-api")
                    .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                        add(new ApiPathPredicateItem().setPattern("/ebuy-product/product/**").
                                setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                    }});
    
            definitions.add(api1);
         GatewayApiDefinitionManager.loadApiDefinitions(definitions);
        }
    }
    
    1. 基于Sentinel 的Gateway限流是通过其提供的Filter来完成的,使用时只需注入对应的 SentinelGatewayFilter 实例以及 SentinelGatewayBlockExceptionHandler 实例即可。>2. @PostConstruct定义初始化的加载方法,用于指定资源的限流规则。这里资源的名称为product-api,统计时间是1秒内,限流阈值是1。表示每秒只能访问一个请求。
  3. 配置网关

    server:
      port: 9091 #端口
    spring:   #http://127.0.0.1:9011/product/12123
      application:
        name: ebuy-gateway #服务名称
      cloud:
        #根据服务名请求
        gateway:
          globalcors:
            cors-configurations:
              '[/**]': # 匹配所有请求
                allowedOrigins: "*" #跨域处理 允许所有的域
                allowedMethods: # 支持的方法
                  - GET
                  - POST
                  - PUT
                  - DELETE
          routes:
            - id: ebuy-product
              uri: lb://ebuy-product  #uri以lb://开头(lb代表从注册中心获取服务),后面接的就是你需要转发到的服务名称
              predicates:
                - Path=/ebuy-product/** #路由转发,直接将匹配的路由path直接拼接到映射路径url之后
              filters: #过滤规则
                #重写转发的url,将/ebuy-product/(?.*)重新写为{segment},然后转发到订单微服务
                - RewritePath=/ebuy-product/(?<segment>.*), /$\{segment}
            - id: ebuy-order
              uri: lb://ebuy-order
              predicates:
                - Path=/ebuy-order/**
              filters:
                - RewritePath=/ebuy-order/(?<segment>.*), /$\{segment}
    
      redis:
        host: 127.0.0.1
        port: 6379
        password: 123456
    logging:
      level:
        cn.ebuy: DEBUG
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:8000/eureka/,http://127.0.0.1:9000/eureka/
      instance:
        prefer-ip-address: true   # 使用ip地址注册
    
  4. 自定义API分组

    /**
      * 自定限流组(可选)
      */
     @PostConstruct
     private void initCustomizedApis() {
         Set<ApiDefinition> definitions = new HashSet<>();
    
         ApiDefinition api1 = new ApiDefinition("product-api")
                 .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                     add(new ApiPathPredicateItem().setPattern("/ebuy-product/product/**").
                             setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                 }});
    
    	 ApiDefinition api2 = new ApiDefinition("order-api")
                 .setPredicateItems(new HashSet<ApiPredicateItem>() {{
                     add(new ApiPathPredicateItem().setPattern("/ebuy-order/order/**").
                             setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
                 }});
    
        definitions.add(api1);
        definitions.add(api2);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
     }
    

    在这里插入图片描述

18.7 网关高可用

高可用HA(High Availability)是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。我们都知道,单点是系统高可用的大敌,单点往往是系统高可用最大的风险和敌人,应该尽量在系统设计的过程中避免单点。方法论上,高可用保证的原则是“集群化”,或者叫“冗余”:只有一个单点,挂了服务会受影响;如果有冗余备份,挂了还有其他backup能够顶上。
在这里插入图片描述
我们实际使用 Spring Cloud Gateway 的方式如上图,不同的客户端使用不同的负载将请求分发到后端的 Gateway,Gateway 再通过HTTP调用后端服务,最后对外输出。因此为了保证 Gateway 的高可用性,前端可以同时启动多个 Gateway 实例进行负载,在 Gateway 的前端使用 Nginx 或者 F5 进行负载转发以达到高可用性。

  • 创建多个Gateway工程

    server:
      port: 9091 #端口
    spring:   #http://127.0.0.1:9011/product/12123
      application:
        name: ebuy-gateway #服务名称
      cloud:
        #根据服务名请求
        gateway:
          globalcors:
            cors-configurations:
              '[/**]': # 匹配所有请求
                allowedOrigins: "*" #跨域处理 允许所有的域
                allowedMethods: # 支持的方法
                  - GET
                  - POST
                  - PUT
                  - DELETE
          routes:
            - id: ebuy-product
              uri: lb://ebuy-product  #uri以lb://开头(lb代表从注册中心获取服务),后面接的就是你需要转发到的服务名称
              predicates:
                - Path=/ebuy-product/** #路由转发,直接将匹配的路由path直接拼接到映射路径url之后
              filters: #过滤规则
                #重写转发的url,将/ebuy-product/(?.*)重新写为{segment},然后转发到订单微服务
                - RewritePath=/ebuy-product/(?<segment>.*), /$\{segment}
            - id: ebuy-order
              uri: lb://ebuy-order
              predicates:
                - Path=/ebuy-order/**
              filters:
                - RewritePath=/ebuy-order/(?<segment>.*), /$\{segment}
    
      redis:
        host: 127.0.0.1
        port: 6379
        password: 123456
    logging:
      level:
        cn.ebuy: DEBUG
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:8000/eureka/,http://127.0.0.1:9000/eureka/
      instance:
        prefer-ip-address: true   # 使用ip地址注册
    
    
    spring:
    	profiles: gateway01
    Server:
    	port: 8080 #服务端口
    --------------------------
    spring:
    	profiles: gateway02
    Server:
    	port: 8081 #服务端口
    

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

  • 配置nginx

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值