背景
近几年,随着有赞用户的迅速增长和业务的快速发展,对业务开发人员要求越来越高,一方面要求为用户提供稳定的服务,一方面要求进行快速业务迭代。然而,随着公司业务复杂度和服务化整体规模的增长,单个业务功能涉及的微服务接口数、服务化调用链路长度都在迅速增加,业务的回归测试越来越难以覆盖到所有的调用链路和业务逻辑,通过仅在测试环境进行业务测试的方式来保证系统稳定性的难度越来越高。
基于系统稳定性和快速业务迭代的综合考虑,业务应用开发团队采取了新版本服务灰度上线的方式,即新版本服务并非全量发布到线上环境,而是发布少数几个实例进行灰度验证,没有问题后再全量发布。在部分核心服务进行接口升级和逻辑迁移时,还会通过在业务逻辑代码中增加黑白名单或者流量百分比控制的方式,逐步将旧版本接口实现迁移至新版本接口实现。该方式较好地权衡了服务稳定性和业务迭代效率,但仍存在以下问题:
为解决以上痛点,有赞基础保障团队于 2018 年开始立项实现流量控制系统以及灰度发布产品,主要目标如下:
一、流量控制系统
1.1 协议
实现灰度发布产品,首先需要实现底层的流量控制系统。而实现流量控制系统,首先面临问题是如何确定流量控制协议。虽然我们最初的目标是为了支持灰度发布,但我们的系统设计必须充分考虑未来的可扩展性,除了流量路由,还期望支持熔断、限流、错误恢复等服务治理能力。我们在进行方案选型时,有如下几个目标:
根据上面的设计目标以及充分的调研,最终确定使用业界广泛关注的 Service Mesh 框架 Istio 的流量控制协议。实际上,Istio 使用 Envoy 作为>
需要额外说明的是,Istio 使用的路由控制协议,七层协议功能最完备的是 Http 路由协议,而我们内部广泛使用的是 RPC 协议:Java 应用之间使用 Dubbo 协议,跨语言调用使用 Nova 协议(自研),需要进行相应的适配。而值得庆幸的是,Dubbo 和 Nova RPC 调用协议的语意,在整体上与 Http 请求有一定的相似性,适配起来比较方便,如 service(interface)与 method 可对应 Http 的 path,attachment(metadata)可以对应 Http 的 header。此外,原始的 Istio 路由控制协议语义丰富而复杂,主要为了多维度的控制 Http 请求的路由、超时重试、限流、熔断等。我们短期内不需要支持完整的协议,仅支持经过微小适配后的协议子集即可,保留后续对其扩展的能力。
协议的详细细节我们在这里不再展开,具体可参考 Istio 和 Envoy 的文档。这里以一个抽象简化的示例来介绍简单描述一下流量控制协议(目前主要是路由控制,也称为路由规则)。示例如下:
"name":"java-demo-rule",
"domains":[
"java-demo"
"routes":[
"headers":[
"name":"userid",
"value_match":"123"
"cluster":"java-demo|version=v2"
"weighted_clusters":{
"clusters":[
"name":"java-demo|version=v2",
"weight":10
"name":"java-demo|version=v1",
"weight":90
复制代码
上述规则描述的是 java-demo 应用的规则,有两条规则。每个规则主要分为两部分:一部分描述请求匹配条件,请求匹配条件包括请求 path、请求 header 等,匹配方式包括等值匹配、正则匹配、范围匹配、列表匹配等;另一部分描述请求目标集群,目标集群可以是一个集群,也可以是带权重的多个集群;其中请求匹配条件可省略,即匹配所有请求。上述示例描述的是,当请求中的 header 字段“userid”值完全等于“123”的时候,路由到携带标签“version=v2”的实例上;其他请求 10%路由到携带标签“version=v1”的实例上,90%路由到携带标签“version=v1”的实例上。实例携带什么样的标签,是发布系统控制的,实例启动的时候将标签信息同其它服务元数据一起写入服务注册中心,消费者通过服务发现就知道了每个实例及其标签信息。当有多条规则的时候,按顺序匹配,最先匹配的规则生效。
1.2 架构
有赞的流量控制系统涉及到多个基础组件:Http 统一接入网关 Nginx、服务化代理 Tether、服务化框架 Dubbo、流量控制中心 Istio Pilot、运维管理系统 Ops 等。核心架构如下所示:
所有流量控制相关的产品入口都是集成在 Ops 运维管理系统的,Ops 将产品层面的上层控制输入转化为底层路由规则推送到 Nginx 和 Istio Pilot。Nginx 独自管理路由规则;Istio Pilot 为内部服务化系统管理与下发路由规则,规则以 CRD 的方式存储在 K8s 中。服务化基础组件包含 Tether 和 Dubbo,都是周期性地通过轮询的方式从 Istio Pilot 拉取路由规则(后续将升级到 Envoy v2 API,通过 gRPC Push 机制更新)。Tether 是一个服务化代理,是有赞自研的 Service Mesh 框架 Sidecar 组件(参见有赞技术博客)。当 Http 请求到达统一接入层 Nginx 时,Nginx 将根据请求特征、路由规则、Upstream 信息进行匹配,决定将 Http 请求转发到哪些 Upstream 实例,即 Node 应用实例;Node 应用收到 Http 请求后,向后端服务化系统发起 Http Rest RPC 请求,请求发送到本机部署的 Tether,Tether 首先将 Http Rest RPC 请求转化为 Dubbo RPC 请求,然后根据请求特征、路由规则、服务发现信息进行匹配与路由,将 Dubbo 请求转发到匹配的后端 Dubbo 实例。而 Dubbo 接收到请求向其他后端发起请求时,同样根据请求特征、路由规则、服务发现信息进行匹配与路由。其中请求特征分为全局特征与局部特征,全局特征会随着整个请求链路透传,由各个基础组件共同支持;而局部特征仅存在于一个请求跨度中。有赞的流量基本都是与用户所访问的商家店铺绑定的,因此,我们内部将店铺 id 作为一个全局特征,会在整个请求链路中透传,店铺 id 也是在有赞进行流量控制的一个核心维度。
至此,我们大概阐述了有赞底层流量控制系统的整体设计和架构。在此流量控制系统的基础上,我们在上层产品化出了有赞的“灰度发布”和“蓝绿发布”系统,在保证业务快速迭代的同时,最大程度的维护系统的稳定性。
二、灰度发布
2.1 什么是灰度发布
灰度发布,是在生产环境稳定集群之外,额外部署一个小规模的灰度集群,并通过流量控制,引入部分流量到灰度集群,进行生产全量发布前的灰度验证。如果验证失败,可立刻将所有流量切回至稳定集群,取消灰度发布过程;如果验证成功,则将新版本进行全量发布升级至生产环境稳定集群,完成灰度发布过程。
如上图所示,应用 A 是 consumer,应用 B 是 provider,应用 B 稳定集群的版本为 v1,有 3 个实例。(a)应用 B 额外部署了一个灰度集群,版本为 v2,有 1 个实例。(b)通过流量控制,将 5%的请求路由到应用 B 灰度集群,进行小规模验证。(c)如果在灰度验证中发现了故障,则通过流量控制,将全部流量切回应用 B 稳定集群,实现快速回滚。(d)如果灰度验证没有发现任何故障,则应用 B 的灰度发布过程完成,并进行全量发布,稳定集群版本所有实例升级至 v2。
2.2 发布流程
下面从产品层面介绍一下有赞灰度发布的流程,包括:灰度发布开始、灰度初始化、灰度验证、灰度取消或全量发布、灰度发布结束。
(1) 灰度发布开始。用户从 Ops 管理系统,进入应用的发布页面,选择发布类型“灰度发布”,开始灰度发布。
(2) 灰度初始化。底层运维系统进行灰度集群部署,灰度实例上线时服务注册会注册标签“canary=true”(稳定集群默认为“canary=false”),此时灰度实例的上线不会接收到任何流量,因为我们在底层流量控制系统做了特殊保护,在没有路由规则的情况下不会把流量路由到灰度实例,避免灰度集群在初始化没有完成或灰度集群全部挂掉的情况下请求处理失败。目前,我们灰度集群部署的规模为稳定集群的 10%,即若稳定集群有 100 个实例,灰度集群部署 10 个实例。
(3) 灰度验证。初始化完成后,用户可推送灰度路由规则,将部分请求路由至灰度集群进行验证。目前,支持两种灰度规则:店铺列表和请求百分比。店铺列表规则是指如果对应的请求访问的是列表中的店铺,则请求路由至灰度集群。店铺 id 是有赞内部所有请求 header 或 metadata 中都会携带的一个字段,我们一般会选择一些内部测试店铺或小流量店铺进行灰度验证。请求百分比规则是指按一定百分比概率将请求路由至灰度集群。由于灰度集群部署规模仅为稳定集群规模 10%,因此,请求百分比规则最大也只能设置为 10%。如果灰度验证失败,进入流程 4;成功则进入流程 5。
店铺列表灰度规则示例如下:
"name":"java-demo-rule",
"domains":[
"java-demo"
"routes":[
"headers":[
"name":"shopid",
"list_match":[
"cluster":"java-demo|canary=true"
"cluster":"java-demo|canary=false"
复制代码
(说明:店铺 id 为 123 和 456 的请求,进入灰度集群;其余请求保留在稳定集群)
百分比灰度规则示例如下:
"name":"java-demo-rule",
"domains":[
"java-demo"
"routes":[
"weighted_clusters":{
"clusters":[
"name":"java-demo|canary=true",
"weight":10
"name":"java-demo|canary=false",
"weight":90
复制代码
(说明:随机 10%请求进入灰度版本服务集群)
(4) 灰度取消。如果在灰度验证过程中发现了问题,灰度取消可秒级将全部流量切回稳定集群,具体包括两个步骤:删除路由规则和灰度集群下线。当然,用户也可先通过修改规则的方式(如店铺列表置空或请求百分比置 0),先将流量切回稳定集群,然后保留现场,方便进行问题排查。
(5) 全量发布。如果灰度验证没有发现问题,那么就可以进行新版本的全量发布了。包括:稳定集群全量发布、删除灰度路由规则和灰度集群下线。
(6) 灰度发布结束。
上述流程描述可简化成如下流程图:
三、蓝绿发布
3.1 什么是蓝绿发布
蓝绿发布,是在生产环境稳定集群之外,额外部署一个与稳定集群规模相同的新集群,并通过流量控制,逐步引入流量至新集群直至 100%,原先稳定集群将与新集群同时保持在线一段时间,期间发生任何异常,可立刻将所有流量切回至原稳定集群,实现快速回滚。直到全部验证成功后,下线老的稳定集群,新集群成为新的稳定集群。
如上图所示,应用 A 是 consumer,应用 B 是 provider,应用 B 稳定集群的版本为 v1,有 3 个实例。(a)应用 B 额外部署了一个新集群,版本为 v2,同样有 3 个实例。(b)通过流量控制,将所有请求路由到应用 B 新集群,进行全量验证,同时原稳定集群继续保持在线。(c)如果在验证中发现了故障,则通过流量控制,将全部流量切回应用 B 原稳定集群,实现快速回滚。(d)如果验证没有发现任何故障,则应用 B 的蓝绿发布完成,v2 版本集群成为新的稳定集群。
3.2 为什么还需要蓝绿发布
有了灰度发布之后,为什么还需要蓝绿发布呢?主要有如下几点考虑:
对于上面几个问题,使用蓝绿发布系统都可以较好地解决:
理论上,灰度发布的需求都可以用蓝绿发布解决,而且可以更好地解决。但是我们不能完全用蓝绿发布替代灰度发布,因为应用蓝绿发布期间两个集群同时在线,占用平时两倍的服务器资源,成本很高,而灰度发布只需要很小一部分服务器资源就可以验证大部分问题。
3.3 发布流程
下面从产品层面介绍一下有赞蓝绿发布的流程,包括:蓝绿发布开始、蓝绿初始化、蓝绿验证、蓝绿取消或完成上线、蓝绿发布结束。
(1) 蓝绿发布开始。用户从 Ops 管理系统,进入应用的发布页面,选择发布类型“蓝绿发布”,开始蓝绿发布。
(2)蓝绿初始化。首先推送路由规则,控制全部流量在老集群,保证新集群部署启动期间不会接收任何流量。我们通过实例的 BlueGreenVersion 标签来识别是蓝集群还是绿集群。如果老集群 BlueGreenVersion 为 blue,则新集群 BlueGreenVersion 为 green;反之亦然。该标签是发布系统在发布时注入实例环境变量中,实例启动时注册到注册中心的。推送完规则后,就可以部署新集群了。
假设老集群 BlueGreenVersion 为 blue,则推送的规则示例如下:
"name":"java-demo-rule",
"domains":[
"java-demo"
"routes":[
"cluster":"java-demo|BlueGreenVersion=blue"
复制代码
(3) 蓝绿验证。初始化完成后,用户可推送路由规则,将部分请求流量或全部流量路由至新集群进行验证。目前,支持两种蓝绿规则:店铺列表和请求百分比。同灰度规则类似,不再赘述。验证过程中如果没有问题,可以不断将流量迁移至新集群,直至所有流量都在新集群。如果蓝绿验证失败,进入流程 4;成功则进入流程 5。
假设老集群 BluGreenVersion 为 blue,店铺列表蓝绿规则示例如下:
"name":"java-demo-rule",
"domains":[
"java-demo"
"routes":[
"headers":[
"name":"shopid",
"list_match":[
"cluster":"java-demo|BlueGreenVersion=green"
"cluster":"java-demo|BlueGreenVersion=blue"
复制代码
(说明:店铺 id 为 123 和 456 的请求,进入新版本服务集群;其余请求保留在旧版本服务集群)
百分比蓝绿规则示例如下:
"name":"java-demo-rule",
"domains":[
"java-demo"
"routes":[
"weighted_clusters":{
"clusters":[
"name":"java-demo|BlueGreenVersion=green",
"weight":50
"name":"java-demo|BlueGreenVersion=blue",
"weight":50
复制代码
(说明:随机 50%请求进入新版本集群)
(4) 蓝绿取消。如果在蓝绿验证过程中发现了问题,蓝绿取消可秒级将全部流量切回老集群,具体包括三个步骤:推送路由规则控制所有流量到老集群、新集群下线和删除路由规则。当然,用户也可先通过修改规则的方式(如店铺列表置空或请求百分比置 0),先将流量切回老集群,然后保留现场,方便进行问题排查。
(5) 完成上线。如果蓝绿验证没有发现问题,那么就可以完成蓝绿发布上线了。只有当全部流量在新集群时才能操作完成上线。包括两个步骤,老集群下线和删除路由规则。完成上线后,如果再有需要回滚,那只能走普通的回滚流程了。
(6) 蓝绿发布结束。
上述描述可简化成如下流程图:
四、可观测性与可运维性
一个好的系统或产品,仅实现其基本功能是远远不够的,可观测、易运维也是必不可少的。有赞在灰度与蓝绿发布产品中,可观测性与可运维性方面也做了不少工作。主要包括发布应用监控、发布事件通知、全局发布状态以及周期统计报表。下面介绍一下有赞蓝绿发布产品在可观测性与可运维性方面的一些工作。
4.1 发布应用指标监控
蓝绿发布期间新老两个集群运行两个应用版本,需要能够对新老集群的出、入流量的 QPS、耗时、失败率等指标进行监控,通过对比新旧两个版本集群的实时监控数据,用户可以快速的发现问题。如下图所示:
4.2 发布事件通知
发布事件通知是为了在发布过程中的一些重点事件发生时,及时的告知到对应的运维或开发人员,比如蓝绿发布开始、蓝绿发布取消、蓝绿发布完成上线、蓝绿发布验证过长等事件。这些事件通知通过企业办公 IM 发送到对应的人员。如下图所示:
4.3 全局发布状态
全局发布状态主要是通过一些关键指标实时反映蓝绿发布系统的整体使用状况,可以方便蓝绿发布运维人员根据实时状态来做出一些人为的管理和干预。如下图所示:
4.4 周期统计报表
统计报表主要是统计一个周期内的一些指标,方便全局视角的观察和分析,不断改进蓝绿发布系统。对于某个周期支持当前周期的统计与上一周期的统计的同时显示,方便进行对比,周期可以是周、月、季度、年。如下图所示:
五、未来规划
本文主要介绍了有赞的灰度发布和蓝绿发布实践,目前只局限于单应用的控制。然而,新产品或项目的上线往往会涉及到多个应用,这些应用一般会同时发布,如果各个应用独立进行灰度或蓝绿发布、独立验证,那么发布不仅非常费时费力,而且在出现问题时也难以做到快速回滚。如下图所示:应用 A、B、D 共同参与了项目 001 的新功能开发,新版本为 v2,线上稳定版本为 v1,这 3 个应用需要同时上线,我们期待通过一个规则来控制进入 A-v2、B-v2、D-v2 的流量,且经过 A-v2 的流量必然到 B-v2、D-v2,经过 B-v2 的流量必然到 D-v2,这样会可以极大地降低涉及多个应用的复杂项目整体上线的难度,保障项目上线的稳定性。未来我们会实现这方面的需求。
另外,有赞有不少业务依赖消息队列,当前如果请求链路中有消息队列,由于消息队列的一些限制,我们目前还没能够实现对消息消费的灵活控制,这一部分在一定程度上会影响整体的流量控制,未来我们也期望在这方面做一些改进。
本文所有内容结束。感谢您的阅读。