跳至主要內容

网关路由

钝悟...大约 11 分钟分布式分布式调度分布式服务治理调度路由网关

网关路由

什么是网关

网关的首要职责就是:作为统一的出口,对外提供服务;将外部访问网关地址的流量,根据适当的规则路由到内部集群中正确的服务节点之上。因此,微服务中的网关,也常被称为“服务网关”或“API 网关”。

网关首先应该是个路由器,在满足此前提的基础上,网关还可以根据需要作为流量过滤器来使用,提供某些额外的可选的功能。网关常见的能力如下:

  • 动态路由:根据请求路由到对应的服务上去,如果服务不可用还会有重试机制
  • 负载均衡:多服务器提供同一种服务,网关会从配置中心拉取各服务注册信息,然后将请求负载均衡风阀到这些服务器进行处理
  • 流量控制:限制并发请求的流量,避免内部系统受到冲击
  • 安全认证:网关对相关权限验证、脱敏和流量清洗、签名和黑名单功能
  • 熔断降级:当服务不可用或者访问量过大,网关可以将请求做降级,将流量打到其他服务器或者做其他处理,提示用户暂时不可用
  • 灰度发布:先进行小部分服务器升级,通过网关将少量的服务路由到已升级的服务器用来测试服务是否正常,大部分请求依旧在老版本服务器上处理
  • 日志服务:服务访问情况监控和统计报表,请求的吞吐量、并发数、流量监控、性能监控和日常告警等

简单来说:

网关 = 路由器(基础职能) + 过滤器(可选职能)

什么是服务路由

服务路由是指通过一定的规则从集群中选择合适的节点。

负载均衡的作用和服务路由的功能看上去很近似,二者有什么区别呢?

负载均衡的目标是提供服务分发而不是解决路由问题,常见的静态、动态负载均衡算法也无法实现精细化的路由管理,但是负载均衡也可以简单看做是路由方案的一种。

服务路由通常用于以下场景,目的在于实现流量隔离:

  • 分组调用:一般来讲,为了保证服务的高可用性,实现异地多活的需求,一个服务往往不止部署在一个数据中心,而且出于节省成本等考虑,有些业务可能不仅在私有机房部署,还会采用公有云部署,甚至采用多家公有云部署。服务节点也会按照不同的数据中心分成不同的分组,这时对于服务消费者来说,选择哪一个分组调用,就必须有相应的路由规则。
  • 蓝绿发布:蓝绿发布场景中,一共有两套服务群组:一套是提供旧版功能的服务群组,标记为绿色;另一套是提供新版功能的服务群组,标记为蓝色。两套服务群组都是功能完善的,并且正在运行的系统,只是服务版本和访问流量不同。新版群组(蓝色)通常是为了做内部测试、验收,不对外部用户暴露。
    • 如果新版群组(蓝色)运行稳定,并测试、验收通过后,则通过服务路由、负载均衡等手段逐步将外部用户流量导向新版群组(蓝色)。
    • 如果新版群组(蓝色)运行不稳定,或测试、验收不通过,则排查、解决问题后,再继续测试、验收。
  • 灰度发布:灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行 A/B 测试,即让一部分用户使用特性 A,一部分用户使用特性 B:如果用户对 B 没有什么反对意见,那么逐步扩大发布范围,直到把所有用户都迁移到 B 上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。要支持灰度发布,就要求服务能够根据一定的规则,将流量隔离。
  • 流量切换:在业务线上运行过程中,经常会遇到一些不可抗力因素导致业务故障,比如某个机房的光缆被挖断,或者发生着火等事故导致整个机房的服务都不可用。这个时候就需要按照某个指令,能够把原来调用这个机房服务的流量切换到其他正常的机房。
  • 线下测试联调:线下测试时,可能会缺少相应环境。可以将测试应用注册到线上,然后开启路由规则,在本地进行测试。
  • 读写分离。对于大多数互联网业务来说都是读多写少,所以在进行服务部署的时候,可以把读写分开部署,所有写接口可以部署在一起,而读接口部署在另外的节点上。

服务路由的规则

条件路由

条件路由是基于条件表达式的路由规则。各个 RPC 框架的条件路由表达式各不相同。

我们不妨参考一下 Dubbo 的条件路由。Dubbo 的条件路由有两种配置粒度,如下:

  • 应用粒度

    # app1的消费者只能消费所有端口为20880的服务实例
    # app2的消费者只能消费所有端口为20881的服务实例
    ---
    scope: application
    force: true
    runtime: true
    enabled: true
    key: governance-conditionrouter-consumer
    conditions:
      - application=app1 => address=*:20880
      - application=app2 => address=*:20881
    
  • 服务粒度

    # DemoService的sayHello方法只能消费所有端口为20880的服务实例
    # DemoService的sayHi方法只能消费所有端口为20881的服务实例
    ---
    scope: service
    force: true
    runtime: true
    enabled: true
    key: org.apache.dubbo.samples.governance.api.DemoService
    conditions:
      - method=sayHello => address=*:20880
      - method=sayHi => address=*:20881
    

其中,conditions 定义具体的路由规则内容。conditions 部分是规则的主体,由 1 到任意多条规则组成。详见:Dubbo 路由规则open in new window

Dubbo 的条件路由规则由两个条件组成,分别用于对服务消费者和提供者进行匹配。条件路由规则的格式如下:

[服务消费者匹配条件] => [服务提供者匹配条件]
  • 服务消费者匹配条件:所有参数和消费者的 URL 进行对比,当消费者满足匹配条件时,对该消费者执行后面的过滤规则。
  • 服务提供者匹配条件:所有参数和提供者的 URL 进行对比,消费者最终只拿到过滤后的地址列表。

condition:// 代表了这是一段用条件表达式编写的路由规则,下面是一个条件路由规则示例:

host = 10.20.153.10 => host = 10.20.153.11

该条规则表示 IP 为 10.20.153.10 的服务消费者只可调用 IP 为 10.20.153.11 机器上的服务,不可调用其他机器上的服务。

下面列举一些 Dubbo 条件路由的典型应用场景:

  • 如果服务消费者的匹配条件为空,就表示所有的服务消费者都可以访问,就像下面的表达式一样。
=> host != 10.20.153.11
  • 如果服务提供者的过滤条件为空,就表示禁止所有的服务消费者访问,就像下面的表达式一样。
host = 10.20.153.10 =>
  • 排除某个服务节点
=> host != 172.22.3.91
  • 白名单
register.ip != 10.20.153.10,10.20.153.11 =>
  • 黑名单
register.ip = 10.20.153.10,10.20.153.11 =>
  • 只暴露部分机器节点
=> host = 172.22.3.1*,172.22.3.2*
  • 为重要应用提供额外的机器节点
application != kylin => host != 172.22.3.95,172.22.3.96
  • 读写分离
method = find*,list*,get*,is* => host = 172.22.3.94,172.22.3.95,172.22.3.96
method != find*,list*,get*,is* => host = 172.22.3.97,172.22.3.98
  • 前后台分离
application = bops => host = 172.22.3.91,172.22.3.92,172.22.3.93
application != bops => host = 172.22.3.94,172.22.3.95,172.22.3.96
  • 隔离不同机房网段
host != 172.22.3.* => host != 172.22.3.*
  • 提供者与消费者部署在同集群内,本机只访问本机的服务
=> host = $host

脚本路由

脚本路由是基于脚本语言的路由规则,常用的脚本语言比如 JavaScript、Groovy、JRuby 等。

"script://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule=" + URL.encode("(function route(invokers) { ... } (invokers))")

这里面 script:// 就代表了这是一段脚本语言编写的路由规则,具体规则定义在脚本语言的 route 方法实现里,比如下面这段用 JavaScript 编写的 route() 方法表达的意思是,只有 IP 为 10.20.153.10 的服务消费者可以发起服务调用。

function route(invokers){
  var result = new java.util.ArrayList(invokers.size());
  for(i =0; i < invokers.size(); i ++){
    if("10.20.153.10".equals(invokers.get(i).getUrl().getHost())){
       result.add(invokers.get(i));
    }
  }
  return result;
 } (invokers);

标签路由

标签路由通过将某一个或多个服务的提供者划分到同一个分组,约束流量只在指定分组中流转,从而实现流量隔离的目的,可以作为蓝绿发布、灰度发布等场景的能力基础。

标签主要是指对服务提供者的分组,目前有两种方式可以完成实例分组,分别是动态规则打标静态规则打标。一般,动态规则优先级比静态规则更高,当两种规则同时存在且出现冲突时,将以动态规则为准。

以 Dubbo 的标签路由用法为例

(1)动态规则打标,可随时在服务治理控制台下发标签归组规则

# governance-tagrouter-provider应用增加了两个标签分组tag1和tag2
# tag1包含一个实例 127.0.0.1:20880
# tag2包含一个实例 127.0.0.1:20881
---
  force: false
  runtime: true
  enabled: true
  key: governance-tagrouter-provider
  tags:
    - name: tag1
      addresses: ["127.0.0.1:20880"]
    - name: tag2
      addresses: ["127.0.0.1:20881"]
 ...

(2)静态规则打标

<dubbo:provider tag="tag1"/>

or

<dubbo:service tag="tag1"/>

or

java -jar xxx-provider.jar -Ddubbo.provider.tag={the tag you want, may come from OS ENV}

(3)服务消费者指定标签路由

RpcContext.getContext().setAttachment(Constants.REQUEST_TAG_KEY,"tag1");

请求标签的作用域为每一次 invocation,使用 attachment 来传递请求标签,注意保存在 attachment 中的值将会在一次完整的远程调用中持续传递,得益于这样的特性,我们只需要在起始调用时,通过一行代码的设置,达到标签的持续传递。

路由规则获取方式

路由规则的获取方式主要有三种:

  • 本地静态配置:顾名思义就是路由规则存储在服务消费者本地上。服务消费者发起调用时,从本地固定位置读取路由规则,然后按照路由规则选取一个服务节点发起调用。
  • 配置中心管理:这种方式下,所有的服务消费者都从配置中心获取路由规则,由配置中心来统一管理。
  • 注册中心动态下发:这种方式下,一般是运维人员或者开发人员,通过服务治理平台修改路由规则,服务治理平台调用配置中心接口,把修改后的路由规则持久化到配置中心。因为服务消费者订阅了路由规则的变更,于是就会从配置中心获取最新的路由规则,按照最新的路由规则来执行。

一般来讲,服务路由最好是存储在配置中心,由配置中心来统一管理。这样的话,所有的服务消费者就不需要在本地管理服务路由,因为大部分的服务消费者并不关心服务路由的问题,或者说也不需要去了解其中的细节。通过配置中心,统一给各个服务消费者下发统一的服务路由,节省了沟通和管理成本。

但也不排除某些服务消费者有特定的需求,需要定制自己的路由规则,这个时候就适合通过本地配置来定制。

而动态下发可以理解为一种高级功能,它能够动态地修改路由规则,在某些业务场景下十分有用。比如某个数据中心存在问题,需要把调用这个数据中心的服务消费者都切换到其他数据中心,这时就可以通过动态下发的方式,向配置中心下发一条路由规则,将所有调用这个数据中心的请求都迁移到别的地方。

参考资料

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.7