如何解决 Spring Cloud 下测试环境路由问题

9,910 阅读17分钟

作者

张乐

开源社区 Spring Cloud Tencent PMC,开源社区 Apollo PMC,腾讯云技术专家、 专注于微服务中间件研发包括:注册中心、配置中心、服务治理中心等。

张皓天

开源社区 Spring Cloud Tencent PMC,腾讯云高级研发工程师,专注于微服务架构领域,深耕服务治理能力与其他相关中间件研发。

前言

Spring Cloud Tencent 微服务开发框架自六月底正式对外宣发后,受到了许多开发者非常火热的关注。不到一个月时间, Github Star 数就已突破 2000,超过 1000 名开发者加入我们的社群,并有 20 多个开发者参与贡献项目代码,项目的热门程度极大地超出我们的预期,同时也验证了我们在最初宣发文章里的观点:Spring Boot + Spring Cloud 仍是当前使用相当广泛开发框架

在这一个月时间里,Spring Cloud Tencent 的关注者们最关心的问题就是,Spring Cloud Tencent 后续规划是什么?

在过去的一段时间,我们的主要精力聚焦在微服务领域最基本的服务治理原子能力,例如服务发现、动态配置、限流熔断、路由等。Spring Cloud 其它套件基本上也局限于这些基础能力。但是企业真正在实践 Spring Cloud 过程中,发现针对自身具体的业务场景,这些原子能力并不能直接提供解决方案,往往需要做二次开发、定制等。例如定制 Spring Cloud Gateway 的 Filter、增强 Feign、支持各种复杂的服务路由场景等。因此,开箱即用的业务通用解决方案对企业来说更具有价值。

综上所述, Spring Cloud Tencent 后续重要的规划之一就是在不断夯实服务治理原子能力的基础上,提供开箱即用的业务通用解决方案,从工具到方案的升级。

为此 Spring Cloud Tencent 新增了 spring-cloud-tencent-plugin-starts 模块,在此模块下实现不同业务场景的解决方案。现阶段我们主要聚焦在精细化流量治理能力场景化方案上,并按照开发流程拆分为三个阶段:

  1. 开发测试阶段的多测试环境场景;
  2. 发布阶段的金丝雀发布、蓝绿发布、全链路灰度等;
  3. 生产运行阶段的单元化、AB测试等。

本期我们主要聊聊开发测试阶段的多测试环境场景实战,详细介绍 Spring Cloud Tencent 实现多测试环境场景的方案。

一、基础知识

1.1 什么是测试环境路由

在实际的开发过程中,一个微服务架构系统下的不同微服务可能是由多个团队进行开发与维护的,每个团队只需关注所属的一个或多个微服务,而各个团队维护的微服务之间可能存在相互调用关系。如果一个团队在开发其所属的微服务,调试的时候需要验证完整的微服务调用链路。此时需要依赖其他团队的微服务,如何部署开发联调环境就会遇到以下问题:

  1. 如果所有团队都使用同一套开发联调环境,那么一个团队的测试微服务实例无法正常运行时,会影响其他依赖该微服务的应用也无法正常运行。
  2. 如果每个团队有单独的一套开发联调环境,那么每个团队不仅需要维护自己环境的微服务应用,还需要维护其他团队环境的自身所属微服务应用,效率大大降低。同时,每个团队都需要部署完整的一套微服务架构应用,成本也随着团队数的增加而大大上升。

此时可以使用测试环境路由的架构来帮助部署一套运维简单且成本较低开发联调环境。测试环境路由是一种基于服务路由的环境治理策略,核心是维护一个稳定的基线环境作为基础环境,测试环境仅需要部署需要变更的微服务。多测试环境有两个基础概念,如下所示:

  1. 基线环境(Baseline Environment): 完整稳定的基础环境,是作为同类型下其他环境流量通路的一个兜底可用环境,用户应该尽量保证基线环境的完整性、稳定性。
  2. 测试环境(Feature Environment): 一种临时环境,仅可能为开发/测试环境类型,测试环境不需要部署全链路完整的服务,而是仅部署本次有变更的服务,其他服务通过服务路由的方式复用基线环境服务资源。

部署完成多测试环境后,开发者可以通过一定的路由规则方式,将测试请求打到不同的测试环境,如果测试环境没有相应的微服务处理链路上的请求,那么会降级到基线环境处理。因此,开发者需要将开发新测试的微服务部署到对应的测试环境,而不需要更新或不属于开发者管理的微服务则复用基线环境的服务,完成对应测试环境的测试。

虽然测试环境路由是一个相对成熟的开发测试环境解决方案,但是能够开箱即用的生产开发框架却不多,往往需要开发者二次开发相应的功能。因此需要一个相对完善的解决方案来帮助实现测试环境路由,简化开发难度并提升开发效率。

1.2 服务路由

服务路由模型

服务路由抽象出最简化的模型如下图所示,解决的是 “哪些请求转发到哪些实例” 的问题。细化来看,包含三个问题:

  1. 如何精确标识请求?
  2. 如何精确标识实例?
  3. 如何转发?

(图:服务路由模型示意图)

在流量的微观世界里,统一通过标签(属性)来标识一个实体,例如请求有来源调用服务、目标环境标签等,服务实例则有版本号、实例分组、环境分组等标签。服务路由则是将满足标签匹配条件的请求转发到满足匹配条件的服务实例。所以服务路由的模型可拆解出如下的的专业术语:

  • 服务实例染色 (为服务实例设置标签信息)
  • 量染色(为请求设置标签信息)
  • 服务路由(根据路由策略,把请求转发到目标实例)

服务实例标签如何传递到调用方

服务实例注册到注册中心时,会带上标签信息。服务调用方从注册中心获取到服务实例信息就包含了实例的标签信息。

标签全链路透传

有一类请求标签数据需要在业务响应链路上一直传递,例如全链路追踪里的 TraceId 、测试环境路由的 FeatureEnv 标签等。

服务路由和负载均衡的区别

服务路由和负载均衡都是解决选择服务实例的问题。区别在于服务路由是从全量的服务实例中挑选出一批满足路由规则的服务实例,而负载均衡则是从路由匹配之后的服务实例列表中挑选出一个适合处理请求的实例。

二、测试环境路由实现原理

2.1 方案总览

测试环境路由的样例实现以下图为例,一共有两个测试环境以及一个基线环境。流量从端到端会依次经过以下组件:App -> 网关 -> 用户中心 -> 积分中心 -> 活动中心。

(图:测试环境路由示意图)

根据上一节服务路由章节所述,为了达到测试环境路由的能力,开发工作需要做三件事情:

  1. 服务实例染色(标识实例属于哪个测试环境)

  2. 流量染色(标识请求应该被转发到哪个测试环境)

  3. 服务路由

    a 网关根据请求的目标测试环境标签转发到对应的目标测试环境的用户中心。

    b 服务调用时,优先转发到同测试环境下的目标服务实例,如果同测试环境下没有服务实例则转发到基线环境。

以下三小节,将会详细介绍这三部分的原理。

2.2 服务实例染色

在多测试环境的场景中,需要对每个测试环境部署的实例进行区分,因此需要在实例上打<featureenv=测试环境名> 的标签。Spring Cloud Tencent 一共支持三种服务实例染色方式。

方式一:配置文件

在 Spring Boot 的 application.yml 配置文件里配置以下内容即可实现染色:

spring:
  cloud:
    tencent:
      metadata:
        content:
          idc: shanghai
          env: f1

Spring Cloud Tencent 应用在启动时,读取配置文件并解析出 idc=shanghai 和 env=f1 标签信息。

如果以上配置文件放在项目源码里,要实现不同的实例具有不同的标签值则需要打不同包。可以通过以下两种方式实现同一个运行包设置不同的标签值:

  1. 通过 -D 启动参数覆盖,例如 -Dspring.cloud.tencent.metadata.content.idc=guangzhou
  2. 通过 Spring Boot 标准方式,把 application.yml 外挂本地磁盘上

方式二:环境变量

环境变量在容器场景下非常方便,Spring Cloud Tencent 约定了前缀为 SCT_METADATA_CONTENT_ 的环境变量为实例的标签信息,例如:

  • SCT_METADATA_CONTENT_IDC=shanghai
  • SCT_METADATA_CONTENT_ENV=f1

Spring Cloud Tencent 应用在启动时,自动会读取环境变量并解析出 IDC=shanghai 和 ENV=f1 标签信息。

方式三:自定义实现 SPI

前面两种方式为 Spring Cloud Tencent 内置的方式,但是不一定符合每个生产项目的规范,因此 Spring Cloud Tencent 还提供了一种允许开发者自定义标签 Provider 的方式。例如以下两种实践场景:

  1. 把实例标签放到机器上的某一个配置文件里,例如 /etc/metadata。
  2. 应用启动时,调用公司的 CMDB 接口获取元信息。

这种场景下,只要实现 InstanceMetadataProvider SPI 扩展即可。

2.3 流量染色

流量染色即为每个请求打上目标测试环境标签,路由转发时根据请求标签匹配目标服务实例。而流量染色可以分为以下几种方式:

方式一:静态染色

2.2 小节介绍了可以为服务实例设置一系列的标签信息,例如 idc=shanghai、env=f1 等。在有些场景下,期望所有经过当前实例的请求都带上当前实例的标签信息。例如经过 env=f1 的实例的请求都携带 env=f1 的标签信息。

服务实例染色有三种方式,对应的定义哪些标签需要作为请求标签透传到链路上也有三种方式,核心思想就是定义需要全链路传递的标签键值对的键列表。

  1. 通过配置文件的 spring.cloud.tencent.metadata.content.transitive=["idc", "env"] 配置项指定
  2. 通过 SCT_METADATA_CONTENT_TRANSITIVE=IDC,ENV 环境变量指定
  3. 通过实现 InstanceMetadataProvider#getTransitiveMetadataKeys() 方法指定

方式二:动态染色

静态染色是把服务实例的某些标签作为请求标签,服务实例标签相对静态,应用启动后初始化一次之后就不再变更。但是在实际的应用场景下,不同的请求往往需要设置不同的标签信息。此时则需要通过动态染色的能力。

为请求动态染色也非常简单,只需增加以 X-Polaris-Metadata-Transitive- 为前缀的 HTTP 请求头即可,例如:X-Polaris-Metadata-Transitive-featureenv=f1。这样 featureenv=f1 就能够作为请求标签在链路上透传。

方式三:网关流量染色

网关常常作为流量的入口或者中转站。经过网关的请求,可以根据某些染色规则为请求增加标签信息。例如满足请求参数 uid=1000 请求打上 featureenv=f1 标签。

网关流量染色是非常实用的能力,在 Spring Cloud Tencent 里实现了非常灵活基于染色规则的 Spring Cloud Gateway 染色插件。例如以下染色规则可以实现为 uid=1000 的请求打上 featureenv=f1 标签、uid=1001 的请求打上 featureenv=f2 标签。更详细的染色规则,可以参考文档。

{
    "rules":[
        {
            "conditions":[
                {
                    "key":"${http.query.uid}",
                    "values":["1000"],
                    "operation":"EQUALS"
                }
            ],
            "labels":[
                {
                    "key":"featureenv",
                    "value":"f1"
                }
            ]
        },
        {
            "conditions":[
                {
                    "key":"${http.query.uid}",
                    "values":["1001"],
                    "operation":"EQUALS"
                }
            ],
            "labels":[
                {
                    "key":"featureenv",
                    "value":"f2"
                }
            ]
        }
    ]
}

同时 Spring Cloud Tencent 也预留了 TrafficStainer SPI ,用户可以实现自定义流量染色插件。

2.4 Spring Cloud Tencent 路由功能原理

北极星提供了非常完善的服务治理能力,上层的服务框架基于北极星原生 SDK 就能快速实现强大的服务治理能力。Spring Cloud Tencent 就是在北极星的基础上实现了服务路由能力。

北极星服务路由原理

北极星服务路由实现原理并不复杂,如下图所示,从注册中心获取到所有实例信息,再经过一系列的 RouterFilter 插件过滤出满足条件的实例集合。

(图:北极星服务路由执行链)

在多测试环境场景下主要用到了 MetadataRouter (元数据路由)插件,此插件核心能力是根据请求的标签完全匹配服务实例的标签。

例如请求有两个标签 key1=value1和 key2=value2,MetadataRouter 则会筛选出所有实例中包含同时满足 key1=value1 和 key2=value2 的服务实例。在多测试环境场景下,Spring Cloud Tencent 缺省使用 featureenv 标签,通过 featureenv 标签筛选出属于同一个测试环境的服务实例。

Spring Cloud Tencent 服务路由原理

Spring Cloud Tencent 实现路由核心分成两个部分:

  1. 扩展 RestTemplate 、 Feign、SCG 获取请求的标签信息并塞到 RouterContext (路由信息上下文)里。
  2. 扩展 Spring Cloud 负载均衡组件(Hoxton 版本之前为 Ribbon,2020版本之后为 Spring Cloud LoadBalancer),在扩展的实现里调用北极星的服务路由 API 实现服务实例过滤。

扩展部分逻辑较为复杂,感兴趣的读者可以参考 spring-cloud-starter-tencent-polaris-router 模块源码。

三、测试环境路由用户操作指引

在上一节中详细介绍了测试环境路由的实现原理,这一节则详细介绍站在用户的视角需要操作的内容。

通过 Spring Cloud Tencent 实现流量的测试环境路由非常简单,核心包含三步:

  1. 服务增加测试环境路由插件依赖
  2. 部署的实例打上环境标签
  3. 为请求流量打上环境标签

完成以上三个步骤即可。

3.1 添加测试环境路由插件依赖

Spring Cloud Tencent 中的 spring-cloud-tencent-featureenv-plugin 模块闭环了测试环境路由全部能力,所有服务只需要添加该依赖即可引入测试环境路由能力。

3.2 服务实例打上环境标签

spring-cloud-tencent-featureenv-plugin 默认以 featureenv 标签作为匹配标签,用户也可以通过系统内置的 system-feature-env-router-label=custom_feature_env_key 标签来指定测试环境路由使用的标签键。以下三种方式以默认的 featureenv 作为示例。

方式一:配置文件

在服务实例的配置文件中添加配置,如在 bootstrap.yml添加如下所示即可:

spring:
  cloud:
    tencent:
      metadata:
        content:
          featureenv: f1  # f1 替换为测试环境名称

方式二:环境变量

在服务实例所在的操作系统中添加环境变量也可进行打标,例如:SCT_METADATA_CONTENT_featureenv=f1

方式三:SPI 方式

自定义实现 InstanceMetadataProvider#getMetadata() 方法的返回值里里包含 featureenv 即可。

基线环境标签值

注意,基线环境部署的服务实例不需要设置 featureenv 标签,表明其不属于任何测试环境,才可在请求没有匹配到对应测试环境的时候,匹配到基线环境。

3.3 流量染色

方式一:客户端染色 (推荐)

如下图所示,在客户端发出的 HTTP 请求里,新增 X-Polaris-Metadata-Transitive-featureenv=f1 请求头即可实现染色。该方式是让开发者在请求创建的时候根据业务逻辑进行流量染色。

(图:客户端染色示意图)

方式二:网关动态染色(推荐)

动态染色是开发者配置一定的染色规则,让流量经过网关时自动染色,使用起来相当方便。例如把 uid=1 用户的请求都转发到 f1 环境,把 uid=0 用户的请求都转发到 f2 环境。只需要配置一条染色规则即可实现。

(图:网关动态染色示意图)

Spring Cloud Tencent 通过实现 Spring Cloud Gateway 的 GlobalFilter 来实现流量染色插件,开发者只需要添加 spring-cloud-tencent-gateway-plugin 依赖,并在配置文件中打开染色插件开关(spring.cloud.tencent.plugin.scg.staining.enabled=true)即可引入流量染色能力。

方式三:网关静态染色

往请求中加入固定的 Header 是网关最常见的插件,如下图所示。可以在每个环境部署一个网关,所有经过网关的请求都增加 X-Polaris-Metadata-Transitive-featureenv=f1 请求头即可。此种方式需要每个环境部署网关,成本高,所以使用频率相对较低。

(图:网关静态染色示意图)

完成以上操作步骤即可实现测试环境路由,读者可运行 Spring Cloud Tencent 下 polaris-router-featureenv-example 完整体验。

四、总结

测试环境路由在微服务架构系统的开发阶段是非常实用的功能,能够大大降低测试环境的维护成本、资源成本,同时能够极大的提高研发效率。通过操作指引的章节可以看出通过 Spring Cloud Tencent 实现测试环境路由非常简单的,只需要部署的服务实例增加相应的环境标签以及在请求头中增加一个标签即可。

业界常见的测试环境路由实现方案往往需要下发路由规则给链路上的服务,从而实现路由能力。但是通过北极星的元数据路由能力,整个方案里无需下发任何路由规则,只需要在实例设置相应的标签信息即可,操作成本非常低。

如果项目刚好使用 Spring Cloud Gateway 作为网关,那么集成 Spring Cloud Tencent 里的网关染色插件能够进一步降低流量染色成本,客户端无需做任何事情,只需要配置网关染色规则即可实现流量染色。

目前 Spring Cloud Tencent 主要实现了微服务之间调用流量的测试环境路由能力,不涉及消息队列、任务调度的测试环境路由能力。

五、欢迎共建

如果您所在项目正在使用 Spring Cloud 框架,并且

  • 沉淀出了非常实用的通用插件能力和场景化解决方案
  • 目前正遇到一些落地难题
  • 对 Spring Cloud Tencent 项目感兴趣

非常欢迎您跟我们一起打磨更多实用且通用的能力,共建满足各类实际生产场景使用的微服务开发框架。您的一个建议、Issue、Pull Request 甚至只是一个小小的 Star 都是对 Spring Cloud Tencent 社区极大的支持。

Github 地址:github.com/Tencent/spr…