写点什么

美团点评收银台前端可用性保障实践

  • 2017-07-30
  • 本文字数:5138 字

    阅读完需:约 17 分钟

本文主要讨论的是前端可用性相关话题,以在美团点评移动端网页收银台的实践为例,讲解收银台前端是如何保障可用性的。

收银台是什么?为什么要保障收银台可用性?

(点击放大图像)

在美团点评,收银台是一个横向的业务基础服务,所有业务的闭环环节。所有线上业务最终完成交易全部由收银台来完成,它的重要性不言而喻,对于收银台来说,有三点需要保障,这三点分别是『可用性』、『体验』、『安全』,他们都共同为一个重要指标服务,那就是『支付成功率』,而对支付成功率影响最大的就是可用性。
可用性对支付成功率的影响有多大?一个小小的 Bug 上线后即使及时发现并回滚,可能也会造成几百上千万的营业额损失,这对于整个团队来说是无法接受的。所以,对于收银台来说,保障可用性是第一优先级。

如何定义前端服务可用性?

一般可用性都是说后端服务的可用性,都说我们的服务可用性到了几个 9,很少有人把可用性放到前端来。其实对于任何一个有 UI 交互流程的业务,都会有前端服务可用性,后端的可用性做的再高,前端一个按钮写的有问题点击不起作用也会导致用户无法完成流程。

前端服务可用性包含三个部分:

  • 前端代码可用性(测试质量,线上异常)
  • 静态资源服务可用性
  • 网络链路可用性(DNS 劫持,网络性能)

既从业务后台服务往上,一直到用户界面,一切都是前端服务,这里面一切用户可能遇到的问题都是前端可用性的范畴。

这就是我们认为的前端可用性,收银台的可用性建设就是围绕着这三个部分展开的。

如何衡量前端服务可用性?

前端服务的可用性衡量和后端的衡量方法相类似,既不考虑影响范围大小,只考虑存在故障的时常,最大化考量可用性。可用性指标不是为了让我们通过复杂的算法来减小事故对可用性计算的影响,而是为了激励我们在可观测范围内做到没有问题,越做越好。影响用户数,影响订单数,影响 GMV 等指标更多的是用于做事故定级。

哪里容易出问题?

前端代码可用性:

  • 空指针问题是困扰前端的一个大问题,由于 JS 本身是弱类型动态语言,无法在开发及编译过程中通过工具推导出可能出现问题的点,进而在前端研发过程中很容易疏忽造成空指针问题;
  • 业务逻辑覆盖率指的是在业务项目当中,代码对动态逻辑的处理能力,往往在一些复杂的业务项目当中,逻辑混乱交错,前端的展示和进一步的动作由后端控制,这种情况下复杂的逻辑交织在一起产生无数分支,逻辑环境难以模拟,进而很容易在逻辑的处理上产生疏忽;
  • 兼容性问题困扰着各个端的研发,对于前端来说,要面临的环境更多,包括平台、系统版本、浏览器版本、WebView 版本、Hybrid 桥版本等等,很难从测试角度全部覆盖;

静态资源服务可用性:

  • 前端静态资源服务链的稳定性,例如 NGINX 、Node 等等;
  • CDN 并不是任何时候都可以正常提供服务的,可能会遇到 SSL 证书链问题,回源服务可用性问题等等;

网络链路稳定性:

  • DNS 劫持是一个老大难问题,大部分情况下是运营商为了节省跨省流量结算的费用而进行 DNS 劫持,走内部的缓存,还有一部分情况是广告,想象一下把收银台的代码劫持并插入一个运营商广告是有多可怕;

大块的问题就是上述几种,细枝末节的问题就不在这里一一细表,那么具体我们是怎么解决的呢?

怎样保障才能被信服?

记得刚刚开始负责支付业务的时候,老板 (rank) 经常问一个问题:“收银台稳定性怎么保障?”,我当时想的就比较简单,无非就是流程保障、测试保障等等,但这不是老板想听的,不然他也不会老问我,显然是当时没有回答出他想要的答案。现在想想真是 “too young too simple, some times naive”。

支付这样一个特殊的业务有它对可用性独到的要求,对于可用性保障上必然不是任何业务都会用到的那老几样儿。老板想听的是对稳定性保障的独到见解,可复制的方法,有可用性保障的理论基础,让任何一个日后负责这个业务的人都能够照方抓药,保障前端服务的稳定性。

现在总结起来可用性的保障分为三个阶段:

  • 事前
  • 事中
  • 事后

保障手段分为三个大类:

  • 软的
  • 硬的
  • 根源的

「软的」是指用“人”来保障的部分:

  • 流程保障
  • 规范保障
  • 测试保障

「硬的」是指用“工程工具”来保障的部分:

  • 静态代码检查
  • 单测
  • Web 自动化测试
  • 持续集成
  • 线上前端异常监控
  • 业务异常监控
  • 前端服务异常监控
  • 网络异常监控

「根源的」是整个可用性保障的核心,是指通过“技术选型” 来让系统更健壮。这里面有两个核心点:

一、技术选型要简单稳健

要求在具备伸缩性的基础下避免任何复杂的不可控技术方案。核心链路上的所有代码团队要具备维护能力,要减少外部依赖;

这里面有一个关键的选型概念就是 「场景契合度」,技术选型不是你愿意用什么,你熟悉用什么,是在这个业务场景和团队规模下需要你用什么。

举个例子,收银台是一个单页应用,之所以设计成单页应用是因为它涉及到的视图跳转和数据传递太多,单页应用相比多页更具优势。那么在选型的时候我们当时有 React, Angular, Ember 等一线前端 SPA 框架可以选,但最后我们还是自己做了一个简单的视图生命周期管理工具,为什么?

  • 「场景契合度」,React Angular 等前端框架更适合极端复杂的大型单页应用,为了能够更好的处理这种复杂度采用了一系列厚重的工具去约束研发的过程,其中还包含一些这个项目不会遇到的问题的优化,例如渲染优化等等。对于收银台来讲,单个视图中的复杂度并没有那么高,可以遇到前端渲染性能瓶颈的项目并不多;
  • 「源码维护能力」,收银台作为核心链路中的核心业务,在技术上绝对不允许被动,团队必须具有核心代码的维护能力,而依照我们当时的团队规模,这是不现实的;

在收银台这个 SPA 场景里,我们只需要视图生命周期管理这个功能。所以,我们参考 Cocoa ViewController 的生命周期设计实现了一个简单的单页视图工具「Cyra」,它只负责视图生命周期的管理,简单、拓展性高、源码可维护无外部依赖。

(点击放大图像)

(点击放大图像)

二、避免出现核心链路上的可用性短板

举个例子,网页首帧渲染优化有三种常见方式:

  • 手工预渲染
  • 编译预渲染
  • 服务器预渲染(SSR)

其优化的核心内容就是把尽可能多的首帧渲染所需信息在第一个请求的响应中给出,也就是主文档请求。让用户能够尽可能快的看到内容。

从优化效果上来讲,SSR 的效果最好,它可以把 JS、CSS、HTML 以外的动态的数据一起通过第一个响应返回回来。

但是,最后我们选择的是编译预渲染,为什么?

先说什么是 SSR,SSR 这个概念是新提出来的,但原理上很早就存在,早年间一直都是 SSR,有服务器端把页面拼装好传递给客户端,类似 JSP、ASP 这种技术,和佛家的人生三境界一样,禅中彻悟后又回去了,就像现在的前端服务化很难做到当年微软 ASP.NET Web Form 那个水平。

后来前端行业发展迅速,发生了两个大的变化:

  • 大家开始做前后端分离,把静态资源单独管理,好处就不说了,有一个弊端就是当用户浏览器把静态资源下载下来后可能还需要另外一个请求去获取这个页面上的动态数据;
  • 前端工程化的兴起,大家会把 CSS JS HTML 结构统一打包到一个 JS 文件中,HTML 中只有 JS 的引用,这样就导致 HTML 下载完成后还是白屏,只有等到这个巨型 JS 下载完成后首帧内容才开始渲染;

这时就用到了 SSR,通用做法是增加一个 Node 层,在服务器端做首屏内容的拼接,包含静态数据,这样能够保障首帧渲染不仅快,还包含首屏所需要的数据。其结构如下图:

(点击放大图像)

可以看到,Node 这一层在我们界面请求的核心链路上,那么 Node 本身的可用性和上下游的服务相比要差很多,其自身的稳定性需要许多其他工具去保障,那么对于这块业务来说,Node 这一层成为了「核心链路上的可用性短板」,这样即使背后的各个后端系统可用性再好,只要 Node 这一层挂掉就会造成用户无法访问的问题。

所以基于「避免出现核心链路上的可用性短板」这一层考量,我们退而求其次选用「编译预渲染」,在编译期间把首屏结构全部拼装好,这样可用性就得到了保障。

关于 Node 在服务端的应用上,我认为其实大多数情况下,不用要比用要难得多,关于这方面的一些思考可以详见后续文章《服务端为什么不能用 Node》。

理论有了,我们是怎么做的?

「软的」流程规范部分就不展开讲了,各个团队都差不多,只不过是完善不完善的差异。这个部分我主要讲一下「硬的」部分。

前文提到,「硬的」保障主要指的是工程工具的保障手段,工程工具很多,这里对应前文的几大问题顺序讲一讲我们的解决方案。

前端代码可用性部分主要有三个容易出问题的点:空指针、业务逻辑覆盖率、兼容性

「空指针」部分的问题解决只能从语言本身来解决,JS 本身是弱类型动态语言,无法在开发及编译过程中通过工具推导出可能出现问题的点。针对这一点我们从 15 年开始实践 TypeScript ,当时也看了 Facebook 的 Flow ,没有选用 Flow 的原因是当时 Flow 还不够成熟。

引入 TypeScript 后,将我们的弱类型语言编程强类型语言,从编码过程中就可以帮助过滤掉很大一部分空指针问题,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 有强大的系统支持这里不多讲。

网络可用性上最头痛的问题是 DNS 劫持,前文讲到了 DNS 劫持方面除了恶意劫持以外,主要是运营商以节省跨省流量结算费用为目标进行 DNS 劫持。当运营商系统发现 HTTP 访问的域名时会在区域内的服务器中缓存一份资源,后续用户再请求的时候其域名解析会被解析到运营商的服务器上去由运营商的服务器直接返回内容。

其应对方法只有使用 HTTPS ,但并不仅仅是在原有的域名 HTTP 的基础上切换 HTTPS 那么简单,还需要保障这个域名不支持 HTTP 访问并且没有被大范围使用 HTTP 访问过,如果不这样做的话会出现一个问题,运营商在 DNS 解析的时候并不知道这个域名是用什么协议访问的,当之前已经记录过这个域名支持 HTTP 访问后,不管后续是否是 HTTPS 访问,都会进行 DNS 劫持,这是如果使用的是 HTTPS 访问,会因为运营商的缓存服务器没有对应的 SSL 证书而导致请求无法建立链接,这时就会遇到请求失败的问题。在之前业务切换 HTTPS 的时候就遇到了这个问题,请求成功率从 99.96% 降低到了 96%,花了大量的时间去定位问题。当切换了全新的域名后这个问题才得到了解决。

在事后方面,除了强大的支付后台业务系统监控外,公司还有完善的通用监控系统,例如异常监控系统可以分级分批上报前端的 JS Error 及自定义异常,性能监控系统 Performance 可以了解前端的访问情况做性能分析,网络监控系统 CAT 可以快速定位网络层性能状况,区域 DNS 劫持状况等。

以上便是「收银台前端稳定性如何保障?」这个问题我的思考,如有不妥之处欢迎指正。

关于作者

陈禹霖,美团点评前端技术专家,负责金融钱包及支付前端团队。


感谢徐川对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017-07-30 17:164609

评论

发布
暂无评论
发现更多内容

【原创】Spring Boot终极篇《上》

田维常

springboot

国内外互联网大厂工程师联合推荐:程序员三门课+151个建议

小Q

Java 学习 编程 程序员 开发

看完这篇你还能不懂C语言/C++内存管理?

C语言与CPP编程

c c++ C语言 内存管理 编程开发

C++中的vector和Java中的ArrayList的构造函数的区别

jiangling500

Java c++ ArrayList vector

【原创】Spring Boot 集成Spring Data JPA的玩法

田维常

springboot

【原创】SpringBoot快速整合Thymeleaf模板引擎

田维常

springboot

高防服务器是什么?

德胜网络-阳

vivo 云服务海量数据存储架构演进与实践

vivo互联网技术

数据库 架构 云服务 数据存储

社会正在惩罚不读书的人!这份程序员进阶书单,值得收藏~

田维常

程序员 电子书

一期二班 - 吴水金 - 第五课作业

吴水金

redis的stream类型命令详解

LLLibra146

redis stream 消息队列

浅析一个较完整的SpringBoot项目

田维常

sping

Spring Boot 如何快速实现定时任务

田维常

springboot

如何将MySQL查询优化到极致?

冰河

MySQL sql 性能优化 查询优化 查询

面试官:Java装箱与拆箱的区别?

田维常

springboot

送你4句口诀 云存储选型不再犯难

京东科技开发者

云存储

刷了LeetCode的链表专题,我发现了一个秘密!

Simon郎

Java 面试 链表

23张图!万字详解「链表」,从小白到大佬!

王磊

Java 数据结构与算法

【原创】Spring Boot集成Redis的玩法

田维常

spring Boot Starter

【原创】Spring Boot集成Mybatis的玩法

田维常

springboot

【原创】Spring Boot 如何手写starter

田维常

springboot

Polkadot系列(二)——混合共识详解

QTech

区块链 polkadot

如何快速构建Spring Boot基础项目?

田维常

spring Boot Starter

【原创】Spring Boot一口气说自动装配与案例

田维常

springboot

面经手册 · 第16篇《码农会锁,ReentrantLock之公平锁讲解和实现》

小傅哥

Java 面试 小傅哥 ReentrantLock 公平锁

设置Vmware中的Ubuntu为桥接模式

jiangling500

ubuntu vmware 桥接

Redis-缓存雪崩,缓存击穿,缓存穿透

topsion

redis

Spring Boot 集成 Druid 监控数据源

田维常

springboot

【原创】Spring Boot 过滤器、监听器、拦截器的使用

田维常

springboot

【原创】Spring Boot终极篇《下》

田维常

springboot

追风人与笃行者:云手机的2020风云录

脑极体

美团点评收银台前端可用性保障实践_语言 & 开发_陈禹霖_InfoQ精选文章