今日头条搜索品质优化端到端篇
叶航 字节跳动技术质量 稿
导览
为了提升头条端上的搜索性能体验,我们从19年3/4双月开始投入专门的人力做品质优化,通过这段时间的持续投入,搜索整体的性能体验有了明显的提升,基本上与竞品对齐。中间踩过很多坑,也收获了一些经验,这里做一个总结分享,欢迎大家拍砖交流~
为了方便阅读,整体的介绍会拆分为2个部分,本篇介绍一下业务背景和整体的技术挑战,以及端到端场景的品质优化,后续会再对落地页做单独的优化介绍。
业务背景
搜索的业务场景其实比较简单,核心场景是两个:
- 搜索结果页。也就是展示用户搜索结果的页面,也就是本篇介绍所说的“端到端”场景
- 落地页。也就是从结果页里点击打开的新页面,可以是站内页面,也可以是站外H5、小程序等。站内页和小程序等体裁性能相对较好,我们的优化主要针对的是站外落地页
端到端场景的技术挑战
看起来很简单的一个场景,为什么会有性能瓶颈,需要专门做优化呢?
基于H5+webview的架构
搜索结果页是基于webview来实现的。相比于纯Native实现,基于webview的架构在性能体验上会有天然的瓶颈,主要是在速度和稳定性上:
- 速度方面,webview的首次初始化、页面加载流程、webview原生网络模块、web&Ntive通信等环节都存在一定的耗时瓶颈。
- 稳定性方面,webview的白屏、黑屏等异常也给我们持续带来很多困扰。
聊一聊技术选型背景:
选择H5是由业务形态和业务发展共同决定的。头条搜索是综搜,有大量字节如意的需求(简单说就是不同query可以出不同的结构化样式),卡片样式非常多,而且需要很高的动态性,兼具跨端(Android+iOS+M站)优势的webview+h5是不错的选择(也是早期业务刚启动时唯一的选择)。
经过2年的快速迭代,头条搜索的pv已经近2亿,前端卡片样式达到数百种,业务复杂度已经到达一个相当高的地步。在这个背景下,虽然webview架构带来很大的性能挑战,但相比较做整体技术栈的迁移(比如切Native/Lynx/Flutter)带来的巨大成本(主要是),还是继续基于webview架构来优化有更高的roi。也得益于字节有了TTWebview自研内核,为webview优化提供了非常强有力支撑。
复杂的业务&有限的资源
前面提到过搜索的业务已经比较复杂,对性能方面的影响主要在页面体积和请求耗时方面。完整主文档+核心JS xxk,在网络传输跟js执行阶段耗时都很长,除此之外,影响性能的业务复杂度更多来自头条app本身。
作为一个平台型app,头条承载了大量业务,并且由于快速迭代遗留的历史包袱,头条整体的性能压力已经很大,比如启动速度、cpu、内存等,这些压力也会直接影响到搜索业务上。比如冷启动发起搜索路径会比较长,并且刚启动时由于整体资源紧张,搜索成功率也会显著低,再比如虚拟内存紧张,会直接影响搜索页面出现黑屏的概率。
另一方面,业务众多容易产生相互影响,比如webview的使用:头条里有10个以上的业务会用到webview,其他业务使用webview不当很容易影响到搜索。同样的,搜索做webview预加载的策略,也会很容易使详情页白屏升高。也正因此,相比于其他主打搜索功能的app(百度、浏览器等),头条搜索能使用的资源是相对有限的,没办法做激进的、独占式的webveiw资源使用。
进展总结
- 搜索成功率提高
- 搜索80分位耗时降低
优化过程
整体思路
-
多端合作共建(前端、服务端、推荐后端、网络、内核)
-
问题发现:建立用户视角的全链路数据统计和监控
-
数据驱动,识别关键瓶颈,分而治之,极致优化
- 网页渲染架构优化
- 网络链路优化
- 后端优化
- 内核加载流程优化
- 端上耗时压缩
- 极致策略优化
-
监控完善,防劣化
-
组件化建设,头条Lite双端复用
指标统计和分析能力建设
核心是**建立用户视角的全链路数据统计和监控,每次搜索统计以用户点击搜索词为起点,结果页渲染出首屏为终点,中途取消均不视为成功。(**原有的统计口径是利用前端performance api统计的首屏耗时)
主要遇到的挑战是:
- 整个项目团队已经沿用旧口径一段时间了,口径迁移有较大成本,包括开发和信息对齐的成本;
- 另外由于系统webview内部实现纯黑盒,很多环节是没办法进一步分析的;
通过沟通协调,最终整个项目团队达成一致:以用户视角口径为目标、以全流程性能分析为驱动来做优化,并且与内核团队共建完成webview内核全流程打点,为后续更细致的性能分析打下基础。
指标的口径迁移和建设从最终的效果来看是非常值得的,新的口径帮助我们发现了很多被忽略的问题,而这些原本被忽略的点也是我们后续的优化空间所在:
- 耗时方面。比如端上逻辑耗时、webview初始化耗时、js模版加载耗时、jsb通信耗时等在以往是被忽略的,但其实存在很大的优化空间;
- 稳定性方面。旧的成功率以网络请求为口径,遗漏了用户主动取消的场景以及webview异常等;
渲染架构迭代
主要围绕前端架构和端上加载流程来做优化。通过前端页面做SSR服务端渲染改造,再结合端上预创建webview、预链接、内核PLZ优化等,在速度和成功率上取得大幅提升。
【整体的演进过程】
离线化,由模版请求数据
离线化,native代替前端请求
离线化,单卡ssr
完整ssr
基于ssr的模版预加载
【核心优化点】
- 减少js执行
- 并行/预执行
- 服务端渲染,减轻端上工作
- 流式传输,加快首屏渲染
- 分机型策略
搜索网络链路优化
网络是整个链路的重要一环,搜索经SSR服务端渲染改造后会走两种网络
- Webview网络:通过预链接&链接保活,请求的链接复用率提升到90%+,80分位DNS+TCP建链耗时降为0;
- TTNet:前期和Feed共用域名,已经有较高的复用率,后期切独立域名后通过链接保活做优化,保持90%+链接复用率;
另外经过对长尾badcase分析,挖掘出了dns过长、H2链接黑洞等问题,提出并推动网络库实现兜底IP、链接保活、旁路重试等策略,对搜索网络性能/可用性有明显提升。另外在网络协议层面也推进了quic、br压缩等优化策略。
异常case治理
异常case是整个优化过程里的一大难点,也是痛点。由于webview本身的复杂性,结合业务复杂性,webview版本兼容性,厂商兼容性等,衍生出很多异常。这类问题也很难用系统性的方案来解决,各自原因不同,更多是一个不断踩坑填坑的经验积累~下面挑选2个印象深的case具体介绍下:
-
Vivo9系统异常cancel
在搜索做完SSR+内核Plz优化之后,虽然大盘性能提升了,但是仍然有不少搜不出的用户反馈。经过初步分析发现,用户反馈和异常统计在vivo9系统上都有明显的聚集性,这个现象一度让我们非常费解,因为ttwebview的存在按理说已经能规避webview厂商兼容问题。
怀着深深的疑惑逐步排查定位,最终发现是:
vivo9系统为了适配深色模式,会在webview的onPageStart/ onPageFinished/ onLoadResource/ doUpdateVisitedHistory几个时机,从framework层直接调用webview.loadUrl执行一段js来实现深色模式。而在KITKAT以上的版本,loadUrl里面执行js会导致当前正在加载的页面被cancel掉,从而导致搜索搜不出,plz特性
- 原文作者:知识铺
- 原文链接:https://geek.zshipu.com/post/%E4%BA%92%E8%81%94%E7%BD%91/%E4%BB%8A%E6%97%A5%E5%A4%B4%E6%9D%A1%E6%90%9C%E7%B4%A2%E5%93%81%E8%B4%A8%E4%BC%98%E5%8C%96%E7%AB%AF%E5%88%B0%E7%AB%AF%E7%AF%87/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com