前端可用性保障实践
本文基于已发表在Infoq的“美团点评收银台前端可用性保障实践”一文编辑而成。
如何定义前端服务可用性
一般可用性都是说后端服务的可用性,都说我们的服务可用性到了几个9,很少有人把可用性放到前端来。其实对于任何一个有UI交互流程的业务,都会有前端服务可用性,后端的可用性做的再高,前端一个按钮写的有问题点击不起作用也会导致用户无法完成流程。
前端服务可用性包含三个部分:
- 前端代码可用性(测试质量,线上异常)。
- 静态资源服务可用性。
- 网络链路可用性(DNS劫持、网络性能)。
既从业务后台服务往上,一直到用户界面,一切都是前端服务,这里面一切用户可能遇到的问题都是前端可用性的范畴。
这就是我们认为的前端可用性,收银台的可用性建设就是围绕着这三个部分展开的。
如何衡量前端服务可用性
前端服务的可用性衡量和后端的衡量方法相类似,不考虑影响范围大小,只考虑存在故障的时常,最大化考量可用性。可用性指标不是为了让我们通过复杂的算法来减小事故对可用性计算的影响,而是为了激励我们在可观测范围内做到没有问题,越做越好。影响用户数、影响订单数、影响GMV等指标更多的是用于做事故定级。
哪里容易出问题
前端代码可用性:
- 空指针问题是困扰前端的一个大问题,由于JS本身是弱类型动态语言,无法在开发及编译过程中通过工具推导出可能出现问题的点,进而在前端研发过程中很容易疏忽造成空指针问题;
- 业务逻辑覆盖率,指的是在业务项目当中,代码对动态逻辑的处理能力,往往在一些复杂的业务项目当中,逻辑混乱交错,前端的展示和进一步的动作由后端控制,这种情况下复杂的逻辑交织在一起产生无数分支,逻辑环境难以模拟,进而很容易在逻辑的处理上产生疏忽;
- 兼容性,问题困扰着各个端的研发,对于前端来说,要面临的环境更多,包括平台、系统版本、浏览器版本、WebView版本、Hybrid桥版本等等,很难从测试角度全部覆盖。
静态资源服务可用性:
- 前端静态资源服务链的稳定性,例如NGINX、Node等等;
- CDN并不是任何时候都可以正常提供服务的,可能会遇到SSL证书链问题、回源服务可用性问题等等。
网络链路稳定性:
- DNS劫持是一个老大难问题,大部分情况下是运营商为了节省跨省流量结算的费用而进行DNS劫持,走内部的缓存,还有一部分情况是广告,想象一下把收银台的代码劫持并插入一个运营商广告是有多可怕。
大块的问题就是上述几种,细枝末节的问题就不在这里一一细表,那么具体我们是怎么解决的呢?
怎样保障才能令人信服?
记得刚刚开始负责支付业务的时候,老板(rank)经常问一个问题:“收银台稳定性怎么保障?”,我当时想的就比较简单,无非就是流程保障、测试保障等等,但这不是老板想听的,不然他也不会老问我,显然是当时没有回答出他想要的答案。现在想想真是“too young too simple, some times naive”。
在美团点评,收银台是一个横向的业务基础服务,是所有业务的闭环环节,所有线上业务交易的最终环节全部由收银台来完成,它的重要性不言而喻。对于收银台来说,有三点需要保障,这三点分别是可用性、体验和安全,它们共同为一个指标服务,那就是“支付成功率”。其中,对支付成功率影响最大的就是可用性。
可用性对支付成功率的影响有多大?
一个小小的bug上线后即使及时发现并回滚,可能也会造成几百上千万营业额的损失,这对整个团队来说都是无法接受的。所以,对于收银台来说,保障可用性是第一优先级。
同时,支付作为一个特殊的业务有它对可用性独到的要求,在可用性保障上必然不是任何业务都会用到的那老几样儿。老板想听的是对稳定性保障的独到见解,可复制的方法,有可用性保障的理论基础,让任何一个日后负责这个业务的人都能够照方抓药,保障前端服务的稳定性。
现在总结起来可用性的保障分为三个阶段:
- 事前
- 事中
- 事后
保障手段分为三个大类:
- 软的
- 硬的
- 根源的
“软的”是指用“人”来保障的部分:
- 流程保障
- 规范保障
- 测试保障……
“硬的”是指用“工程工具”来保障的部分:
- 静态代码检查
- 单测
- Web自动化测试
- 持续集成
- 线上前端异常监控
- 业务异常监控
- 前端服务异常监控
- 网络异常监控
“根源的”是整个可用性保障的核心,是指通过“技术选型”来让系统更健壮,这里面有两个核心点。
技术选型要简单稳健
要求在具备伸缩性的基础下避免任何复杂的不可控技术方案。核心链路上的所有代码,团队要具备维护能力,要减少外部依赖。
这里面有一个关键的选型概念就是“场景契合度”,技术选型不是你愿意用什么,你熟悉用什么,是在这个业务场景和团队规模下需要你用什么。
举个例子,收银台是一个单页应用,之所以设计成单页应用是因为它涉及到的视图跳转和数据传递太多,单页应用相比多页更具优势。那么在选型的时候我们当时有React、Angular、Ember等一线前端SPA框架可以选,但最后我们还是自己做了一个简单的视图生命周期管理工具,为什么?
- “场景契合度”,React和Angular等前端框架更适合极端复杂的大型单页应用,为了能够更好的处理这种复杂度采用了一系列厚重的工具去约束研发的过程,其中还包含一些这个项目不会遇到问题的优化,例如渲染优化等等。对于收银台来讲,单个视图中的复杂度并没有那么高,可以遇到前端渲染性能瓶颈的项目并不多。
- “源码维护能力”,收银台作为核心链路中的核心业务,在技术上绝对不允许被动,团队必须具有核心代码的维护能力。而依照我们当时的团队规模,这是不现实的。
在收银台这个SPA场景里,我们只需要视图生命周期管理这个功能。所以,我们参考Cocoa View Controller的生命周期设计实现了一个简单的单页视图工具“Cyra”,它只负责视图生命周期的管理,简单、拓展性高、源码可维护且无外部依赖。
避免出现核心链路上的可用性短板
举个例子,网页首帧渲染优化有三种常见方式:
- 手工预渲染
- 编译预渲染
- 服务器预渲染(SSR)
其优化的核心内容就是把尽可能多的首帧渲染所需信息在第一个请求的响应中给出,也就是主文档请求,让用户能够尽可能快的看到内容。
从优化效果上来讲,SSR的效果最好,它可以把JavaScript(以下简称“JS”)、CSS、HTML以外的动态的数据一起通过第一个响应返回回来。
但是,最后我们选择的是编译预渲染,为什么?
先说什么是SSR。这个概念是新提出来的,但原理很早就存在,类似JSP、ASP这种技术早年间一直都是SSR,在服务器端把页面拼装好传递给客户端。和佛家的人生三境界一样,禅中彻悟后又回去了,就像现在的前端服务化很难做到当年微软ASP.NET Web Form那个水平。
后来前端行业发展迅速,发生了两个大的变化:
- 大家开始做前后端分离,把静态资源单独管理,好处就不说了,有一个弊端就是当用户浏览器把静态资源下载下来后可能还需要另外一个请求去获取这个页面上的动态数据;
- 前端工程化的兴起,大家会把CSS JS HTML结构统一打包到一个JS文件中,HTML中只有JS的引用,这样就导致HTML下载完成后还是白屏,只有等到这个巨型JS下载完成后首帧内容才开始渲染。
这时就用到了SSR,通用做法是增加一个Node层,在服务器端做首屏内容的拼接,包含静态数据,这样能够保障首帧渲染不仅快,还包含首屏所需要的数据。其架构如下图:
可以看到,Node这一层在我们界面请求的核心链路上,Node本身的可用性和上下游的服务相比要差很多,其自身的稳定性需要许多其他工具去保障,那么对于这块业务来说,Node这一层成为了“核心链路上的可用性短板”,这样即使背后的各个后端系统可用性再好,只要Node这一层挂掉就会造成用户无法访问的问题。
所以基于“避免出现核心链路上的可用性短板”这一层考量,我们退而求其次选用“编译预渲染”,在编译期间把首屏结构全部拼装好,这样可用性就得到了保障。
关于Node在服务端的应用上,我认为其实大多数情况下,不用要比用要难得多,关于这方面的一些思考可以详见后续文章《服务端为什么不能用Node》。
理论有了,我们是怎么做的?
“软的”流程规范部分就不展开讲了,各个团队都差不多,只不过是完善不完善的差异。接下来主要讲一下“硬的”部分。
前文提到,“硬的”保障主要指的是工程工具的保障手段,工程工具很多,这里对应前文几大问题的顺序,讲一讲我们的解决方案。
前端代码可用性部分主要有三个容易出问题的点:空指针、业务逻辑覆盖率、兼容性。
空指针
“空指针”部分的问题解决只能从语言本身来解决,JS本身是弱类型动态语言,无法在开发及编译过程中通过工具推导出可能出现问题的点。针对这一点我们从2015年开始实践TypeScript(以下简称“TS”),当时也看了Facebook的Flow,但当时Flow还不够成熟,所以没有选用。
引入TS后,将我们的弱类型语言变成强类型语言,从编码过程中就可以帮助过滤掉很大一部分空指针问题,TS强大的类型推导系统可以帮我们分析出系统中的空指针隐患,进而可以解决线上99%的空指针问题。当然TS还有很多其他好处,这里就不展开了。
业务逻辑覆盖率
“业务逻辑覆盖率”这个问题的背景不再赘述,由于收银台的复杂度高、case多,复杂情况下的后端状态很难模拟,因此只能采用自动化工具去解决,这就涉及到了“Web自动化流程测试”。
Web自动化流程测试在这种场景下除了可以验证case的正确性以外,最重要的功能就是要有一个异常强大的case管理模块。业界目前并没有理想的工具能够支撑我们的场景。
美团点评内部有一个我们参与需求的Web自动化流程测试工具“Freekite”,它在case验证功能的基础上,有一个强大的可视化case管理模块,支持复杂的case细分。除了界面操作的细分外,可以全量Mock或部分Mock后端的数据响应,根据响应拆分出不同的case分支。除此之外,还包含智能自动化断言功能,断言基本不需要人工参与。
可能有人要问了,这个case录完以后万一遇到界面改版怎么办?没关系,虽然有强大的相似度匹配功能,Freekite还支持单独节点的重新录制,也就完美的解决了case的维护问题,大幅度减少工作量增强效率。紧接着我们会在项目中增加Freekite的持续集成,在项目的每一个阶段进行流程上的自动化回归验证,业务逻辑覆盖率的问题就基本解决了。下图为Freekite可视化Case管理。
兼容性
“兼容性”问题公司内部有云测平台,可以快速在多机型真机上回归主要流程,可以通过云测平台覆盖占有率95%以上的各种机型。然而兼容性也是一样,需要从根本上选用一个可靠的选型,从而避免在处理兼容性问题上会遇到的拆东墙补西墙最后还是不放心的尴尬境地。兼容性问题在移动端除了布局外主要出现在两种操作中:点击和滚动。
前文描述的自主研发的单页视图工具就以最简单的div隐藏显示的方式来处理视图切换,使所有元素处于正常的文档流当中,点击处理也通过分级降级的方式最大化平衡体验和兼容性,从而保障了整个项目的兼容性。
静态资源服务可用性主要就是NGINX层的健康检查及CDN的回源监控,这一点公司SRE有强大的系统支持(有关美团点评SRE的实践可以参考之前的博客文章),这里就不多讲了。
网络可用性上最头痛的问题是DNS劫持,前文讲到了DNS劫持方面除了恶意劫持以外,主要是运营商以节省跨省流量结算费用为目标进行DNS劫持。当运营商系统发现HTTP访问的域名时会在区域内的服务器中缓存一份资源,后续用户再请求的时候其域名解析会被解析到运营商的服务器上去由运营商的服务器直接返回内容。
其应对方法只有使用HTTPS,但并不仅仅是在原有的域名HTTP的基础上切换HTTPS那么简单,还需要保障这个域名不支持HTTP访问并且没有被大范围使用HTTP访问过。如果不这样做的话会出现一个问题,运营商在DNS解析的时候并不知道这个域名是用什么协议访问的,当之前已经记录过这个域名支持HTTP访问后,不管后续是否是HTTPS访问,都会进行DNS劫持。这时如果使用的是HTTPS访问,会因为运营商的缓存服务器没有对应的SSL证书而导致请求无法建立链接,从而遇到请求失败的问题。在之前业务切换HTTPS的时候就遇到了这个问题,请求成功率从99.96%降低到了96%,花了大量的时间去定位问题。当切换了全新的域名后这个问题才得到了解决。
在事后方面,除了强大的支付后台业务系统监控外,公司还有完善的通用监控系统,例如异常监控系统可以分级分批上报前端的JS Error及自定义异常,性能监控系统Performance可以了解前端的访问情况做性能分析,网络监控系统CAT可以快速定位网络层性能状况、区域DNS劫持状况等。
作者简介
禹霖,美团点评前端技术专家,负责金融钱包及支付前端团队。
最后,我们正在招人,美团点评金融服务平台招聘Web前端研发工程师,欢迎想的多做的多的有志之士加入,简历发送至chenyulin[at]outlook.com。
下一章:云端的SRE发展与实践
本文根据作者在美团点评第21期技术沙龙的分享记录整理而成。 背景: SRE(Site Reliability Engineering)是Google于2003年提出的概念,将软件研发引入运维工作。现在渐渐已经成为 ...