公司或企业随用户的迅速增长和业务的快速发展,其对外提供的服务系统(微服务)对业务开发人员要求越来越高,一方面要求为用户提供稳定的服务,一方面要求进行快速业务迭代。然而,随着公司业务复杂度和服务化整体规模的增长,单个业务功能涉及的微服务接口数、服务化调用链路长度都在迅速增加,业务的回归测试越来越难以覆盖到所有的调用链路和业务逻辑,通过仅在测试环境进行业务测试的方式来保证系统稳定性的难度越来越高。

基于系统稳定性和快速业务迭代的综合考虑,为保障系统的稳定性,业务应用开发团队需要采取了新版本服务灰度上线的方式,即新版本服务并非全量发布到线上环境,而是发布少数几个实例进行灰度验证,没有问题后再全量发布。在部分核心服务进行接口升级和逻辑迁移时,还会通过在业务逻辑代码中增加黑白名单或者流量百分比控制的方式,逐步将旧版本接口实现迁移至新版本接口实现。该方式较好地权衡了服务稳定性和业务迭代效率,但仍存在以下问:

  • 需要业务开发人员在服务接口中编码大量与业务逻辑无关的黑白名单或流量百分比控制代码,对业务入侵比较大;
  • 调整黑白名单或流量控制百分比,需要业务团队发布代码或者接入动态配置中心,过程复杂、可复用性差、操作灵活性差;
  • 当新版本接口出现波动或异常,通常需要业务团队通过紧急修改代码和紧急发布,来更新黑白名单和流量百分比控制策略,时间较长,业务影响较大。

为解决以上痛点,一般互联网企业都会实现流量控制系统以及灰度发布产品,主要目标如下:

  • 将业务通过黑白名单或者百分比方式控制新、旧版本接口流量的逻辑,整体下沉到服务化基础组件和Http入口Nginx层;
  • 提供完整的流量控制方案,除了上面提到的黑白名单和百分比流量控制策略,大部分企业还将深挖业务需求,提供更丰富的动态流量控制策略;
  • 与运维管理系统打通,实现易于使用和管理的灰度发布及其他服务治理产品;

为实现上述目标,分别详解相关技术点,实现方式:

流量控制系统
协议:
实现灰度发布产品,首先需要实现底层的流量控制系统。而实现流量控制系统,首先面临问题是如何确定流量控制协议。虽然最初的目标是为了支持灰度发布,但系统设计必须充分考虑未来的可扩展性,除了流量路由,还期望支持熔断、限流、错误恢复等服务治理能力,在进行方案选型时,有如下几个目标:

  • 协议应当是完备的,支持各种服务治理功能。包括但不限于:服务熔断、限流、A/B Test等。后续的服务治理功能应当都能够通过该控制协议实现;
  • 可读性好,易于解析。控制协议应该是易读且易于解析的,便于理解和方便中间件实现,尽量不要引入复杂协议或新的编码和序列化方式;
  • 避免重复造轮子,尽量使用业界已经成熟的优秀设计。
    根据上面的设计目标以及充分的调研,最终确定使用业界广泛关注的Service Mesh框架Istio的流量控制协议。实际上,Istio使用Envoy作为data plane,也就是支持了Envoy的API,Envoy已经在业界非常多的实际生产场景中部署使用。最初采用的是Envoy v1 API,其路由控制协议支持json编码,语意丰富且易于解析和阅读(目前广泛使用的是基于gRPC的v2 API,我们也在向v2 API 迁移中)。同时,Istio很好的支持K8s容器化部署,符合自研运维平台容器化的发展趋势。

需要额外说明的是,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":"20212029202003877"
                }
            ],
            "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”值完全等于“20212029202003877”的时候,路由到携带标签“version=v2”的实例上;其他请求10%路由到携带标签“version=v2”的实例上,90%路由到携带标签“version=v1”的实例上。实例携带什么样的标签,是发布系统控制的,实例启动的时候将标签信息同其它服务元数据一起写入服务注册中心,消费者通过服务发现就知道了每个实例及其标签信息。当有多条规则的时候,按顺序匹配,最先匹配的规则生效。

架构
一个流量控制系统涉及到多个基础组件:Http统一接入网关Nginx、服务化代理ProxServics、服务化框架RPC,Dubbo、流量控制中心Istio Pilot、运维管理系统aSass等,核心架构如下所示:

灰度发布
什么是灰度发布
灰度发布,是在生产环境稳定集群之外,额外部署一个小规模的灰度集群,并通过流量控制,引入部分流量到灰度集群,进行生产全量发布前的灰度验证。如果验证失败,可立刻将所有流量切回至稳定集群,取消灰度发布过程;如果验证成功,则将新版本进行全量发布升级至生产环境稳定集群,完成灰度发布过程。
在这里插入图片描述

如上图所示,应用A是consumer,应用B是provider,应用B稳定集群的版本为v1,有3个实例。(a)应用B额外部署了一个灰度集群,版本为v2,有1个实例。(b)通过流量控制,将5%的请求路由到应用B灰度集群,进行小规模验证。(c)如果在灰度验证中发现了故障,则通过流量控制,将全部流量切回应用B稳定集群,实现快速回滚。(d)如果灰度验证没有发现任何故障,则应用B的灰度发布过程完成,并进行全量发布,稳定集群版本所有实例升级至v2。

发布流程
下面从产品层面介绍灰度发布的流程,包括:灰度发布开始、灰度初始化、灰度验证、灰度取消或全量发布、灰度发布结束。

(1) 灰度发布开始。用户从asass运维管理系统,进入应用的发布页面,选择发布类型“灰度发布”,开始灰度发布。

(2) 灰度初始化。底层运维系统进行灰度集群部署,灰度实例上线时服务注册会注册标签“canary=true”(稳定集群默认为“canary=false”),此时灰度实例的上线不会接收到任何流量,因为在底层流量控制系统做了特殊保护,在没有路由规则的情况下不会把流量路由到灰度实例,避免灰度集群在初始化没有完成或灰度集群全部挂掉的情况下请求处理失败。目前灰度集群部署的规模为稳定集群的10%,即若稳定集群有100个实例,灰度集群部署10个实例。

(3) 灰度验证。初始化完成后,用户可推送灰度路由规则,将部分请求路由至灰度集群进行验证。目前,支持两种灰度规则:用户ID和请求百分比。用户ID规则是指如果对应的请求访问的是列表中的用户,则请求路由至灰度集群。用户id是一般是互联网电商行业涉及到电商 系统中的所有请求header或metadata中都会携带的一个字段,我们一般会选择一些内部测试用户或小流量用户进行灰度验证。请求百分比规则是指按一定百分比概率将请求路由至灰度集群。由于灰度集群部署规模仅为稳定集群规模10%,因此,请求百分比规则最大也只能设置为10%。如果灰度验证失败,进入流程4;成功则进入流程5。
用户ID列表灰度规则示例如下:

{
    "name":"java-demo-rule",
    "domains":[
        "java-demo"
    ],
    "routes":[
        {
            "headers":[
                {
                    "name":"userID",
                    "list_match":[
                        "20213923913999",   #. accountID
                        "20213923913100"    # accounctID
                    ]
                }
            ],
            "cluster":"java-demo|canary=true"
        },
        {
            "cluster":"java-demo|canary=false"
        }
    ]
}

(说明:用户ID为20213923913999和20213923913100的请求,进入灰度集群;其余请求保留在稳定集群)

百分比灰度规则示例如下:

{
    "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) 灰度取消。如果在灰度验证过程中发现了问题,灰度取消可秒级将全部流量切回稳定集群,具体包括两个步骤:删除路由规则和灰度集群下线。当然,用户也可先通过修改规则的方式(如用户ID列表置空或请求百分比置0),先将流量切回稳定集群,然后保留现场,方便进行问题排查。

(5) 全量发布。如果灰度验证没有发现问题,那么就可以进行新版本的全量发布了。包括:稳定集群全量发布、删除灰度路由规则和灰度集群下线。

(6) 灰度发布结束。

上述流程描述可简化成如下流程图:
在这里插入图片描述
蓝绿发布
什么是蓝绿发布
蓝绿发布,是在生产环境稳定集群之外,额外部署一个与稳定集群规模相同的新集群,并通过流量控制,逐步引入流量至新集群直至100%,原先稳定集群将与新集群同时保持在线一段时间,期间发生任何异常,可立刻将所有流量切回至原稳定集群,实现快速回滚。直到全部验证成功后,下线老的稳定集群,新集群成为新的稳定集群。
在这里插入图片描述
如上图所示,应用A是consumer,应用B是provider,应用B稳定集群的版本为v1,有3个实例。(a)应用B额外部署了一个新集群,版本为v2,同样有3个实例。(b)通过流量控制,将所有请求路由到应用B新集群,进行全量验证,同时原稳定集群继续保持在线。(c)如果在验证中发现了故障,则通过流量控制,将全部流量切回应用B原稳定集群,实现快速回滚。(d)如果验证没有发现任何故障,则应用B的蓝绿发布完成,v2版本集群成为新的稳定集群。

为什么还需要蓝绿发布
有了灰度发布之后,为什么还需要蓝绿发布呢?主要有如下几点考虑:

应用在生产环境全量发布后,发现故障时回滚时间慢。当线上核心应用存在几十上百的服务实例时,应用实例分批滚动回滚,部分业务应用启动时间需要几分钟,导致整个回滚过程的时间可能超过十分钟,甚至几十分钟。
灰度集群容量有限,灰度流量可能超过灰度集群容量。目前限定最大灰度流量为10%,但是对于用户id控制方式目前很难完全控制灰度流量低于10%。如果灰度用户id列表中包含大用户,类似总裁,老板,或者遇到用户列表中的重要用户搞秒杀活动,则流量可能会超过灰度集群容量,导致灰度服务实例负载过高,甚至不可用。
灰度发布期间能发现的问题有限。如数据库慢查问题、死锁问题等,10%流量很难发现,可能只会在100%流量中才容易暴露。
灰度发布成功后,仍然需要进行全量发布,在此过程中仍有较多不确定性,如因一些未预料的异常导致发布失败等。
对于上面几个问题,使用蓝绿发布系统都可以较好地解决:

蓝绿发布期间,流量全部切至新集群时,原稳定集群继续保持在线,若新集群有问题,可通过流量控制秒级切回至原稳定集群,没有应用启动以及其他等待时间。
蓝绿发布期间,新集群规模与原稳定集群规模一致,即使是瞬时大流量也没有问题。
蓝绿发布期间,新集群承载全站流量,容易验证各种场景,如数据库死锁等并发问题。
蓝绿发布新集群验证完成后,已经处于正常服务状态,不会再引入不确定性的变更操作。
理论上,灰度发布的需求都可以用蓝绿发布解决,而且可以更好地解决。但是我们不能完全用蓝绿发布替代灰度发布,因为应用蓝绿发布期间两个集群同时在线,占用平时两倍的服务器资源,成本很高,而灰度发布只需要很小一部分服务器资源就可以验证大部分问题。

发布流程
下面从产品层面介绍一下有赞蓝绿发布的流程,包括:蓝绿发布开始、蓝绿初始化、蓝绿验证、蓝绿取消或完成上线、蓝绿发布结束。

(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":[
                        "20213923913999",
                        "20213923913100"
                    ]
                }
            ],
            "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) 蓝绿发布结束。

上述描述可简化成如下流程图:

在这里插入图片描述
可观测性与可运维性
一个好的系统或产品,仅实现其基本功能是远远不够的,可观测、易运维也是必不可少的。在灰度与蓝绿发布产品中,可观测性与可运维性方面需要实时监控,方例运维操作。主要包括发布应用监控、发布事件通知、全局发布状态以及周期统计报表,蓝绿发布产品在可观测性与可运维性方面的效果反馈。

发布应用指标监控
蓝绿发布期间新老两个集群运行两个应用版本,需要能够对新老集群的出、入流量的QPS、耗时、失败率等指标进行监控,通过对比新旧两个版本集群的实时监控数据,用户可以快速的发现问题。如下图所示:

发布事件通知
发布事件通知是为了在发布过程中的一些重点事件发生时,及时的告知到对应的运维或开发人员,比如蓝绿发布开始、蓝绿发布取消、蓝绿发布完成上线、蓝绿发布验证过长等事件。这些事件通知通过企业办公IM发送到对应的人员。如下图所示:

全局发布状态
全局发布状态主要是通过一些关键指标实时反映蓝绿发布系统的整体使用状况,可以方便蓝绿发布运维人员根据实时状态来做出一些人为的管理和干预。如下图所示:

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐