Loading

Gateway

目前,我们的微服务架构已经有了微服务集群、注册中心和配置中心,并且我们的微服务集群可以利用各种远程调用协议来相互调用。

不过,在当前的场景下,用户如果想要去使用某个服务,他必须直接调用微服务,这对用户十分不友好,用户可能要记住很多很多服务的IP及端口,并且,他要手动选择到一个具体的服务实例上,这让我们无法对用户想要访问的请求做一个负载均衡,并且,一些服务可能是需要对外部隐藏的,只在微服务内部被调用,也有一些可能需要权限的校验。总结一下就是:

  1. 杂乱的微服务实例,对用户不友好
  2. 缺少负载均衡机制
  3. 暴露所有微服务,应该根据实际情况隐藏或校验访问权限

总的来说,问题很多,但都可以归结为:我们的系统缺少一个面向用户的门面

而这个门面,就可以用网关来实现,SpringCloud中提供了两个网关组件:GatewayZuul,这节主要介绍Gateway

所以,网关的目的就是给用户提供一个一致的接口,并可以在其中做任何操作,比如负载均衡、隐藏服务、权限校验

QuickStart

创建网关项目

网关是微服务架构中的一个单独组件,就像一个单独的服务实例,所以我们要创建一个项目网关项目。

然后,引入如下依赖:

<dependencies>
    <!-- 网关组件依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!-- nacos服务发现 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>

为什么网关需要引入nacos?因为它要转发用户的请求到实际的微服务实例,所以它要获取所有的服务列表,并且在其中做负载均衡。

配置application.yaml

下面的配置较长,但除了gateway中的内容,剩下的我们都见过:

spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/user/**
        - id: order-service-route
          uri: lb://order-service
          predicates:
            - Path=/order/**

server:
  port: 80

简单的说一下,上面的配置就是配置了一下nacos注册中心的地址用于网关拉取微服务实例信息,并且将自己的端口设置为80,服务名设置成gateway。

然后,在spring.cloud.gateway.routes中配置了路由规则。

路由规则

既然网关要将用户发给它的请求路由到微服务实例上,那么怎么路由?这就是路由规则指定的。

routes是一个list,list中的元素又是一个map,每一个元素描述一个路由规则。

  • id描述了路由规则的唯一标识
  • uri描述要将请求映射到哪里,这里的lb协议代表启用负载均衡,然后后面跟的是服务名,你也可以跟http://ip
  • predicates描述了路由映射的断言列表,如果用户请求满足断言列表中的所有断言时,它就会被路由到uri

所以上面,我们提供了一个到user-service的路由,当请求路径中带有/user前缀就匹配到这个路由,同理还有一个order-service的路由。

运行

img

总结

现在我们通过Gateway组件解决了之前的问题:

  1. 用户对系统的访问仅仅通过网关
  2. 网关可以提供相同服务间的负载均衡机制
  3. 你可以通过路由规则对用户隐藏某些服务

断言工厂

gateway提供了一个接口:RoutePredicateFactory,它是生成一种路由断言规则的东西,它有如下实现类:

img

这些实现类都是我们可以使用的断言方式,其中,有我们已经使用过的也是最常用的,基于路径的PathRoutePredicateFactory

下面是可以使用的断言列表:

img

过滤器工厂

过滤器允许你对转发到微服务实例的请求以及从微服务实例拿到的响应做一些处理,有如下的过滤器:

img

其中可以看到,有对请求头做处理的,有对响应头做处理的,有做断路器的等等,其实这个列表中总共有32个Filter。

你可以通过配置文件来对某个路由规则添加filter,也可以添加默认filter,即作用于所有路由规则的filter:

spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/user/**
          filters:
            - AddResponseHeader=CustomHeader, CustomValue
        - id: order-service-route
          uri: lb://order-service
          predicates:
            - Path=/order/**
      default-filters: 
        - AddResponseHeader=DefaultCustomHeader, DefaultCustomValue

上面的代码中,我们给user-service-route这个路由规则添加了一个添加响应头的Filter,并添加了我们自定义的响应头,同时通过default-filters给所有路由规则添加默认的过滤器。

再次访问user服务,携带了两个自定义的响应头:

img

而访问order服务,只携带了一个默认自定义响应头:

img

全局过滤器

有时候我们需要实现一些逻辑和业务相关性比较高的过滤器,此时不能指望上面的那些过滤器了,我们可以自己实现GlobalFilter接口来定义自己的过滤逻辑。

@Order(-1) // 设置过滤器顺序
@Component // 将过滤器声明为一个Bean
public class AuthFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> params = request.getQueryParams();
        // 检查参数中的`role`,判断是否为`admin`,如果是,放行,否则终结这个请求
        if (params.containsKey("role") && params.getFirst("role").equals("admin")) {
            return chain.filter(exchange);
        }
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        return response.setComplete();
    }
}

过滤器顺序

Gateway会将到来的请求匹配的所有过滤器合并,其中包括当前路由的过滤器、默认过滤器和全局过滤器,并将合并后的过滤器集合排序。

  1. 每一个过滤器都有一个order序号,序号越小优先级越高
  2. GlobalFilter的序号是通过@Order定义的
  3. 路由过滤器和default过滤器的顺序按照定义顺序从1递增,并且它们分开计数,即一个路由过滤器中的第一个order是1,但不影响default中第一个也是1
  4. 当两个过滤器序号相同时(只可能在不层次的过滤器上发生,比如不可能在两个默认过滤器上发生),顺序是defaultFilter > 路由过滤器 > GlobalFilter

跨域处理

如果我们的网关有可能被浏览器通过ajax跨域访问的话,那可以做一些跨域相关的配置:

gateway:
    globalcors:
    add-to-simple-url-handler-mapping: true # 解决默认Options请求被拦截的问题
    cors-configurations:
        '[/**]':
        allowed-origins:
            - "http://localhost:8090"
        allowed-methods:
            - "GET"
            - "POST"
            - "PUT"
            - "DELETE"
            - "OPTIONS"
        allowed-headers: "*"
        allow-credentials: true # 允许携带cookie
        max-age: 360000 # 跨域有效期
posted @ 2022-08-06 15:07  yudoge  阅读(124)  评论(0编辑  收藏  举报