Gateway网关

1、微服务网关的基本介绍

不同的微服务一般会有不同的网络地址,客户端在访问这些微服务时必须记住几十甚至几百个地址,这对于客户端方来说太复杂也难以维护,如果让客户端直接与各个微服务通讯,可能会有很多问题:

  1. 客户端会请求多个不同的服务,需要维护不同的请求地址,增加开发难度
  2. 在某些场景下存在跨域请求的问题
  3. 加大身份认证的难度,每个微服务需要独立认证

因此,我们需要一个微服务网关,介于客户端与服务器之间的中间层。所有的外部请求都会先经过微服务网关,客户端只需要与网关交互,只知道一个网关地址即可。

网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过网关这一层。也就是说,API 的实现方更多地只需要考虑业务逻辑,而安全、性能、监控可以交由网关来完成,这样既提高业务灵活性又不缺安全性。

微服务网关是一个服务器,是系统对外的唯一入口。网关封装了系统内部架构,为外部客户端提供一个定制的 API。API 网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP 的访问 API。服务端通过 API-GW 注册和管理服务。

 

1.1、使用微服务网关的优点

网关具有的职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。当然,最主要的职责还是与“外界联系”。

使用网关的优点:

  1. 安全 。只有网关系统对外进行暴露,微服务可以隐藏在内网,通过防火墙保护。
  2. 易于监控。可以在网关收集监控数据并将其推送到外部系统进行分析。
  3. 易于认证。可以在网关上进行认证,然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证。
  4. 减少了客户端与各个微服务之间的交互次数。
  5. 易于统一授权。

微服务网关就是一个系统,通过暴露该微服务网关系统,方便我们进行相关的鉴权,安全控制,日志统一处理,易于监控的相关功能。

 

1.2、常见的网关实现方式

常见的实现微服务网关的技术有以下:

  1. Nginx。 使用Nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用。
  2. zuul。Zuul 是 Netflix 出品的一个基于 JVM 路由和服务端的负载均衡器。Netflix开源,功能丰富,使用JAVA开发,易于二次开发;需要运行在web容器中,如Tomcat。但是缺乏管控,无法动态配置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如Nginx;
  3. spring cloud gateway。gateway 是spring 出品的 基于spring 的网关项目,集成断路器,路径重写,性能比Zuul好。

 

2、搭建gateway网关服务

创建一个以 springboot 作为模板的 gateway 模块,引入nacos服务发现和gateway依赖,如下:

<!--nacos服务注册发现依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!--网关gateway依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

然后在配置文件 application.yml 中配置服务基本信息、nacos地址、路由等即可。

路由配置包括:

  1. 路由id:路由的唯一标示
  2. 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
  3. 路由断言(predicates):判断路由的规则
  4. 路由过滤器(filters):对请求或响应做处理

如下:

server:
  port: 10010
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
    gateway:
      routes:
        - id: user-service   # 路由标示,必须唯一
          # uri: http://127.0.0.1:8081 # 路由的目标地址。也可以通过http的写成固定地址,但不推荐使用
          uri: lb://userservice # 路由的目标地址。lb就是负载均衡的意思,后面跟服务名称
          predicates: # 路由断言,判断请求是否符合规则
            - Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合
        - id: order-service
          uri: lb://orderservice
          predicates:
            - Path=/order/**

启动该服务,可以在 nacos 注册中心中查看到该服务,如下:

然后外部服务即可直接通过访问网关来访问微服务,而无需直接访问微服务的地址,如下通过直接访问 http://localhost:10010/order/101 即可访问到 order-service 的服务,网关会自动将匹配到的路由转发至对应的服务中,并且会自动实现负载均衡。

 

 

 

2.1、路由断言工厂(Route Predicate Factory)

我们在配置文件中写的断言规则只是字符串,这些字符串会被 Predicate Factory(断言工厂) 读取并处理,转变为路由判断的条件。例如 Path=/user/** 是按照路径匹配,这个规则是由rg.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory 类来处理的,像这样的断言工厂在SpringCloudGateway还有十几个。

Spring提供了11种基本的Predicate工厂,如下:

 

 

示例:

spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
    gateway:
      routes:
        - id: order-service
          uri: lb://orderservice
          predicates:
            - Path=/order/**
            - After=2022-04-20T15:35:08.721398+08:00[Asia/Shanghai]

上面的路由断言意思是必须匹配 /order/ 路径,并且只有在 2022-04-20 日期后才能匹配成功正常访问,必须同时满足条件才能正常访问到服务,否则无法访问,接口会报 404。

可参考官网:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories

 

2.2、路由过滤器工厂(GatewayFilter)

GatewayFilter 是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理。

Spring提供了31种不同的路由过滤器工厂。例如:

 

 

示例,比如我们通过路由过滤器来配置所有进入某个服务,比如 userservice 服务的请求都自动添加一个请求头:X-Request-red=blue。只需在配置 gateway 服务的配置文件中添加以下配置即可,如下:

spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
    gateway:
      routes:
        - id: user-service 
          uri: lb://userservice 
          predicates: 
            - Path=/user/** 
          filters: # 过滤器
            - AddRequestHeader=X-Request-red, blue # 添加请求头

由此,所有通过 gateway 网关发往 userservice 的请求都会自动添加 X-Request-red=blue 请求头,在 userservice 服务中可以通过参数获取到该请求头数据。

上面的过滤器配置是针对了某个具体的服务,如果要对所有的路由都生效,则可以将过滤器工厂写到 default 下。格式如下:

spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
    gateway:
      routes:
        - id: user-service 
          uri: lb://userservice 
          predicates: 
            - Path=/user/** default-filters: # 默认过滤器,会对所有的路由请求都生效
        - AddRequestHeader=X-Request-red, blue # 添加请求头

 

可参考官网:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories

 

2.3、全局过滤器(GlobalFilter)

全局过滤器(GlobalFilter)与 GatewayFilter 的 default-filters 的作用类似,也是处理一切进入网关的请求和微服务响应,对所有路由都生效。区别在于 GatewayFilter 通过配置定义,只能处理一些比较简单的逻辑,而 GlobalFilter 的逻辑通过代码实现,可以自定义复杂逻辑。

要想实现全局过滤器需要实现 GlobalFilter 接口:

public interface GlobalFilter {
   /**
    *  处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
    *
    * @param exchange 请求上下文,里面可以获取Request、Response等信息 
    * @param chain 用来把请求委托给下一个过滤器 
    * @return {@code Mono<Void>} 返回标示当前过滤器业务结束   
   */
   Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

示例,我们定义一个全局过滤器,拦截所有的请求,判断请求的参数中是否有 authorization,并且参数值是否为admin,如果是则放行,否则拦截该请求。

我们在 gateway 服务中创建一个类,通过该类实现 GlobalFilter 接口即可,如下:

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.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Order(0)
@Component
public class AuthorizeFilter implements GlobalFilter{
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> params = request.getQueryParams();
        // 2.获取参数中的 authorization 参数
        String auth = params.getFirst("authorization");
        // 3.判断参数值是否等于 admin
        if ("admin".equals(auth)) {
            // 4.是,放行
            return chain.filter(exchange);
        }
        // 5.否,拦截
        // 5.1.设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);   //这里设置了HttpStatus.UNAUTHORIZED状态码,即401,如果请求的参数不满足时将会返回401
        // 5.2.拦截请求
        return exchange.getResponse().setComplete();
    }
}

 

2.4、过滤器的执行顺序(defaultFilter、路由过滤器、GlobalFilter)

请求在进入网关后会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter。在请求匹配到路由后,会将 当前路由过滤器、DefaultFilter、GlobalFilter,合并到同一个过滤器链(集合)中,通过比较它们之间的 order 值大小进行排序,值越小越先执行,以此来依次执行每个过滤器。

  • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
  • GlobalFilter 通过实现 Ordered 接口,或者添加 @Order 注解来指定 order 值;但是路由过滤器和 defaultFilter 的 order 则由 Spring 自动指定,默认是按照声明顺序从1递增,比如 defaultFilter 下有多个过滤器,则从上至下默认从1开始递增,如果 filters 下定义了多个过滤器,同样从上至下默认从1开始递增
  • 当不同类型之间的过滤器的 order 值大小一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter 的顺序执行。

 

3、服务网关解决跨域问题

跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题。网关处理跨域采用的是 CORS 方案,只需要简单配置即可实现,如下:

spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
    gateway:
      # gateway的全局跨域请求配置
      globalcors:
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':  # 匹配的路由
            allowedHeaders: "*"  # 允许在请求中携带的头信息
            allowedOrigins: "*"  # 允许哪些网站的跨域请求。也可以通过数组的方式来只指定一些特定的网站
            allowCredentials: true  # 是否允许携带cookie
            allowedMethods: "*"  # 允许的跨域ajax的请求方式。也可以通过数组的方式来只指定一些特定的请求方式
            maxAge: 360000 # 跨域检测的有效期

 

posted @ 2022-05-12 15:53  wenxuehai  阅读(1588)  评论(0编辑  收藏  举报
//右下角添加目录