Gateway服务网关的介绍与使用

什么是服务网关

传统的单体架构中只需要开放一个服务给客户端调用,但是微服务架构中是将一个系统拆分成多个微服务,如果没有网关,客户端只能在本地记录每个微服务的调用地址,当需要调用的微服务数量很多时,它需要了解每个服务的接口,这个工作量很大。那有了网关之后,能够起到怎样的改善呢?

网关作为系统的唯一流量入口,封装内部系统的架构,所有请求都先经过网关,由网关将请求路由到合适的微服务,所以,使用网关的好处在于:

  • 简化客户端的工作。 网关将微服务封装起来后,客户端只需同网关交互,而不必调用各个不同服务;
  • 降低函数间的耦合度。 一旦服务接口修改,只需修改网关的路由策略,不必修改每个调用该函数的客户端,从而减少了程序间的耦合性
  • 解放开发人员把精力专注于业务逻辑的实现。
    由网关统一实现服务路由(灰度与ABTest)、负载均衡、访问控制、流控熔断降级等非业务相关功能,而不需要每个服务 API 实现时都去考虑
    但是 API 网关也存在不足之处,在微服务这种去中心化的架构中,网关又成了一个中心点或瓶颈点,它增加了一个我们必须开发、部署和维护的高可用组件。正是由于这个原因,在网关设计时必须考虑即使 API 网关宕机也不要影响到服务的调用和运行,所以需要对网关的响应结果有数据缓存能力,通过返回缓存数据或默认数据屏蔽后端服务的失败。

服务网关的基本功能:

Gateway服务网关的介绍与使用_第1张图片

流量网关与服务网关的区别

Gateway服务网关的介绍与使用_第2张图片
流量网关和服务网关在系统整体架构中所处的位置如上图所示,流量网关(如Nignx)是指提供全局性的、与后端业务应用无关的策略,例如 HTTPS证书卸载、Web防火墙、全局流量监控等。
而微服务网关(如Spring Cloud Gateway)是指与业务紧耦合的、提供单个业务域级别的策略,如服务治理、身份认证等。也就是说,流量网关负责南北向流量调度及安全防护,微服务网关负责东西向流量调度及服务治理。

服务网关的介绍

Gateway服务网关的介绍与使用_第3张图片

  • **Kong 网关:**Kong 的性能非常好,非常适合做流量网关,但是对于复杂系统不建议业务网关用 Kong,主要是工程性方面的考虑
  • Zuul1.x 网关:Zuul 1.0
    的落地经验丰富,但是性能差、基于同步阻塞IO,适合中小架构,不适合并发流量高的场景,因为容易产生线程耗尽,导致请求被拒绝的情况
  • gateway 网关:功能强大丰富,性能好,官方基准测试 RPS (每秒请求数)是Zuul的1.6倍,能与 SpringCloud
    生态很好兼容,单从流式编程+支持异步上也足以让开发者选择它了。
  • Zuul 2.x:性能与 gateway 差不多,基于非阻塞的,支持长连接,但 SpringCloud 没有集成 zuul2 的计划,并且
    Netflix 相关组件都宣布进入维护期,前景未知。
    综上,gateway 网关更加适合 SpringCloud 项目,而从发展趋势上看,gateway 替代 zuul 也是必然的。

Spring Cloud Gateway 网关的

  • pom.xml 的 引入
<properties>
    <spring-boot.version>2.3.2.RELEASE</spring-boot.version>
    <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
    <spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>
</properties>

<!-- 只声明依赖,不引入依赖 -->
<dependencyManagement>
    <dependencies>
        <!-- 声明springBoot版本 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- 声明springCloud版本 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- 声明 springCloud Alibaba 版本 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- 引入gateway网关 -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </exclusion>
    </exclusions>
</dependency>
    </dependencies>
</dependencyManagement>
  • 配置项目名与端口:
server:
  port: 8100
spring:
  application:
    name: gateway-service
  cloud:
    nacos:
      server-addr: 33.183.134.49:8848
    gateway:
      routes:
        - id: mqtt-service-route
          uri: lb://mqtt-service
          predicates:
            - Path=/mqtt/**
        - id: usermgr-service-route
          uri: lb://usermgr-service
          predicates:
            - Path=/usermgr/**
  • Spring Cloud Gateway 配置项的说明:

  • 断言(Predicate):参照 Java8 的新特性Predicate,允许开发人员匹配 HTTP请求中的任何内容,比如请求头或请求参数,最后根据匹配结果返回一个布尔值。

  • 路由(route):由ID、目标URI、断言集合和过滤器集合组成。如果聚合断言结果为真,则转发到该路由。

  • 过滤器(filter):可以在返回请求之前或之后修改请求和响应的内容。

  • 路由 Route:
    Route 主要由 路由id、目标uri、断言集合和过滤器集合组成,那我们简单看看这些属性到底有什么作用。

  • id:路由标识,要求唯一,名称任意(默认值 uuid,一般不用,需要自定义)

  • uri:请求最终被转发到的目标地址

  • order:路由优先级,数字越小,优先级越高

  • predicates:断言数组,即判断条件,如果返回值是boolean,则转发请求到 uri 属性指定的服务中

  • filters:过滤器数组,在请求传递过程中,对请求做一些修改

  • 断言 Predicate
    Predicate 来自于 Java8 的接口。Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
    Predicate 可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。Spring Cloud Gateway 内置了许多 Predict,这些 Predict 的源码在 org.springframework.cloud.gateway.handler.predicate 包中。
    实例如下:

spring:
  cloud:
    gateway:
      # 路由数组:指当请求满足什么样的断言时,转发到哪个服务上
      routes:
        # 路由标识,要求唯一,名称任意
        - id: gateway-provider_1
    # 请求最终被转发到的目标地址
          uri: http://mqtt-service
          # 设置断言
          predicates:
            # Path Route Predicate Factory 断言,满足 /gateway/provider/** 路径的请求都会被路由到 http://localhost:9024 这个uri中
            - Path=/mqtt/**
            # Weight Route Predicate Factory 断言,同一分组按照权重进行分配流量,这里分配了80%
            # 第一个group1是分组名,第二个参数是权重
            - Weight=group1, 8
          # 配置过滤器(局部)
          filters:
            # StripPrefix:去除原始请求路径中的前1级路径,即/gateway
            - StripPrefix=1              
  • 过滤器 filter:
    Gateway 过滤器的生命周期:
  • PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP
    Header、收集统计信息和指标、将响应从微服务发送给客户端等。
    Gateway 过滤器从作用范围可分为两种:
  • GatewayFilter:应用到单个路由或者一个分组的路由上(需要在配置文件中配置)
  • GlobalFilter:应用到所有的路由上(无需配置,全局生效)
  • 自定义局部过滤器
    虽说内置的过滤器能够解决很多场景,但是难免还是有些特殊需求需要定制一个过滤器,下面就来介绍一下如何自定义局部过滤器。
/**
 * 名称必须是xxxGatewayFilterFactory形式
 * todo:模拟授权的验证,具体逻辑根据业务完善
 */
@Component
@Slf4j
public class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthorizeGatewayFilterFactory.Config> {

    private static final String AUTHORIZE_TOKEN = "token";

    //构造函数,加载Config
    public AuthorizeGatewayFilterFactory() {
        //固定写法
        super(AuthorizeGatewayFilterFactory.Config.class);
        log.info("Loaded GatewayFilterFactory [Authorize]");
    }

    //读取配置文件中的参数 赋值到 配置类中
    @Override
    public List<String> shortcutFieldOrder() {
        //Config.enabled
        return Arrays.asList("enabled");
    }

    @Override
    public GatewayFilter apply(AuthorizeGatewayFilterFactory.Config config) {
        return (exchange, chain) -> {
            //判断是否开启授权验证
            if (!config.isEnabled()) {
                return chain.filter(exchange);
            }

            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = request.getHeaders();
            //从请求头中获取token
            String token = headers.getFirst(AUTHORIZE_TOKEN);
            if (token == null) {
                //从请求头参数中获取token
                token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
            }

            ServerHttpResponse response = exchange.getResponse();
            //如果token为空,直接返回401,未授权
            if (StringUtils.isEmpty(token)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                //处理完成,直接拦截,不再进行下去
                return response.setComplete();
            }
            /**
             * todo chain.filter(exchange) 之前的都是过滤器的前置处理
             *
             * chain.filter().then(
             *  过滤器的后置处理...........
             * )
             */
            //授权正常,继续下一个过滤器链的调用
            return chain.filter(exchange);
        };
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Config {
        // 控制是否开启认证
        private boolean enabled;
    }
}

局部过滤器需要在路由中配置才能生效,配置如下:

spring:
  cloud:
    gateway:
      routes:
        - id: gateway-provider_1
          uri: http://localhost:9024
          predicates:
            - Path=/gateway/provider/**
          ## 配置过滤器(局部)
          filters:
            - AddResponseHeader=X-Response-Foo, Bar
            ## AuthorizeGatewayFilterFactory自定义过滤器配置,值为true需要验证授权,false不需要
            - Authorize=true
  • GlobalFilter 全局过滤器:

全局过滤器应用全部路由上,无需开发者配置

@Slf4j
@Component
@Order(value = Integer.MIN_VALUE)
public class AccessLogGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //filter的前置处理
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().pathWithinApplication().value();
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        return chain
            //继续调用filter
            .filter(exchange)
            //filter的后置处理
            .then(Mono.fromRunnable(() -> {
                ServerHttpResponse response = exchange.getResponse();
                HttpStatus statusCode = response.getStatusCode();
                log.info("请求路径:{},远程IP地址:{},响应码:{}", path, remoteAddress, statusCode);
            }));
    }
}

全局过滤器不必在路由上配置,注入到IOC容器中即可全局生效。
网关自定义全局异常处理器:

/**
 * 用于网关的全局异常处理
 * @Order(-1):优先级一定要比ResponseStatusExceptionHandler低
 */
@Slf4j
@Order(-1)
@Component
@RequiredArgsConstructor
public class GlobalErrorExceptionHandler implements ErrorWebExceptionHandler {

    private final ObjectMapper objectMapper;

    @SuppressWarnings({"rawtypes", "unchecked", "NullableProblems"})
    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse response = exchange.getResponse();
        if (response.isCommitted()) {
            return Mono.error(ex);
        }

        // JOSN格式返回
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        if (ex instanceof ResponseStatusException) {
            response.setStatusCode(((ResponseStatusException) ex).getStatus());
        }

        return response.writeWith(Mono.fromSupplier(() -> {
            DataBufferFactory bufferFactory = response.bufferFactory();
            try {
                //todo 返回响应结果,根据业务需求,自己定制
                CommonResponse resultMsg = new CommonResponse("500",ex.getMessage(),null);
                return bufferFactory.wrap(objectMapper.writeValueAsBytes(resultMsg));
            }
            catch (JsonProcessingException e) {
                log.error("Error writing response", ex);
                return bufferFactory.wrap(new byte[0]);
            }
        }));
    }
}

你可能感兴趣的:(gateway,微服务,java)