前端网关(一)全链路灰度

1,355 阅读9分钟

前言

本文将会从灰度这一个点出发,讲解整个前端链路中网关存在的必要性。

稳定性对于业务系统而言是非常重要的一个指标,所以在上线前,开发者会通过单元测试、集成测试、UI自动化测试等等各种各样的测试来保证交付质量;在上线后通过埋点、监控、告警各种各样的方式来提前感知问题;这都是为了保证稳定性。事前提高交付质量是预防,事后监控是亡羊补牢,但仅有预防和监控,出现问题时,影响的用户还是全量的,那有没有一种办法能够降低「变更」对用户的影响呢,换言之,「变更」的影响用户数可控并且可调节呢,毫无疑问是可以的,这就是「灰度」的意义所在。

但通常情况下,「灰度」都是针对于「服务型」应用的,比如微服务、Restful 单体服务等,前端一般通过hard code或者配置下发的方式来配合后端灰度;或者前端什么都不做,直接全量发布,后端自己通过hard code来兼容「灰度」和「生产」两个版本。

这种灰度方式会引出以下两个问题:

1、如果是前端导致的线上事故,单纯的后端灰度没有任何意义;

2、灰度过程中出现问题时,前端比较难确定是本次后端灰度导致的问题,还是前端本来就有的问题,因为对于前端而言不存在灰度版本;

全链路灰度

「全链路灰度」就是用来解决上述问题的,笔者理解的「全链路灰度」是一种整个链路中所有节点受统一灰度策略调度的灰度方式。而笔者在实践下来的经验是:「全链路灰度」策略应该由最靠近用户的一端去决定,也就是说由用户最先感知到的Web页面或者终端App决定,并对流量进行染色,而后端「服务」提供生产与灰度两个版本作为依赖被调用即可。整体链路逻辑如下图所示(微服务内部调用链路省略,但基本一致):

灰度链路示意

好处在于:

1、灰度策略统一由端侧决定,后端服务在灰度过程中保持一个固定条件判定灰度即可,条件值由端侧决定,比如前端应用在进行请求,携带固定request header即表示命中灰度。这样后端服务的灰度策略在整个灰度过程中无需调整,跟随端侧即可,从而减少了沟通gap。

2、无论链路上哪一环出现问题,理想情况下都可以直接关闭端侧灰度,使整体流量都回归到生产版本,而无需多团队配合(实际情况还是会有一些例外的,比如数据库问题、中间件问题,关闭灰度可能也不能立即恢复)。

3、当灰度期间发生问题时,可以直接根据错误信息中包含的版本信息(一般前端监控系统都会上报release tag信息),缩小排查范围至指定版本。

以上仅仅列举了几点,实际根据业务场景不同还会有其他的益处,这里不在拓展了;但此方案的关键点在于,端侧该如何进行灰度呢?

Web应用的灰度

对于跨端应用来说,灰度还是相对而言比较好处理的,比如RN、小程序、或者是webview加载的H5,通常可以通过在加载资源/bundle前,调用A/B Test或者是灰度接口,来判断加载哪一个版本的资源,如下图所示:

客户端灰度

Hybrid灰度流程

那如果是直接在浏览器中打开的Web页面呢?到这里,似乎陷入了一个困境,对于直接使用浏览器打开的页面没有很好的灰度方案。在思考这个问题的解决方案前,不妨先了解下前端应用的请求链路。

前端应用链路

比较常见的前端应用链路有以下几种,图中箭头均表示七层的HTTP请求(基本上就是浏览器输入url到页面渲染过程的一部分)

常见的前端应用链路

比较奇葩的前端应用链路

CDN原理感兴趣的可自行查阅阿里云CDN文档,这里就不在赘述了。再来看下「后端服务链路」:

后端服务链路

后端应用的灰度能力基本都是由Gateway(比如Kong、APISIX,当然也有自研的)提供的,Gateway是具有流量编排能力的,比如Kong是通过Nginx + OpenRestry实现的,其底层通过lua实现了各种各样的流量编排能力。通过一定的灰度逻辑将此次请求转发到不同的集群上,「集群」简单来说是一组由相同代码部署的服务器节点(这里描述不够准确,展开的话会涉及到「服务注册/发现」相关的概念,后面的系列讲到SSR时会涉及到这部分,这里就不展开了)。

但前端应用纯靠自身的链路很难做到「灰度」这件事情,因为前端大多数情况下都属于静态资源(大家嘴里常说的CDN资源,或者CDN文件,很纳闷这个词是谁发明的,资源本身也不是直接存放在CDN上的),由静态资源服务器统一托管,比如OSS或者S3,而资源和资源间只有Object key之分(或者说文件路径),或者资源和资源按照用途通过不同域名、不同资源服务器作区分(用对象存储来举例的话,就是不同的Bucket,感兴趣的可以去看看阿里云的OSS文档,懒人可以简单的理解为两台静态资源服务器就行)。但归根到底,用户访问的同一URL背后代表的资源多数情况下也是同一份。

结合后端服务的链路以及前端应用链路,可以得出这样一个结论,如果前端应用需要灰度能力的话,就需要链路中的某个节点像后端Gateway一样能够提供可开发、可配置的能力。这其中有这个能力的就是CDN 和 Nginx:

先聊聊CDN,CDN厂商一般会提供edge script或者lambda一些serverless的编程能力,可以通过其来实现灰度能力,但脚本本身时存在于边缘节点上的,如果是单纯靠CDN这种非中心化的灰度,那只能做一些简单的白名单之类的条件灰度,比如按照UA来做灰度,权重灰度(或者叫百分比灰度)是不准确的。如果是中心化的灰度,那就需要edge script去请求接口来判断灰度,一方面是部分厂商没有提供请求的能力,另一方面,在CDN请求会拉长整个链路。所以CDN承载灰度能力可以pass;

再说说Nginx,Nginx是可以提供灰度能力的,其本身可以通过OpenRestry + Lua来提供流量编排的能力,比如Kong,部分公司也是采用了类似的方案,来统一管理前后端应用。不过一方面使用lua的开发者一般是运维,另一方面由于笔者了解的各厂情况不多,不敢妄言优劣,所以就不再论述此方案了。

基于前端网关的全链路灰度设计

笔者在之前的实践中最终选择的方案是基于Node.js来实现一个流量网关,而其中的一个核心功能就是提供资源的灰度能力。基于此方案的灰度链路如下(绿色部分为网关内部的逻辑处理):

基于前端网关的灰度链路设计

前端应用和后端服务的对应关系一般是通过对流量进行染色来实现的,说人话就是在A/B版本的前端代码请求时携带不同的标志位,标志位可以是cookie,也可以是HTTP Header,具体是什么取决于实际业务场景。这个标志位可以通过网关 set-cookie,也可以是一个代码中的全局变量。

这样一来虽然解决了前端应用的灰度能力,但也引入了其他的问题:

1、前端资源是通过CDN缓存的,如何保证CDN完全遵循灰度策略,换言之如何保证公平性(灰度结果与配置完全匹配)?

2、如何保证灰度的粘滞性/稳定性(比如在灰度策略不变的情况下,同一用户,多次访问的灰度结果应该是幂等的)?

这里简单展开下:

1、CDN本身是一个具有缓存能力的代理服务器,其缓存配置可以是CDN自身配置决定的,也可以是源站即网关返回的HTTP 缓存相关header决定的。

2、对于黑白名单类的灰度策略,在灰度策略不变的情况下,同一用户多次访问得到的灰度结果,肯定是幂等的;但对于权重灰度而言,就需要一个能代表用户的标识(比如客户端IP、设备指纹、UID等等),通过一个幂等函数的处理,得到对应的灰度结果,这样才能保证灰度的粘滞性。这里就不详细展开了,具体的灰度算法在下一篇会讲解。

总结

以上就是关于笔者在实践中,基于前端网关的全链路灰度能力的一个思考,只是整个系列的第一篇,会有一些没有讲透的点,后面会展开的讲前端网关的架构设计、以及部分能力实现。

其实前端并不仅仅只能通过终端技术来解决业务中存在的问题、痛点,其实可以将视角从前端抽离,审视整个链路,你会发现,解决问题的方式并不仅有前端一种。

PS:由于好久没有写技术文章了,文笔会有些生疏,欢迎各位大佬多多指教~