网关的概念

首先要清楚网关的概念,网关是一个逻辑上的概念,是指一个连接不同网络的节点。比如说家里的运营商的入网的路由器就是一个网关,它连接的是家里的网络(ip 地址范围一般在 192.168.x.x );nginx 也可以作一个网关,你可以只向公网暴露一个 nginx 所用的 ip 和端口号,nginx 再根据请求中的域名和路径等信息将请求转发到内网中实际的应用服务上。

微服务网关和别的网关没有太大的区别,只是这个网关的背后的目标不是主机(路由器做网关时目标是主机),而是一组微服务。

微服务为什么需要网关

  1. 提供统一的出/入口
    微服务的网关的基本作用和普通的网关一样,均是连接不同的网络。
    你可能疑惑为什么不直接暴露所有的微服务?
    先不说这样会使用掉比较多的 ip 资源,直接暴露所有的微服务就需要为每个服务做服务层面的防火墙;再者如果你的微服务是对外开放的,那么当一个 ip 地址被使用之后你就没办法撤回来了;反之如果为了增强服务的可用性部署多个实例,那么由于新增的服务 ip 地址的不同,它们实际上不会为原有的服务分担压力(除非你在这个服务的应用层面做了负载均衡)。
    这一切的问题根本原因就是没有一个统一的出入口。
  2. 配合服务发现进行负载均衡
    在我最初使用 nignx 做网关时,nginx 所有的行为都和 ip: port 绑定,如果我需要为某一个服务添加一个实例为它们做负载均衡的话,我需要手动的改配置。在我个人的服务上尚且能够接受,如果服务变动的频率和数量大大增加之后,非自动化的处理将变得不可接受。
    在配合服务发现之后这个问题得到了解决。
    微服务的网关大多是与服务发现关联的,微服务的网关的行为同样是由配置决定的,不过不同于静态的配置,微服务的网关从服务发现中心获取各个服务的配置,包括它们的命名空间, ip 地址,端口号,甚至是当前的压力情况等。当一个服务启动之后会向服务发现中心注册自己,这样一来,别的服务查询时就能发现这个新加入的服务(在这里是微服务网关)。
    微服务的网关在同一种服务的实例之间实现负载均衡(这同一种服务的多个实例就被称为一个集群)。
    除了负载均衡外,还可以实现其他的优化,大概的列一下吧:
    * 熔断(断路器):检测一个服务,如果它的请求失败率超过了一个阈值,那么在之后的一段时间内,都不会将请求发送到这个实例上(为的是让这个实例有机会处理完当前的任务而回到一个比较健康的状态,也是降低请求的失败几率)。
    * 舱壁隔离(不是在网关中实现的,这里还是简单的提一下):限制一个服务所使用的最大资源量,超过资源量时直接让请求失败而不处理请求
    (可能过于抽象了,举个例子,我们有一台机器上部署有多个服务,一个服务的调用遇到了一个边界情况的请求,比如在一个非优化的快排的接口传入了一个逆序的序列,复杂度从 O(n $log_n$ ) 提高到 O( $n^2$ ) ,然后整个机器上的所有服务对外都不响应了。这就是一个服务的异常状态影响了整个服务。如果我们使用舱壁隔离,最后的情况可能就是这个快排服务用了一整天去处理这个请求,实际上 20s 就差不多超时了,而其他服务正常工作了一天)。
    * 重试:如果一个对幂等的接口的请求的失败是由于偶然的错误,那么可以在一定限度内重复这个请求。
    这些优化都可以归类到容错中,具体的可见:服务容错 | 凤凰架构 (icyfenix.cn)

简单搭建

一贯的简单讲人话风格。

一个典型的微服务网关需要至少三个应用,服务注册/发现中心,执行业务逻辑的服务,以及服务网关。

当然,甚至也可以使用单独的网关处理对静态资源的请求。

执行业务逻辑的服务,以及服务注册/发现中心可以参照一下另一篇博客:spring 服务注册/发现(eureka) (kicey.site)

下面对 spring-cloud-gateway 做一个简单的引入。

依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

这里除了 spring-cloud-gateway 本身之外还使用了 eureka-client 依赖,用于与服务发现中心交互。

配置

spring:
  application:
    name: spring-cloud-gateway

  cloud:
    gateway:
      discovery:
        locator:
          enabled: true

eureka:
  client:
    service-url:
      defaultZone: http://host:port/path/

注意 cloud.gateway.discovery.locator.enabled: true 这个配置项,这个选项使得网关服务从配置中心拉取配置中心中其他服务的配置,用于转发。

主类注解

@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

如果使用 spring-cloud-starter-gateway 依赖,不需要添加网关相关的注解,上面的注解是用于 spring boot 和服务发现/注册的。

路由配置

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          
      routes:
        - id: cloud-order-service
          # lb表示负载均衡
          uri: lb://cloud-order-service
          predicates:
            # 路径匹配,所有order的请求都转发到cloud-order-service
            - Path=/service-path/**

id 是发现中心中服务的名称,对应配置中的 spring.application.name ,uri:表示这个服务的网关地址,即向网关访问这个地址请求将发送给相应的服务(lb 代表负载均衡,这里可能涉及到一致性会话的问题,提一下不展开),predicates 中可以定义断言规则,最常见的是设置请求服务时的访问路径。

最终请求

protocol://gatewayhost: port/gateway-target-path/

将发送到

protocol://servicehost: serviceport/service-path/gateway-target-path 上。

此外,

spring-cloud-gatewy 还能借助丰富的断言规则和自定义的 filter 实现诸多基于统一出/入口的功能。