spring cloud gateway网关再浅析

网关是微服务架构应用系统的前置流量入口,有着非常重要的作用,下面上接前文,继续聊聊Spring Cloud Gateway网关。
1、为什么需要网关
传统的单体架构中只需要开发一个服务给客户端调用,但是在微服务架构中是将一个应用系统拆分成多个微服务,客户端需要在本地记录每个微服务的调用地址,当微服务数量或实例很多时,客户端调用时就会很麻烦,增加了维护微服务地址的工作量。
网关作为应用系统的唯一流量入口,封装了应用系统的架构,所有的请求都先经过网关,由网关服务将请求路由到合适的微服务,主要作用如下:

  • 简化客户端工作:网关将微服务封装起来后,客户端只需同网关交互,而不必调用各个不同服务;
  • 降低耦合度:一旦服务地址修改,只需要修改网关的路由策略,不必修改每个调用的客户端,从而降低耦合度;
  • 让开发人员把精力专注于业务逻辑:由网关统一实现服务路由、负载均衡、访问控制、流控、熔断降级等非业务功能;

但是网关也存在不足之处,在微服务这种去中心化的架构中,网关又成为了一个中心点或瓶颈点,它增加了一个必须开发、部署和维护的组件,所以需要对网关的响应结果有数据缓存能力,通过返回缓存数据或默认数据屏蔽后端服务的失败。网关最好是支持 I/O 异步、同步非阻塞的,如果服务是同步阻塞调用,可以理解为微服务模块之间是没有彻底解耦的,即如果A依赖B提供的API,如果B提供的服务不可用将直接影响到A不可用。

2、网关基本功能
在这里插入图片描述
3、Spring Cloud Gateway网关
搭建网关时pom中引入如下依赖

<properties>
  <spring-boot.version>2.7.0</spring-boot.version>
  <spring-cloud.version>2021.0.4</spring-cloud.version>
  <spring-cloud-alibaba.version>2021.1</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>
  </dependencies>
</dependencyManagement>

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

Spring Cloud Gateway概念:

  • 断言:predicate,参照 Java8 的新特性Predicate,允许开发人员匹配 HTTP 请求中的任何内容,比如请求头或请求参数,最后根据匹配结果返回一个布尔值;
  • 路由:route,由ID、目标URI、断言集合和过滤器集合组成。如果聚合断言结果为真,则转发到该路由;
  • 过滤器:filter,可以在返回请求之前或之后修改请求和响应的内容;

Ⅰ、断言
Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。Predicate 可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。
权重配置示例:

spring:
  cloud:
    gateway:
      # 路由数组:指当请求满足什么样的断言时,转发到哪个服务上
      routes:
          # 路由标识,要求唯一,名称任意
        - id: gateway-provider_1
          # 请求最终被转发到的目标地址
          uri: http://localhost:9024
          # 设置断言
          predicates:
            # Path Route Predicate Factory 断言,满足 /gateway/provider/** 路径的请求都会被路由到 http://localhost:9024 这个uri中
            - Path=/gateway/provider/**
            # Weight Route Predicate Factory 断言,同一分组按照权重进行分配流量,这里分配了80%
            # 第一个group1是分组名,第二个参数是权重
            - Weight=group1, 8
          # 配置过滤器(局部)
          filters:
            # StripPrefix:去除原始请求路径中的前1级路径,即/gateway
            - StripPrefix=1            
            
        - id: gateway-provider_2
          uri: http://localhost:9025
          predicates:
            - Path=/gateway/provider/**
            # Weight Route Predicate Factory,同一分组按照权重进行分配流量,这里分配了20%
            - Weight=group1, 2
          filters:
            # StripPrefix:去除原始请求路径中的前1级路径,即/gateway
            - StripPrefix=1  

Ⅱ、路由
Route 主要由 路由id、目标uri、断言集合和过滤器集合组成。

  • id:路由标识,要求唯一,名称任意(默认值 uuid,一般不用,需要自定义);
  • uri:请求最终被转发到的目标地址;
  • order:路由优先级,数字越小,优先级越高;
  • predicates:断言数组,即判断条件,如果返回值是true,则转发请求到 uri 属性指定的服务中;
  • filters:过滤器数组,在请求传递过程中,对请求做一些修改;

Ⅲ、过滤器

gateway过滤器的生命周期分为:

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

gateway过滤器的作用范围分为:

  • GatewayFilter:应用到单个路由或者一个分组的路由上(需要在配置文件中配置);
    Spring Cloud Gateway 中内置了许多的局部过滤器,详细查看官网。
  • GlobalFilter:应用到所有的路由上(无需配置,全局生效);
    全局过滤器应用全部路由上,无需开发者配置,Spring Cloud Gateway 也内置了一些全局过滤器,详细查看官网。

4、Spring Cloud Gateway集成nacos注册中心
上面的配置示例中并没有集成注册中心,每次路由配置都是指定固定的服务ip:port,回看如下
在这里插入图片描述
这样配置,网关服务需要知道所有服务的域名或ip+port,另外,一旦服务的域名或ip+port发生变化,路由配置中的uri也必须修改。再者,服务集群中无法实现负载均衡。
那么可以通过集成nacos注册中心实现服务发现,网关能够从注册中心自动获取uri,并实现负载均衡。
配置nacos如下:

nacos:
  namespace: 856a40d7-6548-4494-bdb9-c44491865f63
  url: 127.0.0.1:8081
spring:
  cloud:
    nacos:
      discovery:
       server-addr: ${nacos.url}
        namespace: ${nacos.namespace}
        register-enabled: true

配置gateway修改为:

spring:
  cloud:
    gateway:
      routes:
        - id: gateway-provider_1
          ## 使用了lb形式,从注册中心负载均衡的获取uri
          uri: lb://gateway-provider
          ## 配置断言
          predicates:
            - Path=/gateway/provider/**
          ## 配置过滤器
          filters:
            - AddResponseHeader=X-Response-Foo, Bar

配置中唯一不同的就是路由uri,格式为

lb://service-name
其中,lb:固定格式,指的是从nacos中按照名称获取微服务,并遵循负载均衡策略;service-name:nacos注册中心的服务名称,这里并不是IP地址形式的;

为什么指定了 lb 就可以开启负载均衡,前面说过全局过滤器 LoadBalancerClientFilter 就是负责路由寻址和负载均衡的,若是lb形式的,就进行负载均衡处理了。
随着系统架构的不断发展,微服务的数量肯定会越来越多,不可能每添加一个微服务,就在网关配置一个新的路由规则,那样维护成本很大。这时可以开启自动路由功能,网关自动根据注册中心的服务名为每个服务创建一个router,将以服务名开头的请求路径转发到对应的服务,配置如下:

# enabled:默认为false,设置为true表明spring cloud gateway开启服务发现和路由的功能,网关自动根据注册中心的服务名为每个服务创建一个router,将以服务名开头的请求路径转发到对应的服务
spring.cloud.gateway.discovery.locator.enabled = true
# lowerCaseServiceId:启动 locator.enabled=true 自动路由时,路由的路径默认会使用大写ID,若想要使用小写ID,可将lowerCaseServiceId设置为true
spring.cloud.gateway.discovery.locator.lower-case-service-id = true

5、Spring Cloud Gateway自定义全局异常处理
在这里插入图片描述

在应用网关时可以看到一个现象,一旦路由的微服务下线或者失联了,Spring Cloud Gateway直接返回了一个错误页面,显然这种异常信息不友好,前后端分离架构中必须定制返回的异常信息。传统的Spring Boot 服务中都是使用 @RestControllerAdvice 来包装全局异常处理的,但是由于服务下线,请求并没有到达。因此必须在网关中也要定制一层全局异常处理,这样才能更加友好的和客户端交互。
实现 ErrorWebExceptionHandler,重写其中的 handle 方法,如下

@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);
        }

        // Json格式返回
        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 {
                ResponseData resultMsg = ResponseData.fail(ex.getMessage(),503);
                return bufferFactory.wrap(objectMapper.writeValueAsBytes(resultMsg));
            }
            catch (JsonProcessingException e) {
                log.error("Error writing response", ex);
                return bufferFactory.wrap(new byte[0]);
            }
        }));
    }
}

测试结果类似如下
浏览器访问
在这里插入图片描述
postman访问
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值