灰度发布架构设计终极篇
概述
在互联网公司中,产品通常迭代比较频繁的,尤其涉及到一些关键比较大项目需求时,影响范围比较大的时候,通常需要进行灰度控制,通过引流一小部分流量到新版本,可以及时发现程序问题,有效阻止大面积故障的发生。
业界上已经有比较成熟的服务发布策略,比如蓝绿发布、A/B 测试以及金丝雀发布、滚动发布,这些发布策略主要专注于如何对单个服务进行发布。在微服务体系架构中,服务之间的依赖关系错综复杂,有时某个功能发版依赖多个服务同时升级上线。我们希望可以对这些服务的新版本同时进行小流量灰度验证,这就是微服务架构中特有的全链路灰度场景,通过构建从网关到整个后端服务的环境隔离来对多个不同版本的服务进行灰度验证。
灰度发布类型
灰度发布如果按照端来分的话,可以分为web前端、客户端、服务端灰度。
无论是哪种灰度,一般需要满足以下2点要求:
需要一个放量配置,给产品/运营等工作人员配置放量策略;
需要做到同一个用户始终访问的是同一个版本的代码,如果同个用户上个请求访问的是A版本,下个请求访问的是B版本,就可能会出问题。
web前端灰度
作为用户体验的入口,web前端的灰度是比较常用的方案。我们前端每次发布一个新版本,就把资源增量式地上传到CDN,然后给它分配一个唯一的版本号,再把所有的版本号存储起来。当处理请求时,根据动态配置的分流策略来决定用户使用哪个版本。
比如分流策略是放量10%,即新版本随机放量给10%的用户使用,当用户首次命中资源版本号时,需要把用户id和版本号的映射关系存储起来(可存到cookie),这样就能保证同个用户上次请求和下次请求访问到的都是同个版本的代码。
那如果线上有紧急bug需要修复,又要重新发布新版本,该如何处理当前灰度的状态?是赶紧结束上一个灰度然后全量发布还是一起发上去同时灰度?一般来说,再有新版本发布或者放量策略发生变化时,应该重新分流灰度。
客户端灰度
大家在使用APP的时候,通常收到一些提示"是否体验升级新版本",通常收到该提示,说明你被系统划分为灰度用户。通常来讲web前端灰度是在客户无感知的情况下平滑进行,而APP灰度需要涉及到用户的主动安装行为。
客户端在启动时,会向灰度系统发起请求,灰度系统根据客户端传过来的参数和当前的放量策略来决定是否要给客户端升级提醒。一般会根据以下几种策略来决定给予用户升级提醒:
根据用户设备的系统和应用版本;
根据渠道:发布到不同应用市场的app都会被打上渠道标签,所以可以根据渠道来区分用户;
根据设备ID和用户ID。
通过设备ID主要是为了控制提醒频率,用户ID主要是为了区分出特性用户,比如对活跃用户发送提醒。
如果一个带有bug的安装包全量发布出去,一旦有问题,我们只能快速定位问题来提醒用户安装新版本,是否安装取决于用户,所以客户端灰度发布是非常有必要的。
服务端灰度
服务端灰度分为兼容变更灰度和不兼容变更灰度。
1)兼容变更
兼容变更又可分为物理灰度和逻辑灰度。
物理灰度:物理灰度比较简单,根据机器维度进行灰度,直接部署新老版本在不同机器,流量均匀地打在新老版本上面。这种方式虽然简单,但不适用于不兼容变更;
逻辑灰度:逻辑灰度就是根据更精确的流量策略来控制流量,这种灰度一般要写一定的灰度代码。这种方式能比较精确地控制流量,但是增加了一定的灰度代码,灰度完成后要删除相关灰度代码,有点麻烦。
2)不兼容变更
不兼容变更指的是更改了当前功能,即接口逻辑跟之前版本发生很大变化,必须要前后端同时发布,否则会有一段时间服务不可用。
一般的做法是引入接口版本号,新老版本接口并存,比如 /v1/api 和 /v2/api。前端使用/v2/api版本,当过去一段稳定期后(可以是登录态时间失效后),就可下掉/v1/api版本。
灰度放量策略
流量策略一般分为以下几种:
1. 按流量百分比
先到先得的方式比如限制10%的用户体验的是新版本,90%的用户体验的是老版本。先访问网站的用户就优先命中新版本,直到流量用完为止。
2. 按人群划分
1)按用户id、用户ip、设备类型比如可通过平时的埋点上报数据得知用户的pv、uv、页面平均访问时长等数据,根据用户活跃度来让用户优先体验新版本,进而快速观察使用效果。
2)按地域、性别、年龄等用户画像比如可通过用户的性别、年龄等做下新老版本的对比效果来看看目标用户在新版本的使用年龄段,性别范围是多少。
3. 按渠道划分
比如根据用户的注册来源来放量。
灰度发布策略
灰度部署发布通常会分为三大类,主要包括蓝绿发布、滚动发布、灰度发布,这三大类
蓝绿发布
蓝绿部署中,一共有两套系统:一套是正在提供服务系统(也就是上面说的旧版),标记为“绿色”;另一套是准备发布的系统,标记为“蓝色”。两套系统都是功能完善的,并且正在运行的系统,只是系统版本和对外服务情况不同。正在对外提供服务的老系统是绿色系统,新部署的系统是蓝色系统。
蓝绿发布示意图
蓝色系统不对外提供服务,用来做啥?用来做发布前测试,测试过程中发现任何问题,可以直接在蓝色系统上修改,不干扰用户正在使用的系统。蓝色系统经过反复的测试、修改、验证,确定达到上线标准之后,直接将用户切换到蓝色系统,切换后的一段时间内,依旧是蓝绿两套系统并存,但是用户访问的已经是蓝色系统。这段时间内观察蓝色系统(新系统)工作状态,如果出现问题,直接切换回绿色系统。当确信对外提供服务的蓝色系统工作正常,不对外提供服务的绿色系统已经不再需要的时候,蓝色系统正式成为对外提供服务系统,成为新的绿色系统。原先的绿色系统可以销毁,将资源释放出来,用于部署下一个蓝色系统。
蓝绿发布特点
蓝绿部署的目的是减少发布时的中断时间、能够快速撤回发布。
两套系统没有耦合的时候才能百分百保证不干扰
蓝绿发布注意事项
蓝绿部署只是上线策略中的一种,它不是可以应对所有情况的万能方案。蓝绿部署能够简单快捷实施的前提假设是目标系统是非常内聚的,如果目标系统相当复杂,那么如何切换、两套系统的数据是否需要以及如何同步等,都需要仔细考虑。当你切换到蓝色环境时,需要妥当处理未完成的业务和新的业务。如果你的数据库后端无法处理,会是一个比较麻烦的问题:
可能会出现需要同时处理“微服务架构应用”和“传统架构应用”的情况,如果在蓝绿部署中协调不好这两者,还是有可能会导致服务停止。
需要提前考虑数据库与应用部署同步迁移/回滚的问题。
蓝绿部署需要有基础设施支持。
在非隔离基础架构(VM 、 Docker等)上执行蓝绿部署,蓝色环境和绿色环境有被摧毁的风险。
滚动发布
一般是取出一个或者多个服务器停止服务,执行更新,并重新将其投入使用。周而复始,直到集群中所有的实例都更新成新版本。
滚动发布示意图发布流程:
相对于蓝绿发布需要一套完备的机器不同,滚动发布只需要一台机器(这儿这是为了理解,实际可能是多台),我们只需要将部分功能部署在这台机器上,然后去替换正在运行的机器,如上图,将更新后的功能部署在Server1上,然后Server1去替换正在运行的Server,替换下来的物理机又可以继续部署Server2的新版本,然后去替换正在工作的Server2,以此类推,直到替换完所有的服务器,至此,服务更新完成。
滚动发布特点
这种部署方式相对于蓝绿部署,更加节约资源——它不需要运行两个集群、两倍的实例数。我们可以部分部署,例如每次只取出集群的20%进行升级。
回滚困难
滚动发布注意事项
滚动发布没有一个确定可行的环境。使用蓝绿部署,我们能够清晰地知道老版本是可行的,而使用滚动发布,我们无法确定。
修改了现有的环境。
回滚困难。举个例子,在某一次发布中,我们需要更新100个实例,每次更新10个实例,每次部署需要5分钟。当滚动发布到第80个实例时,发现了问题,需要回滚,这个回滚却是一个痛苦,并且漫长的过程。
有的时候,我们还可能对系统进行动态伸缩,如果部署期间,系统自动扩容/缩容了,我们还需判断到底哪个节点使用的是哪个代码。尽管有一些自动化的运维工具,但是依然令人心惊胆战。
因为是逐步更新,那么我们在上线代码的时候,就会短暂出现新老版本不一致的情况,如果对上线要求较高的场景,那么就需要考虑如何做好兼容的问题。
灰度发布
灰度发布,也叫金丝雀发布。是指在黑与白之间,能够平滑过渡的一种发布方式。AB test就是一种灰度发布方式,让一部分用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度,而我们平常所说的金丝雀部署也就是灰度发布的一种方式。具体到服务器上, 实际操作中还可以做更多控制,譬如说,给最初更新的10台服务器设置较低的权重、控制发送给这10台服务器的请求数,然后逐渐提高权重、增加请求数。一种平滑过渡的思路, 这个控制叫做“流量切分”。
17世纪,英国矿井工人发现,金丝雀对瓦斯这种气体十分敏感。空气中哪怕有极其微量的瓦斯,金丝雀也会停止歌唱;而当瓦斯含量超过一定限度时,虽然鲁钝的人类毫无察觉,金丝雀却早已毒发身亡。当时在采矿设备相对简陋的条件下,工人们每次下井都会带上一只金丝雀作为“瓦斯检测指标”,以便在危险状况下紧急撤离。
灰度发布示意图过程:
准备好部署各个阶段的工件,包括:构建工件,测试脚本,配置文件和部署清单文件。
将“金丝雀”服务器部署进服务器中,测试。
从负载均衡列表中移除掉“金丝雀”服务器。
升级“金丝雀”应用(排掉原有流量并进行部署)。
对应用进行自动化测试。
将“金丝雀”服务器重新添加到负载均衡列表中(连通性和健康检查)。
如果“金丝雀”在线使用测试成功,升级剩余的其他服务器。(否则就回滚)
A/B测试
A/B测试和蓝绿发布、滚动发布以及金丝雀发布,完全是两回事。
蓝绿发布、滚动发布和金丝雀是发布策略,目标是确保新上线的系统稳定,关注的是新系统的BUG、隐患。
A/B测试是效果测试,同一时间有多个版本的服务对外服务,这些服务都是经过足够测试,达到了上线标准的服务,有差异但是没有新旧之分(它们上线时可能采用了蓝绿部署的方式)。
A/B测试关注的是不同版本的服务的实际效果,譬如说转化率、订单情况等。
A/B测试时,线上同时运行多个版本的服务,这些服务通常会有一些体验上的差异,譬如说页面样式、颜色、操作流程不同。相关人员通过分析各个版本服务的实际效果,选出效果最好的版本。
小结
综上所述,三种方式均可以做到平滑式升级,在升级过程中服务仍然保持服务的连续性,升级对外界是无感知的。那生产上选择哪种部署方法最合适呢?这取决于哪种方法最适合你的业务和技术需求。如果你们运维自动化能力储备不够,肯定是越简单越好,建议蓝绿发布,如果业务对用户依赖很强,建议灰度发布。如果是K8S平台,滚动更新是现成的方案,建议先直接使用。
蓝绿发布:两套环境交替升级,旧版本保留一定时间便于回滚。
灰度发布:根据比例将老版本升级,例如80%用户访问是老版本,20%用户访问是新版本。
滚动发布:按批次停止老版本实例,启动新版本实例。
全链路灰度设计方案
为了满足业务的迭代速度,开发者开始对原来的单体架构进行细粒度的拆分,将单体应用中的服务模块拆分成一个个独立部署运行的微服务,并且这些微服务的生命周期由对应的业务团队独自负责,有效的解决了单体架构中存在的敏捷性不足、灵活性不强的问题。
但任何架构都不是银弹,在解决旧问题同时势必会引入一些新的问题。微服务体系中最令人头疼的问题,是如何对众多微服务进行高效、便捷的治理,主要表现在可见性、连接性和安全性这三个方面。
如何保证微服务体系中服务新版本升级过程中平滑无损,以及如何低成本的为多个微服务构建流量隔离环境,方便开发者同时对多个服务新版本进行充分的灰度验证,避免故障的发生,是本章节主要会涉及到的内容,以下内容的讨论主要以电商系统中的购物车、订单为例进行说明。
在分布式微服务架构中,应用中被拆分出来的子服务都是独立部署、运行和迭代的。单个服务新版本上线时,我们再也不需要对应用整体进行发版,只需关注每个微服务自身的发布流程即可,如下:
为了验证服务 Cart 的新版本,流量在整个调用链路上能够通过某种方式有选择的路由到 Cart 的灰度版本,这属于微服务治理领域中流量治理问题。常见的治理策略包括基于 Provider 和基于 Consumer 的方式。
基于 Provider 的治理策略。配置 Cart 的流量流入规则,User 路由到 Cart 时使用 Cart 的流量流入规则。
基于 Consumer 的治理策略。配置 User 的流量流出规则, User 路由到 Cart 时使用 User 的流量流出规则。
此外,使用这些治理策略时可以结合上面介绍的蓝绿发布和灰度发布方案来实施真正的服务级别的版本发布。
全链路灰度
继续考虑上面微服务体系中对服务 Cart 进行发布的场景,如果此时服务 Order 也需要发布新版本,由于本次新功能涉及到服务 Cart 和 Order 的共同变动,所以要求在灰度验证时能够使得灰度流量同时经过服务 Cart 和 Order 的灰度版本。如下图:
按照上一小节提出的两种治理策略,我们需要额外配置服务 Order 的治理规则,确保来自灰度环境的服务 Cart 的流量转发至服务 Order 的灰度版本。这样的做法看似符合正常的操作逻辑,但在真实业务场景中,业务的微服务规模和数量远超我们的例子,其中一条请求链路可能经过数十个微服务,新功能发布时也可能会涉及到多个微服务同时变更,并且业务的服务之间依赖错综复杂,频繁的服务发布、以及服务多版本并行开发导致流量治理规则日益膨胀,给整个系统的维护性和稳定性带来了不利因素。
对于以上的问题,开发者结合实际业务场景和生产实践经验,提出了一种端到端的灰度发布方案,即全链路灰度。全链路灰度治理策略主要专注于整个调用链,它不关心链路上经过具体哪些微服务,流量控制视角从服务转移至请求链路上,仅需要少量的治理规则即可构建出从网关到整个后端服务的多个流量隔离环境,有效保证了多个亲密关系的服务顺利安全发布以及服务多版本并行开发,进一步促进业务的快速发展。
全链路灰度的解决方案
如何在实际业务场景中去快速落地全链路灰度呢?目前,主要有两种解决思路,基于物理环境隔离和基于逻辑环境隔离。
物理环境隔离
物理环境隔离,顾名思义,通过增加机器的方式来搭建真正意义上的流量隔离,物理隔离更多的属于蓝绿发布方案。
这种方案需要为要灰度的服务搭建一套网络隔离、资源独立的环境,在其中部署服务的灰度版本。由于与正式环境隔离,正式环境中的其他服务无法访问到需要灰度的服务,所以需要在灰度环境中冗余部署这些线上服务,以便整个调用链路正常进行流量转发。此外,注册中心等一些其他依赖的中间件组件也需要冗余部署在灰度环境中,保证微服务之间的可见性问题,确保获取的节点 IP 地址只属于当前的网络环境。
这个方案一般用于企业的测试、预发开发环境的搭建,对于线上灰度发布引流的场景来说其灵活性不够。况且,微服务多版本的存在在微服务架构中是家常便饭,需要为这些业务场景采用堆机器的方式来维护多套灰度环境。如果您的应用数目过多的情况下,会造成运维、机器成本过大,成本和代价远超收益;如果应用数目很小,就两三个应用,这个方式还是很方便的,可以接受的。
逻辑环境隔离
另一种方案是构建逻辑上的环境隔离,我们只需部署服务的灰度版本,流量在调用链路上流转时,由流经的网关、各个中间件以及各个微服务来识别灰度流量,并动态转发至对应服务的灰度版本。如下图:
上图可以很好展示这种方案的效果,我们用不同的颜色来表示不同版本的灰度流量,可以看出无论是微服务网关还是微服务本身都需要识别流量,根据治理规则做出动态决策。当服务版本发生变化时,这个调用链路的转发也会实时改变。相比于利用机器搭建的灰度环境,这种方案不仅可以节省大量的机器成本和运维人力,而且可以帮助开发者实时快速的对线上流量进行精细化的全链路控制。
那么全链路灰度具体是如何实现呢?通过上面的讨论,我们需要解决以下问题:
链路上各个组件和服务能够根据请求流量特征进行动态路由
需要对服务下的所有节点进行分组,能够区分版本
需要对流量进行灰度标识、版本标识
需要识别出不同版本的灰度流量
接下来,会介绍解决上述问题需要用到的技术。
版本路由
无论是网关暴露的http接口还是dubbo接口,都会有相应的版本号,在进行灰度发布设计的时候可充分考虑前端、网关、后端dubbo服务版本号传递。
标签路由
标签路由通过对服务下所有节点按照标签名和标签值不同进行分组,使得订阅该服务节点信息的服务消费端可以按需访问该服务的某个分组,即所有节点的一个子集。服务消费端可以使用服务提供者节点上的任何标签信息,根据所选标签的实际含义,消费端可以将标签路由应用到更多的业务场景中。
节点打标
那么如何给服务节点添加不同的标签呢?在如今火热的云原生技术推动下,大多数业务都在积极进行容器化改造之旅。这里,我就以容器化的应用为例,介绍在使用 Kubernetes Service 作为服务发现和使用比较流行的 Nacos 注册中心这两种场景下如何对服务 Workload 进行节点打标。
在使用 Kubernetes Service 作为服务发现的业务系统中,服务提供者通过向 ApiServer 提交 Service 资源完成服务暴露,服务消费端监听与该 Service 资源下关联的 Endpoint 资源,从 Endpoint 资源中获取关联的业务 Pod 资源,读取上面的 Labels 数据并作为该节点的元数据信息。所以,我们只要在业务应用描述资源 Deployment 中的 Pod 模板中为节点添加标签即可。
在使用 Nacos 作为服务发现的业务系统中,一般是需要业务根据其使用的微服务框架来决定打标方式。如果 Java 应用使用的 Spring Cloud 微服务开发框架,我们可以为业务容器添加对应的环境变量来完成标签的添加操作。比如我们希望为节点添加版本灰度标,那么为业务容器添加spring.cloud.nacos.discovery.metadata.version=gray,这样框架向Nacos注册该节点时会为其添加一个标签verison=gray。
流量染色
请求链路上各个组件如何识别出不同的灰度流量?答案就是流量染色,为请求流量添加不同灰度标识来方便区分。我们可以在请求的源头上对流量进行染色,前端在发起请求时根据用户信息或者平台信息的不同对流量进行打标。如果前端无法做到,我们也可以在微服务网关上对匹配特定路由规则的请求动态添加流量标识。此外,流量在链路中流经灰度节点时,如果请求信息中不含有灰度标识,需要自动为其染色,接下来流量就可以在后续的流转过程中优先访问服务的灰度版本。
逻辑环境隔离——基于 SDK
上面我们详细介绍了实现全链路灰度所需要的几种技术,如果想为现有的业务接入全链路灰度能力,不可避免的需要为业务使用的开发框架 SDK 进行改造。首先,需要支持动态路由功能,对于 Spring Cloud、Dubbo 开发框架,可以对出口流量实现自定义 Filter,在该 Filter 中完成流量识别以及标签路由。同时需要借助分布式链路追踪技术完成流量标识链路传递以及流量自动染色。此外,需要引入一个中心化的流量治理平台,方便各个业务线的开发者定义自己的全链路灰度规则。基于 SDK 实现方式的图例如下:
逻辑环境隔离——基于 Java Agent
基于 SDK 方式的弊端在于需要业务进行 SDK 版本升级,甚至会涉及到业务代码的变动。企业内部各个微服务虽然使用同一种开发框架,但很难保证框架版本是一致的,所以不得不为每一个版本维护一份全链路灰度的代码。业务代码与 SDK 代码紧耦合,SDK 版本迭代会触发业务不必要的发版变更,对业务的侵入性比较强。
另一种比较流行的方式是基于字节码增强技术在编译时对开发框架进行功能拓展,这种方案业务无感知,以无侵入方式为业务引入全链路灰度能力。基于 Java Agent 的实现方式的图例如下:
但仍然无法避免是开发者需要为业务使用版本不一致的开发框架维护对应的 Java Agent 的版本。如果您比较倾向于这种无侵入的方案但又不想自己来维护,您可以选择阿里云 MSE 服务治理产品,该产品就是一款基于 Java Agent 实现的无侵入式企业生产级服务治理产品,您不需要修改任何一行业务代码,即可拥有不限于全链路灰度的治理能力,并且支持近 5 年内所有的 Spring Boot、Spring Cloud 和 Dubbo。
逻辑环境隔离——基于 Service Mesh
在业务系统的微服务架构中,如果存在大量使用不同的技术栈、语言栈的微服务,Java Agent 的方式就无能为力了。我们可能需要为每一个语言的 SDK 编写和维护全链路灰度代码,不仅需要不同语言栈的开发者,而且涉及到语言无关的 bug 修复时需要全语言版本的 SDK 共同升级,这种代价不见得比基于物理环境隔离方案小。
那有没有一种与语言无关的方案呢?有,下一代微服务架构服务网格,Service Mesh。它将分布式服务的通信层抽象为单独的一层,在这一层中实现负载均衡、服务发现、认证授权、监控追踪、流量控制等分布式系统所需要的功能。显然,我们所需的全链路灰度能力也可以在这个流量治理基础设施层来实现。幸运的是,服务网格明星产品Istio以声明式 API 资源对流量治理进行了统一抽象,借助于 VirtualService 和 DestinationRule 治理规则可以很容易实现全链路灰度的效果,并且Istio集成了各种主流的分布式链路追踪框架。基于 Service Mesh 的实现方式的图例如下:
在实际生产环境中,服务多版本并行开发是很常见的事情,而且版本迭代速度非常快。版本每次变更都需要修改 VirtualSerivice 资源中路由匹配规则,另外 VirtualSerivice 资源中并没有提供容灾能力。比如存在一条路由规则访问服务提供方的某个灰度版本,如果目标服务不存在该灰度版本或者不可用,按照目前 Istio 的实现是仍然将流量转发至该版本,缺乏容灾机制。还有一种业务场景,如果我们希望对处于一定 UID 范围的用户流量转发指定灰度环境,是无法通过 Istio 现有的流量治理规则实现的。此时,您可以选择阿里云服务网格产品 ASM,是一个统一管理微服务应用流量、兼容 Istio 的托管式平台。ASM 针对上述两个场景都有应对方案,轻松解决您在多语言场景下的全链路灰度诉求。
三种方式对比
下表是三种方式对比,从多个方面进行了对比:
如果您倾向于使用无侵入式的 Java Agent 的方式,但又担心自建带来的稳定性问题,您可以选择 MSE 微服务治理产品,该产品是阿里巴巴内部多年在微服务治理领域的沉淀的产出,经历了各种大促考验。
如果您倾向于使用语言无关、无侵入式的 Service Mesh 的方式,但又担心自建带来的稳定性问题,您可以选择阿里云 ASM 产品,相比开源 Istio,在功能性、稳定性和安全性都有很大的提升。
流量入口:网关
在分布式应用中,作为流量入口的网关是不可或缺的。在全链路灰度场景中,就要求微服务网关具备丰富的流量治理能力,支持服务多版本路由,支持对特定路由规则上的请求进行动态打标。对于入口服务可见性问题,网关需要支持多种服务发现方式。安全性问题上,网关作为集群对外的入口可以对所有请求流量进行认证鉴权,保障业务系统不被非法流量入侵。
在虚拟化时期的微服务架构下,业务通常采用流量网关 + 微服务网关的两层架构,流量网关负责南北向流量调度和安全防护,微服务网关负责东西向流量调度和服务治理。传统的网关比如zuul、springcloud gateway以及Apache的shenyu网关都可以扩展在一定程度上支持灰度发布。
在容器和 K8s 主导的云原生时代,Ingress 成为 K8s 生态的网关标准,赋予了网关新的使命,使得流量网关 + 微服务网关合二为一成为可能。阿里云 MSE 发布的云原生网关在能力不打折的情况下,将两层网关变为一层,不仅可以节省 50% 的资源成本,还可以降低运维及使用成本。最重要的是,云原生网关支持与后端微服务治理联动实现端到端的全链路灰度。
灰度设计实践
灰度发布的最终要解决的问题,基于一定的流量测试,测试没有问题后会进行全量放开,具体如下图:
具体实现思路如下:
在代码中做。
一套线上环境,代码中做开关,对于不同的用户走不同的逻辑
在接入层做。
多套(隔离的)线上环境,接入层针对不同用户转发到不同的环境中
两种方案的优缺点:
方案 | 优点 | 缺点 |
在代码中做 | 灵活,粒度细;一套代码(环境)运维成本低 | 灰度逻辑侵入代码 |
在接入层做 | 无需(少)侵入代码;风险小 | 多套线上环境,运维成本高 |
灵活的灰度方案一般需要在接入层实现,具体就是自定义负载均衡策略实现。
下面介绍在接入层使用的方式:
nginx层实现(使用nginx+lua)
网关层实现
dubbo的灰度,项目中如果使用dubbo,有可能会需要dubbo服务的灰度实现。
负载均衡又可分为服务端负载均衡和客户端负载均衡
服务器端负载均衡:例如Nginx,通过Nginx进行负载均衡,先发送请求,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。
客户端负载均衡:例如ribbon或者dubbo,客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡;即在客户端就进行负载均衡算法分配。
基于Nginx灰度发布方案
基于前端不同的用户的cookie:tts_version_id的值不同进行灰度分流,具体nginx.conf配置如下图:
如果用户灰度分流的策略比较复杂,推荐两个开源的项目Openresty、ABTestingGateway。
Openresty
OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
Openresty学习地址:https://moonbingbing.gitbooks.io/openresty-best-practices/content/base/intro.html
ABTestingGateway
ABTestingGateway是新浪微博的开源的项目,是一个可以动态设置分流策略的灰度发布系统,工作在7层,基于nginx和ngx-lua开发,使用 redis 作为分流策略数据库,可以实现动态调度功能。
ABTestingGateway是在 nginx 转发的框架内,在转向 upstream 前,根据 用户请求特征 和 系统的分流策略 ,查找出目标upstream,进而实现分流。
nginx实现的灰度系统中,分流逻辑往往通过 rewrite 阶段的 if 和rewrite 指令等实现,优点是性能较高,缺点是功能受限、容易出错,以及转发规则固定,只能静态分流。针对这些缺点, ABTestingGateway,采用ngx-lua 实现系统功能,通过启用lua-shared-dict和lua-resty-lock作为系统缓存和缓存锁,系统获得了较为接近原生nginx转发的性能。
git地址:https://github.com/CNSRE/ABTestingGateway
基于dubbo灰度发布方案
dubbo在设计之初对灰度发布就提供了很好的支持,它现在支持两种路由方式:条件路由、标签路由。
条件路由主要支持以服务或Consumer应用为粒度配置路由规则,源码对应的路由器是ConditionRouter.java。
标签路由主要是以Provider应用为粒度配置路由规则,通过将某一个或多个服务的提供者划分到同一个分组,约束流量只在指定分组中流转,从而实现流量隔离的目的,对应源码路由器是TagRouter.java。
Dubbo原生路由器支持的场景其实已经很丰富的了,比如条件路由器,可以设置排除预发布机器、指定Consumer应用访问指定的Provider应用,这其实就是蓝绿发布,还能设置黑白名单、按网段访问等等,这些所有的标签管理我们都可以再dubbo-admin进行管理。如果你的灰度分流策略过于复杂以至于上述两个路由不满足,我们可以自定义实现一个Router,基于Dubbo SPI的扩展成本非常低,我们只需定义自己的路由器并把整合到应用就可以了。
灰度路由器是以provider应用为粒度配置路由规则的,包含两个过滤器,条件过滤器和权重过滤器(不是Dubbo的过滤器),它们的主要任务根据配置过滤出备用provoider。配置信息放在分布式配置上,首次加载放入应用缓存,一旦有变更将自动更新。这样在发布时可以动态调整灰度参数,达到逐步扩大流量和影响人群的目的。
dubbo官方文档,对于路由这块的支持介绍的比较详细,具体文档可以参考:https://dubbo.apache.org/zh/docs3-v2/java-sdk/advanced-features-and-usage/service/routing/routing-rule/
基于SpringCloud灰度发布方案
Ribbon 提供了几个负载均衡的组件,其目的就是让请求转给合适的服务器处理。
默认轮询
自定义策略需要继承AbstractLoadBalancerRule
开源方案:
自定义DiscoveryEnabledRule继承PredicateBaseRule
首先在请求开始处,实现自己的灰度逻辑,比如下面的demo根据请求url如果包含‘version’向holder中添加route为A的标识,否则添加route为B的标识。 (Holder本质是一个localThread)
在目标服务添加matadateMap
PredicateBaseRule中使用google提供的pridicate,MetadataAwarePredicate中实现apply方法判断发现的服务是否是目标服务
最后基于SpringCloud的灰度发布推荐如下两个开源项目:
https://github.com/SpringCloud/spring-cloud-gray
https://github.com/Nepxion/Discovery
灰度发布devops工具平台
分布式链路追踪
还有一个很重要的问题是如何保证灰度标识能够在链路中一直传递下去呢?如果在请求源头染色,那么请求经过网关时,网关作为代理会将请求原封不动的转发给入口服务,除非开发者在网关的路由策略中实施请求内容修改策略。接着,请求流量会从入口服务开始调用下一个微服务,会根据业务代码逻辑形成新的调用请求,那么我们如何将灰度标识添加到这个新的调用请求,从而可以在链路中传递下去呢?
从单体架构演进到分布式微服务架构,服务之间调用从同一个线程中方法调用变为从本地进程的服务调用远端进程中服务,并且远端服务可能以多副本形式部署,以至于一条请求流经的节点是不可预知的、不确定的,而且其中每一跳的调用都有可能因为网络故障或服务故障而出错。分布式链路追踪技术对大型分布式系统中请求调用链路进行详细记录,核心思想就是通过一个全局唯一的 traceid 和每一条的 spanid 来记录请求链路所经过的节点以及请求耗时,其中 traceid 是需要整个链路传递的。
借助于分布式链路追踪思想,我们也可以传递一些自定义信息,比如灰度标识。业界常见的分布式链路追踪产品都支持链路传递用户自定义的数据,其数据处理流程如下图所示:
其中可以使用SkyWalking作为全链路跟踪中间件,能够很好的根据traceId进行灰度链路跟踪。
监控与告警
一个好的系统或产品,仅实现其基本功能是远远不够的,可观测、易运维也是必不可少的。主要包括发布应用监控、告警事件通知、全局发布状态以及周期统计报表,这里强烈推荐Prometheus+Grafana作为云原生监控告警平台。
灰度发布期间新老两个集群运行两个应用版本,需要能够对新老集群的出、入流量的QPS、耗时、失败率等指标进行监控,通过对比新旧两个版本集群的实时监控数据,用户可以快速的发现问题。如下图所示:
文章最后推荐一下我的知识星球,知识星球可以和我探讨一下技术与管理,欢迎大家加入。
reference:
https://www.cnblogs.com/alisystemsoftware/p/15694892.html
https://www.woshipm.com/pd/4381854.html
https://www.cnblogs.com/cheyunhua/p/14009876.html