01
背景与摘要
eBay Touchstone是eBay公司的实验平台,它提供全面的A/B测试服务,涵盖实验设计,实验创建,指标计算,统计分析到最终评估上线等整个实验生命周期,旨在协助公司业务部门做出更明智的决策。在过去几年,Touchstone平台已经累计了超20000个实验应用场景,拥有1600+的月活用户,并为eBay超过40个业务领域提供了宝贵的数据分析和深入洞察,包括市场趋势分析、用户行为分析、产品性能评估等方面。
近两年来,Touchstone经历了显著的业务增长,年活跃用户同比增长了40%,新建实验数量同比增长了76%,服务端请求的峰值在一年内增长了5倍。随着服务规模的扩大,用户对网站访问速度的不满也日益增多,有记录显示打开数据报表需要近1分钟的加载数据,这严重影响了用户的工作效率。我们认识到,现有的代码基础和技术架构已不足以满足用户对于高效访问速度的期望。因此,性能优化成为了平台发展的当务之急,也是我们必须首要解决的问题。为此,我们展开了一系列的性能分析与优化措施。
本文将重点介绍eBay Touchstone团队如何通过前后端技术实践,度量网站性能,将网站首屏最大内容绘制时间(Largest Contentful Paint, 简称LCP)从起初的10+s显著优化至2.4s, 关键内容加载时间(Key Element Timing, 简称KET)从最初的20+s显著优化到1.3s。
最大 内容 绘制 (P75) | 关键 内容 加载 | 活跃 用户数 | 新建 实验数 | 服务器 请求 峰值 | |
2022年度 | 10s | 20s | 1389 | 1147 | 5w/天 |
2023 年度 | 2.4s | 1.3s | 2261 | 1614 | 26w/天 |
变化 | -7.6s | -18.7s | +872 | +467 | +21w/天 |
变化比 | -76% | -93.5% | +76% | +40% | +500% |
02
性能指标
为了解决网站加载速度缓慢的性能问题,首先必须确定合适的性能指标,并采用有效的度量方法来准确反映网页的加载性能。
经过调研和针对实际项目的分析,我们选择了Largest Contentful Paint(LCP)作为核心的性能评估指标,并辅以Key Element Timing(KET)作为次辅助指标。接下来将详细阐述我们选择LCP和KET作为性能衡量标准的理由,这些指标的具体计算方法,以及如何利用Google Analytics 4进行性能指标的收集、统计和监控的实践方法。
1.业界指标介绍
当前广泛采用的网站性能评估指标可分为两大主要类别:
一是基于PerformanceNavigationTiming和PerformanceResourceTiming API的衡量标准;二是由Google团队提出的Core Web Vitals(CWV)指标体系。
在对这两套衡量体系中的指标进行细致分析后,鉴于我们面临的主要问题是网页及数据加载缓慢,我们特别关注了那些能够直接反映加载性能的指标。具体而言,我们聚焦于DOMContentLoaded(DCL)和Page Load事件,以及CWV中的First Contentful Paint(FCP)和Largest Contentful Paint(LCP)。下面将详细讨论这些指标的差异,并解释我们在选择性能评估标准时的决策过程。
DOMContentLoaded(DCL):
在初始 HTML 文档被加载和解析后触发。通常情况下,这一事件点会发生在样式、字体和图像完成加载之前,因此 DOMContentLoaded 事件发生在页面绘制完成之前。
Page Load:
在整个页面及其所有依赖资源加载完成后触发,或者可以用Page Fully Loaded表达来帮助理解,这里的依赖包括所有资源,如样式表、脚本、iFrame和图像等。
First Contentful Paint(FCP):
指的是用户访问页面后,浏览器中出现第一个DOM元素所需的时间。FCP常用来衡量加载初始完全白屏的时间。
Largest Contentful Paint(LCP):
指页面上最大元素加载所需的时间,也是专注于衡量加载时间的指标。不同于DOMContentLoaded,LCP考虑了在初始加载后添加到页面的元素,该时间会随着页面渲染变化而变化,因为页面中的最大元素在渲染过程中可能会发生改变。
2.为什么选择LCP和KET
附图清晰地展示了各个加载性能指标与页面加载过程中的关键事件之间的关联。从用户感知角度来看,传统性能指标如DOMContentLoaded和Page Load主要关注于那些容易测量的加载事件,然而,这些事件的发生时刻并不总是能够准确反映用户的实际体验。例如,一个网站可能整体加载迅速,但如果在所有内容完全就绪之前持续阻塞渲染,用户可能会面对一段时间的空白屏幕。在这种情况下,尽管从技术角度页面已经“加载完毕”,用户的体验却可能是失望的。
相比之下,First Contentful Paint(FCP)更能有效地标示页面响应的初始阶段性能,而结合我们的核心需求,Largest Contentful Paint(LCP)则能更准确地衡量页面主要内容加载完成的时间,从而更真实地反映用户在现实世界中的体验。
因此,我们选择Largest Contentful Paint(LCP)作为网站性能优化的主要指标,并依据研究推荐的第75百分位数(P75)LCP值作为我们的性能衡量目标。
我们迅速认识到,用户的多样化需求需要进行精细化分析和全面考量,因此很难通过单一的标准化方法来准确衡量他们遇到的问题。
在对eBay Touchstone平台的实际优化分析中,我们发现报表数据的收集与分析是用户最频繁使用的核心功能之一。用户在访问报表数据页面时,他们最关心的是报表数据的加载时间,而非页面上其他较大元素的显示。Largest Contentful Paint(LCP)是Chrome浏览器基于页面元素渲染所做的最佳推测,但我们无法确保LCP始终能够准确捕捉到用户获取有用信息的确切时刻,例如报表页面数据完全加载的时间点。这就凸显了采用Key Element Timing(KET)作为辅助性能指标的必要性,以补充LCP的不足。
KET允许开发者和产品团队根据需求,通过设置elementtiming属性来自定义每个页面的关键元素。利用W3C提供的Element Timing API,我们可以获取这些关键元素渲染完成的具体时间点,从而以个性化且精确的方式衡量用户获取最关键内容的时间。这种方法使我们能够更贴近用户体验,为他们提供更优质的服务。
3.如何度量和监测
在前文中,我们阐述了选择Largest Contentful Paint(LCP)和Key Element Timing(KET)作为性能指标的理由。现在,我们将介绍用于度量网站性能的具体方案。
虽然常用的工具如Page Speed Insights(PSI)和Lighthouse能够通过本地测试提供单次评估结果,但它们无法捕捉到线上实际用户的综合性能数据。这种局限性带来了几个问题:
本地性能测试得分仅反映了特定访问环境下的性能,缺乏统计学上的说服力。
当用户报告网站性能不佳或访问速度缓慢时,我们无法复现用户的具体情况,从而难以定位并解决性能问题。
随着项目的持续迭代和新功能的推出,网站性能可能逐步退化。若没有有效的监测机制,这些问题可能不会被及时发现和解决。
经过全面的分析和筛选,考虑到实时性和全面性的需求,我们最终决定采用Google Analytics 4作为性能指标数据的收集平台。通过与Google Tag Manager的结合,我们能够追踪并上报每个实际用户的性能数据及其上下文信息。我们利用直观的图表监控LCP和各页面KET的第75百分位数(P75)值,这些数据将指导我们的性能优化策略,并帮助我们监测优化效果。
03
优化方案与架构
1.前端实践
利用收集到的性能数据,我们对每个高频访问页面进行了系统性的性能评估和瓶颈分析。在网站的加载过程中,性能瓶颈可以归类于以下四大问题:网络调度耗时、静态资源加载耗时、数据传输的耗时,以及页面的渲染耗时。为了给读者提供有价值的实践指导,接下来我们就这四个方面从前端技术角度阐述我们相应的优化方案。
网络调度
该阶段的延迟问题是HTTP1.x必然会遇到的,主要体现在并发请求时的资源等待和网络协议初始化的耗时。
1.优化并发限制:在HTTP1.x协议下,浏览器为了避免过度占用服务器资源和网络带宽,限制最多只能同时与同一域名下的6个资源建立连接。这使得高并发多个请求会导致更长的调度等待时间。
为了解决这一问题,我们通过CNAME记录技术将页面初始化时的30多个请求分散到5个不同域名,从而巧妙地规避了浏览器的并发限制。
2.预建连:然而,当请求被分散到多个域名时,每个域名都需要经历DNS解析和建立连接的过程,这些步骤本身也相当耗时。因此,我们不仅要合理地增加域名数量,还要跟常用域名进行预建立连接,具体包括 dns-prefetch和preconnect的技术应用。
Dns-prefetch是一种较轻量级的预解析技术,用于提前解析某个域名。
Preconnect则允许浏览器提前建立一个完整的连接,包括DNS解析、TLS协商,和TCP握手。这样,当浏览器后续请求该域名时,可以直接利用缓存中的结果,无需重复进行这些操作。
需要注意的是,Preconnect应谨慎使用,仅适合于关键域名,因为它是一种资源密集型操作,过度使用可能会导致性能负向优化。在我们的实际项目中,报表数据页面需要等待8个请求完成后才能触发KET值的上报,这些请求会被分配到2个域名。因此我们选择对这两个最关键的域名使用Preconnect进行预建立连接,而对后续的2个较关键域名使用dns-prefetch,HTML代码中我们加入了链接标签对这4个域名进行预建连,而且剩余的域名不进行任何预建连。
静态资源加载
静态资源主要包括Html,JS,CSS,图片等,代码分割。为了降低静态资源的下载和解析时间,我们采取了一系列技术措施,包括代码分割、Lazy Load、CDN托管以及Service Worker的应用。
1.冗余代码清理:随着前端项目架构的升级,我们通过合并和整合操作,清除了约10%的多余代码,这种直接而有效的优化方法,不仅减少了资源体积,也为页面渲染带来了显著的性能提升。
2.代码分割和Lazy Load:与之前将所有代码整合打包成一个1.89MB的单一文件相比,我们利用webpack实现了代码分割,将main bundle减少到了583KB,并利用@loadable/component按需生成多个bundles进行并行加载,这一策略显著减少了首屏加载所需的静态资源体积。
3.Service Worker:理论上,CDN通过将静态资缓存至全球各地的节点,并自动选择与用户访问距离最近的节点提供服务,可以缩短用户与资源间的距离,从而提高访问速度。然而,我们发现海外CDN并未带来预期的性能提升。因此,我们采用了Service Worker技术对静态资源请求进行拦截,并将请求代理至Cache Storage以读取缓存数据。这样一来,所有托管在海外CDN上的静态资源在首次加载后,后续的访问都可以通过Service Worker从Cache Storage中直接读取,实现了稳定且持久的的性能优化效果。
数据传输
在面对数据传输量大的场景时,如何有效降低请求时间是一个普遍且关键的问题。那对于前后端分离的项目架构,前端又能为此做些什么呢?为了解决这一问题,我们采取了时间与空间的权衡策略:
1.减少数据体量:对于数据量庞大的列表页面加载,减少数据体量是首要的优化方向,这可以显著地减少后端的数据处理时间和网络传输时间:数据分页传输、数据压缩、数据结构精简、缩短默认检索时间段。
以eBay Touchstone项目为例,我们优化了原先返回所有属性的对象列表的API,现在仅返回列表所需的19个关键属性,并将默认检索时间从过去1年的数据(4600+项数据)缩短至过去3个月(1800+项数据)。通过这些优化,数据传输量从1.0MB降至206KB,传输时间也更加稳定。
2.数据缓存:我们根据数据的特性和适用场景,通过redux状态管理库、Http缓存、Cache Storage等多个方面降低了数据请求的次数和体量。
a.Redux状态管理库在react应用中广泛使用,它不仅可以管理和共享数据,还可以应用于存储可重用的数据,从而减少各路由间的服务器请求个数。
b.HTTP缓存的具体规范定义不过多赘述,简单来讲可以分为两类:
· 强缓存:缓存有效期内不发起请求直接使用本地缓存的内容
· 弱缓存/协商缓存:本地缓存会发起请求和服务器协商后再使用本地缓存
针对变化频率低、业务不敏感的数据集,我们可以通过设置cache-control、Etag/If-None-Match等HTTP头部来减少服务器请求的频率。
c.Cache Storage是一种Web API,传统上用于支持离线访问和存储常用静态资源,如JS、CSS、图片、字体等。它提供了高级且灵活的缓存策略,例如自定义缓存键、缓存版本控制和缓存大小管理。
在eBay Touchstone的实践中,我们通过时间戳实现增量数据同步,并结合Cache Storage在客户端存储访问频繁的数据。鉴于页面的渲染通常依赖于对实验指标元数据的全量加载(涉及约10,000条记录),为了提高数据传输效率并减少请求所需处理的数据量,我们采取了结合Cache Storage的增量同步缓存策略:每次请求返回的实验指标元数据被保存在客户端的Cache Storage中,并记录下该时刻的时间戳。随后的请求将携带这一时间戳,使得服务器仅需返回自上一缓存时间点之后的增量数据更新。这种增量同步策略显著减少了网络带宽的占用,并因此显著提升了平台的整体性能。
每种的缓存技术都拥有其固有的优势和适用特性,而在实践中,通常推荐采用多种缓存策略的组合,以实现性能的最大化和资源利用的最优化。
页面渲染
页面渲染是指浏览器将HTML、CSS和JS转换为屏幕上可见像素的一系列过程。提升渲染速度的核心在于优化文档对象模型(DOM)和级联样式表对象模型(CSSOM)的构建,以及减少JS执行对渲染过程的阻塞影响。
一方面,为了提高首屏加载性能,我们采用了渐进式渲染策略,优先加载和渲染关键DOM元素,并控制资源加载的顺序,从而减少初始渲染所需处理的DOM节点数量和相关CSSOM的规模。
另一方面,JavaScript的执行和渲染任务共同竞争有限的主线程资源。当JavaScript执行时间过长时,渲染任务可能会被延迟,导致用户体验到页面的卡顿或响应迟缓。为了缓解这一问题,我们不仅采用了代码分割技术来减少单次JavaScript解析执行的时长,还探索了利用Web Worker实现多线程计算,将部分任务移出主线程,从而减少主线程的阻塞,使浏览器能够更有效地响应用户操作。
渐进式渲染:通过异步加载非核心内容,我们的目标是降低最大内容绘制(LCP)和关键渲染时间(KET)。页面中不涉及主体元素或用户关注焦点的部分被视为非核心内容。为了尽快展示核心内容,例如表单列表,我们选择将右侧非关键的历史记录内容延迟加载,直至核心内容渲染完成后再进行。
代码分割:通过将JavaScript代码分割成更小的块,并按需加载,我们可以减少单个脚本的执行时间,从而提高页面响应速度。
多线程:通过使用Web Worker创建子线程,我们可以将计算密集型任务移至Worker中执行,避免阻塞主线程。尽管我们的网站已经通过上述优化实现了性能的显著提升,使得多线程优化策略在当前阶段可能不是必需的,但这一思路仍为未来的性能优化提供了可行的方向。
2.后端实践
性能优化在后端服务的开发中是一个多维度且多方向的任务。在本次实践中,我们将重点分享那些表现最为显著的优化措施,这些措施主要涉及架构演进、数据库优化,以及缓存策略的优化这三个关键领域。
架构演进
Touchstone Service (以下简称为TS) 作为一个实验管理平台,主要提供包括实验的全生命周期管理,权限管理,分层管理,以及实验效果展现等功能。TS实际上面临的是一个将在线事务处理(OLTP)例如实验的管理和配置(Experiment Management,下文简称EM),与在线分析处理(OLAP)例如实验效果的分析和展现(Reporting and Analysis,下文简称RA),融合的复杂应用场景。
在性能要求方面,无论是数据量还是业务复杂性,OLTP和OLAP均展现出独特的需求。OLTP的核心挑战在于在处理复杂业务逻辑时维持低延迟,并确保并发控制、数据一致性、安全性以及系统的高可用性。相对而言,OLAP着重于在处理大量数据的同时,为用户提供灵活而迅速的查询与分析能力。同为了支撑这两个业务场景,我们也需要有相应的后台功能(Background Service, 下文简称BS),网关功能(API Gateway, 下文简称AG), 监控功能(Monitoring & Alerting,下文简称MA)。
问题分析
让我们先观察一下前文提到的TS的五大功能在各类资源需求上的一些特点:
在其架构设计初期,TS与众多发展中的商业实体和组织机构一样,出于快速行动,最小化开销等因素,将五大功能放在一个单体服务架构之下。然而随着用户基数的扩大,实验数量的增加,以及各个功能内部的不断发展,使用单体应用来同时提供这些不同的功能不可避免的导致性能的劣化。譬如我们给用户提供了一个查询实验列表的API,从接受到用户请求,数据库查询,再到返回结果,在一般的情况下是可以在200ms左右就能完成,但实际上,如果一旦在处理用户请求的过程中,一个进行数据完整性扫描的定时任务被唤醒,由于该任务也具有时效性,不能被推迟。大量的CPU时间以及内存需要投入到该工作中,期间还会因为大量的大对象分配触发Full GC. 从而可能会产生高达10s的延迟的尖刺。
以上仅仅是一个例子,从上图中我们就可以发现,单体架构在以下几个关键领域对性能造成了负面影响:
扩展性:资源浪费和分配不合理抬高了横向扩展的成本,限制了必要的资源扩展的可能性。
举例来说,EM是CPU密集型,而与BS相关的功能是内存密集型。但由于两者绑定在同一个应用中,在横向扩展的时候选择的实例必须是同时满足CPU和内存的需求,这样的实例通常会比单独大CPU或者大内存的实例价格要贵很多,但事实上同时把内存和CPU都用到极限的情况是极少的,但相关的资源却不能回收。导致整体的平均CPU利用率(5%)和平均内存利用率都很低(50%) 在数据上无法支持横向扩展的需求以提升服务性能。并且EM本身不依赖于列储存数据库, 为了支持EM的业务扩张而做的横向扩展导致了连接数的增加也是不合理的。使得整体服务不得不运行在一个紧平衡之下,稍有风吹草动,性能就会波动。
资源隔离:功能之间因为资源共享而造成相互影响。举例来说,由于BS服务中一些周期性的预热任务比较重,在调度执行的时候会占用大量的内存资源,同时也影响到整个GC的稳定性,造成面向客户的AG, EM和RA产生性能波动。
故障隔离:与性能瓶颈类似,由于对稳定性和可靠性的要求不同,与面向客户的功能相比,后台的一些非核心功能例如监控或者预热(BS + MA),因为有较多的第三方依赖(例如eBay内部的监控,应用信息,更新管理,邮件等),且没有足够的失效安全的保障,如果发生故障,往往会波及核心功能的性能。
部署影响:涉及五类功能的每一个改动无论是否涉及核心的功能都会触发整体的部署,频繁的部署会影响集群中能够稳定提供服务的机器数量。因为第一点,服务本身也没有太多冗余,导致性能产生波动,为此我们也收到过不少客户反馈。
服务拆分
这个时候我们就需要考虑对我们的架构进行升级,来满足我们对于扩展性,资源隔离,故障隔离,独立部署的需求,从而优化我们的服务性能,但同时我们也希望能减少一般的微服务在运维,数据,和通信上的复杂度,在一番权衡之后,我们选择了“宏服务”。顾名思义,这是一种同时区别于单体和微服务的折中方案。在核心的设计思路上我们主要考虑以下几个架构原则:
以资源使用,数据和重要性为粒度来划分服务。
减少服务之间不必要的通信,控制系统的复杂性。
如非必要,勿增实体,有效控制服务的数量,确保整体的运维成本可控。
计算和存储分离,共享储存,减少数据不一致的可能性和复杂的数据校验逻辑。
基于这些原则,我们最终的架构图大致如下:
在架构迁移之后,上述的查询实验列表的API就只会单独运行在EM上,由于EM上不再有大对象和超长运行时间的任务,GC的频率和停顿时间得以明显的降低,高百分位的延迟得到了极大的优化。另一方面EM在达到资源瓶颈后可以更容易且快速的横向拓展来提升性能,不需被如列储存连接数或是内存使用率等条件所限制。同时因为没有冗余的功能,可以只根据服务本身的特性选择部署的时间和节奏,EM和RA相关API的整体性能波动和客户反馈也减少了80%。
缓存优化
缓存优化是为了提高数据访问速度和系统性能,减少对数据库的直接访问,降低网络传输和数据库负载,同时也减轻应用层的工作量,从而提高应用的响应速度和用户体验。缓存是我们提升用户在分析实验效果数据时性能体验的关键环节。接下来文章会从问题分析,缓存的技术选型,缓存大小,缓存策略等几个方面展开。
问题分析
在我们引入缓存层之前,在对行为数据进行批处理之后,实验效果的原始数据被输入到Hbase保存。RA会直接请求Hbase来获取数据。这里面存在三个问题:
HBase本身不支持二级索引,这使得adhoc查询变得复杂和低效。
在查找最新数据点对应的时间戳时,涉及到Scan的操作,由于Rowkey设计的局限性,导致涉及到多个RegionServer的数据传输。频繁的Scan操作也对内存造成压力,引发GC。在并发高的情况下,造成性能瓶颈。
在Hbase查询之后,我们需要在service内部进行一些进一步的统计学指标的计算,这些计算本身也会占用相当的延迟和资源。
所以我们决定引入一层API级的缓存来解决上述的问题。
技术选型
这层缓存如果只是各自存在本地,在高并发的情况下会有数据一致性的问题,如果通过复杂的同步策略来进行同步,不但增加了系统复杂性,而且也难以扩展。作为一个业务团队,我们选择把这部分和业务无关的功能剥离出来,利用NuKV(eBay的in-house KV 数据库)作为一个缓存层来实现。
缓存大小
因为缓存内容在数据处理流程中的位置越靠后,条件越复杂,需要缓存的数据量也往往越大。为了保证整体的缓存资源的大小是可控的,我们需要进行有选择的缓存。在决定我们的整个缓存大小的时候,我们注意到在实验效果分析这样一个场景中有着以下的特点:
对于实验效果的查询日志进行统计分析也体现出长尾效应,70%的情况下,即使是adhoc的查询,用户也只会使用一个很小的查询条件集合来获得实验效果。
实验的类别,执行时间,执行对象等元数据都能够帮助来判断这个实验本身的重要性,相应的也能决定实验效果的重要性。
基于以上的特点,我们自然的可以把“所谓”adhoc的查询进行冷热分类,对于少量的热门的查询使用更昂贵的缓存资源,对于长尾的冷门查询使用更经济的缓存资源。
缓存策略
针对热门和冷门查询,我们采用了不同的预热,过期和一致性策略来解决穿透,雪崩,击穿和一致性问题。在对热门查询和冷门查询分别使用不同的缓存策略之后,我们的API的端到端 P75 减少了92%, P90 减少了97%。
热门查询
预热和过期
实验效果的结果往往是一个比较大的数据(最大可达500KB),如果我们在每次查询之后再将数据透写到缓存中,但由于数据量以及网络的问题,在冷启动的时候会对延迟造成不小的影响,性能存在较大波动。在缓存过期时也可能造成缓存击穿。同时多个生产者也会发生冲突,需要分布式的同步解决方案,这也给系统带来复杂度,让查询本身负担了太多不相干的任务。考虑到实验效果的更新只会发生在每次批处理产生新的实验效果之时。最终我们选择通过结合事件驱动和定时任务进行异步的预热,保证这些热门的查询都能常驻缓存。
第一部分是事件驱动的,主要是为了保证缓存的时效性,实验效果的信息的过期只会发生在实验效果生成或者更新时,故当后端的批处理完成了信息加工和持久化后会给消息队列插入一条通知消息,BS在监听到新的消息后进行消费并对缓存进行刷新和预热。这类缓存的过期时间会设置在实验效果的更新频率。
第二部分是定时任务驱动的,对于第一部分的缓存,如果过期时间到了,缓存会被驱逐,导致性能下降。如果设置成永远不过期,会造成资源浪费。考虑到我们使用的缓存平台NuKV对于批量删除和获取全量信息的支持比较有限,我们选择使用定时任务来对本身不再更新,但是又需要支持查询的实验效果信息进行缓存的预热,保证热门查询的缓存是常驻的。
一致性
一方面缓存刷新是事件驱动的;另一方面为了避免缓存更新冲突,我们对键值本身做了隔离,保证同一时间对于某个特定的键值只有一个生产者以避免缓存更新冲突。
以下是大致的架构图:
冷门查询
预热和过期
就如我们上文所说,以上的解决方案可以解决我们之前实验效果查询所面临的大部分热门查询的问题。但是对于以上系统未能覆盖的冷门查询的问题,我们也不能完全置之不理,因为即使是冷门请求,在一个很短的时间周期内也可能是”热门“的。
一个常见的例子是用户可能会在一个会议上将实验效果数据分享给同事或者相关人员。用户对于复杂的查询或者低频的查询的延迟天然有着更高的忍耐度,但是当他们增加查询频率后,这个查询对他们的重要性无形当中也得到了提升,对此我们也需要进行对应的优化。但是总的来说,由于这是相对不常见的查询,我们可以分配较少的资源以实现适当的性能和数据一致性,在这种条件下,之前那些本地缓存的缺点也变的不是那么难以接受了,我们可以将查询结果透写到本地内存缓存,设置较短的过期时间,命中时刷新过期时间。
一致性
一方面我们设置了较短的过期时间;另一方面我们配置路由将相同的请求尽量打到同一台机器上,并且在同一个服务内部的透写过程进行加锁控制,保证并发处理请求的时候不会造成冲突。
数据库优化
数据库优化是通过调整数据库结构和设置,提高数据库性能和查询效率的过程。
历史问题
通过对自身整体服务内查询的整理和分析,我们大致归纳出的主要问题有:
某些请求本身是存在可以合并处理的条件,但是却在实现上单独请求,造成了CPU和IO的浪费,也降低了性能。例如,当创建一个实验的功能开关,如果这个开关要应用于美国,英国等10个地区和网页端,安卓端,IO端等4个频道,需要在一张表上插入40条数据,这完全可以合并成一个调用。
某些表的数据量过大导致查询效率降低,例如我们有一个实验相关操作记录表,就是因为较多的历史数据导致查询速度受到影响。
某些查询缺少合适的索引,存在较多NULL字段的使用,很多查询优化无法使用,降低了查询效率。
解决策略
对此,我们主要采取的策略是:
批处理:将那些从业务和逻辑角度可以合并的单独的请求进行合并批处理。
数据清理:数据库的数据清理对于性能的影响是非常重要的。当数据库中的数据量过大时,查询操作可能会变得非常慢。我们通过定期清理不再需要的数据,总计减少了1.5 Million条数据,从而提高查询性能。
索引优化和数据表设计优化:我们服务大多数的业务都是对读的性能要求大于写的,通过优化数据表的设计,减少NULL字段的使用,新增合理的索引来大幅提高查询性能。
04
总结与展望
本文详细阐述了eBay Touchstone团队在网站性能优化领域的综合实践。团队依据用户需求定义和采集性能指标,并针对具体的业务场景及项目现状,筛选并实施了合适的优化措施,从而实现了网站访问速度的显著提升。
随着业务的不断扩展和功能的持续迭代,项目代码的复杂度逐渐上升,这可能会对网站的性能造成负面影响,更多需要我们做的是:致力在性能遇到瓶颈之前,利用技术手段将网站性能提前优化至符合预期的水平。
未来我们计划探索更多创新的技术解决方案,例如将Lighthouse或Page Speed Insights API集成到持续集成(CI)流水线中,以便自动生成性能报告并进行分析,实现对潜在性能问题的“事前”监控和预防。此外,我们还将考虑引进OLAP作为查询的底座,解决缓存空间和冷启动问题之间的矛盾,减少延迟毛刺,进一步提升90百分位数(P90)查询性能,以为用户带来更加流畅的体验。我们希望这些实践和思考能为广大读者提供更多的参考和灵感。
END
标注:
https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming
https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event
https://web.dev/articles/lcp
https://pages.github.corp.ebay.com/muse/muse-site/blog/2020/04/07/altus-ui-update-4#service-worker-is-enabled-for-plugin-static-assets
https://wicg.github.io/element-timing/#sec-intro
https://developer.mozilla.org/en-US/docs/Web/API/PerformanceElementTiming
https://files.devnetwork.cloud/APIWorld/presentations/2019/Kevin_Hyland.pdf