“ 作者从2014年双十二结束时起始接手淘宝首页,经历了淘宝首页的两次改版和一次从PHP到Node的迁移,不久前完成为了工作的交接。本文介绍了淘宝首页的变迁过程、性能优化、稳定性保证和敏捷办法,分享了作者这里过程中的感受。
关联背景介绍
淘宝首页是淘宝的门面,承载着几乎淘系所有业务的入口,流量很大,量级单位为亿。近几年无线端崛起,业务重点起始向无线终端偏移(日前不可叫偏移,基本以无线为主了),因此淘宝 PC 端首页的流量亦有削减,不外即便如此,它的日均 PV 依然相当高。
淘宝首页一向是内部平台和技术的实验田,它始终在变化着。最新的框架和系统都会找淘宝首页试点,能够试想下,倘若某一项需要推动的升级或优化办法在淘宝首页已然上线,并且拿到了良好的数据和稳定性,其他业务还有什么理由不去尝试和更迭呢?同期,去年一年身在淘宝前端的技术架构组,自然而然亦会主动去 push 有些实验性的内容到业务上。
淘系的站点页面包含首页、其他频道页和活动页等,这些页面并不都由淘宝前端一行一行的代码码出来,业务如此之多,这种玩法即便人数 double 亦忙不外来。事实上,大都数页面都是依托内部的搭建平台一一运营或前端经过模块搭建的方式一一构建的,而前端 focus 的重点在于搭建平台的建设自己以及模块的通用性和复用率的保证,当然,还有有些工程化的东西。
运用搭建平台搭建的页面,前端只需要思虑构成页面的原子模块的研发,整体的渲染由搭建平台供给的统一脚本全权负责。而在淘宝首页上,思虑到页面模块数量巨多,加上还有少量跨分部、跨团队的沟通,渲染模型略微区别。
淘宝首页的整体变迁
背景中说到,淘宝首页依托于内部搭建平台,它的变迁自然亦是跟着搭建系统的变化而变化的。
1、PHP 下的淘宝首页
接手淘宝首页不久,便遇到了一年一度的改版,那时它还运行在 PHP 环境中。这儿需要说明的是,淘宝首页的所有代码完全由前端掌控,前端不会直接跟数据库打交道,其数据源自分为两部分。
数据源自
一是运营填写的数据。 采用前端挖坑的形式,预留坑位让运营获取填写数据,如(伪代码):
上面的代码会产生一份 PHP 的模板和info字段对应的表单坑位,这个过程简叫作“挖坑”。
运营填写这些坑位就会产生这份 PHP 模板对应的数据,最后渲染出来便是一个完整的 HTML 片段(实时性渲染)。
旧版搭建系统中便是经过这种方式构造一个子模块。我描述得非常简单,但做为一个平台它需要思虑的东西还有非常多非常多的,例如数据次序的掌控、按时发布、回滚机制、过滤机制、筛选机制、数据的同步、数据的更新、版本掌控、权限掌控、其他系统的引用等。
二是后端或个性化平台供给的数据。 区别的业务有区别的诉求。有些业务有自己的后端,她们需求运用自己业务产出的数据;有的业务期盼用户看到的内容不同样,千人千面,期望接入算法;有些业务跟卖家直接打交道,期望运用招商数据;而有些业务期望采用运营从数据池筛选出来的数据……总之,淘宝首页需要对接形形色色的系统,接口繁多。后面会说到对动态数据源的整合。
并且这些系统对应的域名是不同样的,JSONP 格式自然亦就成为了首选。但有些特殊的系统,例如宣传,它的渲染并不是一个简单的 JSONP 请求,可能它还要干涉全部宣传的渲染流程,例如加载她们的 JS,把渲染的掌控权交过去。
页面的架构
上面介绍了数据的源自和子模块的结构,那样全部页面又是怎样形成的呢?模块的搭建分为两种,一种是可视化搭建,运营或前端能够将研发好的模块(或模块库中选择的模块)拖拽到容器内,形成一个页面。
当然,上图亦只是一个模型,做为一个系统需要思虑的问题还有非常多非常多,如页面的布局、多终端适配、模块的临时隐匿、位置调节、皮肤选取、模块的复制等。
亦能够经过如下源码搭建的方式(伪代码):
经过模块 id 将模块引入,并且添加有些类似 lazyload 的标记,方便掌控渲染节奏和数据入口。源码搭建和模块搭建的区别在于,前者更易于掌控模块的结构以及模块的渲染次序。
动态数据源
首页面对一大堆接口和平台,对接几十个业务方,接口是个很大的问题,因为后台系统的差异,基本无办法统一数据源的格式,一旦运营哪天心血来潮要换一个他自己觉得用的更爽的或数据更好的系统,前后端估计又得沟通和对接几次。因此显现了下面这张图:
平台具备数据源接入的能力,亦便是说咱们挖的坑不仅能够让运营填数据,还能够从各样数据源中直接导入数据,当然,这儿需要进行一次数据字段的映射转换。后端供给的接口是这般的:
前端约定的接口形式是:
那样系统必须供给这种映射的绑定策略:
绑定之后,数据既能够同步输出,亦能够异步输出,这些都是平台供给的能力。这个方法基本上处理了后端系统/接口变化的问题,并且减少了前后端之间的沟通成本。
不外这儿需要重视的是,虽然页面上的接口都经过平台统一梳理了一次,这亦寓意着,页面所有的请求会先流经平台,而后分发到各个后端,平台的抗压能力需求很高。
2、PHP 到 Node 的变迁
淘宝首页日均请求的这个量级,不可能是十几二十台台服务器抗得住的,支撑它必须有一个服务集群。
每一个 CDN 节点上都具备 PHP 渲染的能力,当页面发布时,咱们把该页面所有的模块和数据同步到所有 CDN 节点上,基本模式大概便是如此了。看起来还挺不错,然则经过一段时间的运维,非常多安全、性能问题都慢慢浮现出来了。
性能问题。每一个 PHP 页面包括多个子模块,而子模块亦有可能引用了其他的子模块,PHP 的 include 操作是存在消耗的,每一次引用都是一次磁盘 IO,一个渲染节点上跑了成千上万个类似淘宝首页的 PHP 页面,并发一高其效率可想而知。
// @邦彦 朋友弥补:php 的 include 操作是存在消耗,然则加载、执行的过程预热后,字节码直接进缓存,并不存在频繁磁盘 io 的状况。cdn php 性能差的问题重点是两个:1. php 版本过旧,5.4 和 7 的性能相差不只几倍;2. fast-cgi 模式在高并发的场景下和 node 相比无任何优良。
推送机制问题。文件同步(图中的 sync 动作)是一种比较恶心的机制,首要,时间上没法掌控,一个文件同步到所有的节点,快则几秒钟,慢的话耗时会超过一两分钟;并且同步过程还有可能失败,健康检测的成本亦是相当高的。发布比较紧凑时,需要同步的文件亦非常多,很容易导致队列堆积,加剧同步差的体验。
实时性强需要问题。文件在推送之前,还可能经过有些前置系统,发布链路越长,线上生效时间越慢,慢的时候大约五分钟才生效,这般的延时针对实时性需求很高(如秒杀)的需要来讲是完全不能接受的。
当然,还有非常多其他问题,如运维成本升高、安全危害升高、PHP 资深人才贮存不足等等。因此 PHP 渲染容器的命运,便是,被干掉。
下图改变了下玩法,服务集群为 Cache CDN,它仅有静态文件处理能力,无 PHP/Node 的渲染能力,因此处理效率高,性能亦好,抗压能力相当强,并且扛不住的时候还能够花钱买服务,拓展 Cache 集群。
用户拜访时,Nginx 转到 Cache CDN,倘若命中缓存则直接返回,无命中便回源到源站服务器。源站服务器是具备模块渲染能力的 Node 服务,它能够做非常多事情:
掌控 Cache 响应头,经过 max-age 和 s-maxage 掌控页面在客户端的缓存时间以及在 Cache 上的缓存时间,这个缓存时间能够按照需要随时做调节,例如大促的时候调长有些
掌控内外网环境,和 AB 测试状态
融合前端关联的工具链,例如检测、压缩、过滤等等
它的优良有非常多,这儿不一一列举了。这个模式中还添加了一层容灾,源站服务器每隔一段时间将数据推送到于 Cache 同机房的备份服务器,一旦源站挂了,还能够自动容灾到备份数据上。
模式的变化不仅在运维上有了突破,CDN 被攻击时的安全危害亦低了非常多,同期亦省却了 sync 所需的各样检测机制,每年节约成本亦是百万以上,优良还是相当显著。
3、Node,不同样的模式
上面 PHP 模块中,咱们只说了 HTML 和数据部分,用心的读者应该已然发掘,CSS 和 JS 这些静态资源都没说到,那页面是怎样渲染的呢?
旧版 PHP 页面中,咱们是直接引入了一个 CSS 和一个 JS,淘宝这边采用的是 git 版本迭代发布,这些静态资源都是直接放在一个 git 仓库中。亦便是这般:
每次发布完 git 文件,再修改 PHP 的版本号,而后发布 PHP 代码。当然,亦做了关联的优化,例如发布 git 时自动更新版本号等。
而新版搭建平台的页面渲染模式与 PHP 的模式不太同样。
一个模块的 CSS/JS 和模板放在一块,CSS/JS 与页面其他模块的静态资源是相互独立的,目的便是期盼单个模块亦能够完整的跑起来,更加利于模块的复用。
而模块的挖坑,亦从模板中独立了出来,采用 JSON Schema 的形式定义数据格式。
搭建平台经过这个 JSON Schema 解析成 图一 的坑位。那样一个模块的渲染就编程了 index.xtpl 和挖坑数据之间的拼装了。
模块之间相互独立隔离,因此会存在必定程度的冗余,不外模块解偶带来的收益要比这点冗余要多得多。事实上,咱们是经过一个仓库去管理单个模块的。页面的渲染就比较简单了,源站 Node 容器会将所有的 index.xtpl 合并成一个 page.xtpl,为减少页面请求,css 和 js 亦会 combo 成一个文件,如上图所示的 http://cdn/??mod1.css,mod2.css,mod3.css。
任何模块的更新,页面都会有感知,下次进入系统时,就会提示是不是需要升级模块和页面。
淘宝首页的性能优化
首页模块众多,倘若一口气吐出来,DOM 数量必然超过 4k 个,其结果便是首屏时间极长。根据 TMS 的研发规范,每一个 TMS 模块都包括一个 index.js 和 index.css,最后展示出来两个 combo 的 js 和 css。首页加载的时候亦不会一口气执行所有 index.js,否则刚起始页面阻塞会非常严重。
页面的渲染规律
首页框架的加载规律,大致如下图所示。
遍历所有 TMS 模块(包括一个 J_Module 的钩子)
部分 TMS 模块无 JS 内容,然则加载了一个 index.js,为模块添加 tb-pass 的 class,用于跳过该模块 JS 的执行
将页面分为两块,首屏为一起,非首屏整体为第二块,先将首屏模块加入到懒加载监控
待首屏模块加载完成,或用户处理了页面交互时(滚动、鼠标移动等),将非首屏模块加入到懒加载监控
处理有些特殊模块,它们会在进入视窗之前几百像素就起始加载
监控滚动,根据以上规律,渲染模块
部分模块即便是被执行了,亦不必定渲染出来,由于它的优先级不高,在模块内部加了事件监听,例如等到 mouseover/onload 事件触发的时候再渲染这些内容。
之前写过性能优化关联的文案,复制就不必了,直接贴位置:
《一块来瞧瞧淘宝首页的个性化》(http://www.barretlee.com/blog/2016/03/31/personality-in-taobao-home-page/)
《淘宝首页性能优化实践》(http://www.barretlee.com/blog/2016/04/01/optimization-in-taobao-homepage/)
代码的性能优化是一个精细活,倘若你要在一个庞大的未经优化的页面上做性能优化,可能会面临一次重构代码。
上面的文案说到的是页面内部的细节优化,然则在研发流程中做的规范化、标准化,以及线上拜访通路中的各个环节优化还无提及。这一起内容可能有点跑题,就不多说了。
淘宝首页的稳定性保证
在大流量下,任何小问题都会被放大成大问题,因此研发环节遇到的任何偶发性问题都需要导致注重。不外非常多偶发性问题在咱们的测试环境中是找不到的,例如与地域关联的问题(如上海的某个 CDN 节点挂了),用户属性问题(如 nickname 最后一个为字母 s 的用户页面天窗),浏览器插件问题,运营商宣传注入问题等等。
难以在上线之前把所有问题思虑周全,然则有两点是必须做好的:兜底容灾 + 监控预警。
1、兜底容灾机制
兜底容灾有两个层面的思虑:
异步接口请求错误,包含接口数据格式错误,接口请求超时等
同步渲染,源站页面渲染出错
异步接口请求,重点触及到的是后台系统,对接系统较多,各个系统的稳定性和抗压能力各不相同,这方面的保证有多种方法,下面是最平常的:
每次数据请求都缓存到本地,并且为每一个接口都供给一个硬兜底。还有一种方法是「重试」,请求一次不成功那就请求第二次。这方面的讨论详细能够瞧瞧之前写的这篇文案:《大流量的下兜底容灾方法》(http://www.barretlee.com/blog/2015/09/16/backup-solution-at-big-traffic/)。
针对同步渲染,它只需要页面模板和同步数据,两者中任一种存在错误,源站都会报错,此时回源返回的内容便是一个 error 页面,状态码为 5xx。这个错误不必定是研发者导致的,有可能是系统链路显现同步反常或断路问题。针对这种问题,我给淘宝首页做了一个镜像页:
一旦源站任何反常,Nginx 都会转到与 Cache CDN 同机房的首页镜像上去,这个镜像内容便是淘宝首页的 HTML 备份源码。
2、监控预警机制
能够先瞧瞧之前写的这篇文案:《前端代码反常日志收集与监控》(http://www.barretlee.com/blog/2015/08/20/cb-fe-monitor/),介绍了有些监控办法。
监控亦有两个层面:
模块级别的监控,接口请求布点、模块天窗检测等
页面的监控,在页面上添加特殊标记,按时回归所有 CDN 节点,查看特殊标记是不是存在
模块层面的监控,内容还是相当多的,监控的点越多越仔细,到最后定位问题的效率就会越高,例如在一个稍微繁杂的模块上,我会埋下这些监控:
接口请求格式错误、请求失败、请求超时,最少三个埋点
硬兜底数据请求失败埋点
模块 5s 内无渲染完成统计埋点
模块内链接和照片黑白名单匹配埋点
其中部分监控还会自动处理知道的错误,例如 https 页面下显现了 http 的照片,会立即自动处理掉这些问题。
3、上线前的自动化检测
这属于淘宝全部工程化环境的一部分,前端自动化测试。通常会在上线之前处理这些问题:
检测 HTML 是不是符合规范
检测 https 升级状况
检测链接合法性
检测静态资源合法性
检测 JavaScript 报错
检测页面加载时是不是有弹出框
检测页面是不是调用 console.*
页面 JS 内存记录
当然,亦可以自己添加测试用例,例如检测接口数据格式、模块天窗问题等。自动化检测亦能够设定按时回归,还是比较有保证的。
淘宝网首页的敏捷办法
1、健康检测
页面模块众多,为了能够跟踪页面上每一个小点的变化,我在请求、渲染的每一个环节都做了仔细的统计,如下图所示:
一旦接口请求失败,或接口走了容灾规律,或模块渲染超过 5s,掌控台都会有黄色警报,当然此时,亦已然向服务器发送了警报统计。
2、接口Hub
接口 Hub 是对数据请求的管理工具,如下图所示:
页面非常多模块的渲染都需要一个以上的数据源,一旦运营反馈页面渲染数据反常,能够直接经过 Hub 找到数据,加速 Bug 定位效率。同期 Hub 亦能够用来切换环境,将一个接口的请求切换到平常或预发环境的接口之中,它是调试的利器。
3、快捷通道
我在页面脚本执行前后都放了一个快捷操作通道,一旦遇到紧急线上问题,例如样式错乱溢出、接口报错引起天窗等,能够经过快捷通道直接修改页面的 CSS 和 JS,两分钟内上线。
不外这类通道只适合紧急问题的修复,毕竟随意插进 JS 代码是存在很大危害的。
小结
本文有非常多方面无延伸拓展开,但期盼能够让你对淘宝首页有一个基本的认识。
本文由阿里小胡子哥授权InfoQ转载,
戳阅读原文直达原文出处。
▽
延展阅读(点击标题):
专访Java Champion:程序员怎样写出更好的代码?
专访朱赟:上天赐予她美貌的同期,赋予了她配套的才华
土木工程毕业转码农,十年干到CTO!
|