站点的第一个字节的时间(TTFB)是从用户开始导航到他们请求的页面的HTML开始到达的时间。在我运行WebPageTest的十多年里,缓慢的TTFB一直是我的痛苦之源。
Looking at a recent test data set (~100k pages):
20% TTFB > 3s80% start render > 5s (10% > 10s)500 pages were > 15MB
So much slow to fix
— Patrick Meenan (@patmeenan) August 10, 2016
为什么TTFB是WebPageTest给网站打分的少数几个“评级”之一,尤其是为什么它排在首位,这是有原因的。
如果第一个字节很慢,那么其他所有的指标也会很慢。改进它是少数几种可以预测对其他度量方法的影响的情况之一。TTFB的每一毫秒改进都会直接转化为其他测量方法下一毫秒节省(即,如果TTFB改进了500毫秒,那么第一次显示将快500毫秒)。也就是说,一个快速的TTFB并不保证一个快速的体验,但是一个缓慢的TTFB确实意味着一个缓慢的体验。据我估计,在所有关于WebPageTest结果的求助请求中,大约有50%来自那些在TTFB速度较慢的情况下苦苦挣扎的网站所有者。
TTFB包含许多内容,包括重定向、DNS、连接设置、SSL协定和实际的服务器响应时间。通过使用Cloudflare这样的服务,大多数问题都可以相对容易地解决,但是HTML本身的服务器响应时间往往是最大的问题,也是最难解决的问题。
下面的瀑布图将服务器响应时间显示为第一个请求时间上的浅蓝色条,当响应速度较慢时,它的影响就非常明显。在最佳条件下,服务器响应时间不会超过其前面的橙色套接字连接条。
服务器响应超过3秒钟
从服务器配置、系统负载、后端数据库和与之通信的系统到应用程序代码本身,各种各样的问题都可能导致源响应时间较慢。要找到性能问题的根源,通常需要Dev Ops工程师团队使用应用程序性能管理工具来跟踪应用程序中最慢的部分并改进它们。
与我合作过的网站所有者中,有很大一部分人没有资源或专业知识来进行这种调查。通常情况下,他们会向某人支付一次性的开发费用来构建他们的站点,或者自己在WordPress上构建站点,然后用他们能找到的成本最低的主机托管这些站点。主机的设计通常是为了运行尽可能多的站点,而不一定是为了达到最高的性能。
HTML的边缘网络缓存
问题是,大多数HTML并不是真正动态的。它需要能够在网站更新时相对快速地进行更改,但网络的大部分内容在一段时间内都是静态的,通常会保持数月或数年。
在某些特殊情况下,比如用户登录(作为管理员或其他身份)时,内容的需要有所不同,但绝大多数访问都是匿名用户进行的。如果HTML可以直接从边缘网络缓存并提供服务,那么性能将会有很大提升(在本例中,所有指标都会快3秒)。
服务器响应时间快得多
WordPress有几十个插件可以在初始位置缓存内容,但是它们需要进行配置(在哪里缓存页面),而且性能在很大程度上取决于主机本身的性能。将内容缓存进一步推到边缘网络可以降低其复杂性,消除返回原点的额外时间,并完全消除托管性能。通过卸载所有的匿名流量,它还可以显著降低主机系统上的负载。
Cloudflare支持缓存静态HTML,企业和企业客户可以通过启用“绕过cookies缓存”来允许登录用户跳过缓存。它与WordPress的Cloudflare插件协同工作,因此无论何时更新内容,缓存都可以被清除。还有一些其他缓存插件可以与各种CDN集成,但在所有情况下,它们都需要配置CDN的API密钥,并且这些插件的启用各特定于单个CDN。
HTML的零配置边缘网络缓存
为了广泛应用缓存,我们需要使HTML的缓存自动发生(或尽可能接近自动)。为此,我们需要一种方法来在源(如WordPress站点)和边缘网络缓存(如Cloudflare的边缘节点)之间进行通信,以便管理可以明确清除的远程缓存。
网页源需要做到:
识别前面是否有支持的边缘网络缓存。
指定应该缓存的内容,以及访问对象(即访问时没有登录cookies)。
当缓存内容发生更改时(全局跨所有边缘)清除缓存内容。
不需要原始数据与API联合来清除更改,也不需要手动配置需要缓存什么,当我们可以使用HTTP请求头来完成在边缘和原始数据之间来回传输的请求时:
1- 为边缘到源网站的请求添加HTTP请求头,以表明存在边缘网络缓存及其支持的功能:
x-HTML-Edge-Cache: supports=cache|purgeall|bypass-cookies
2- 当源站点响应一个可缓存的页面时,它会在响应上添加一个HTTP请求头,以表明它应该被缓存,以及何时不应该使用缓存版本的任意规则(允许绕过已登录用户的缓存):
x-HTML-Edge-Cache: cache,bypass-cookies=wp-|wordpress
在这种情况下,HTML将被缓存,但任何任何有cookies的请求,以“wordpress”或“wp-”开头的,都将绕过缓存并转到源站点。
3- 当一个请求修改了网站内容(更新帖子,改变主题,添加评论),源站点会添加一个HTTP请求头,指示缓存需要被清除:
x-HTML-Edge-Cache: purgeall
唯一需要处理的棘手部分是purge(清除)模块需要清除所有边缘网络的缓存,而不仅仅是清除请求经过的那个边缘网络。
4- 当边缘网络缓存中的HTML接收到新请求时,边缘网络根据缓存响应的规则检查请求cookies。如果cookies不存在,则缓存版本将被提供;否则,请求将被传递到源站点。
通过这个简单的基于请求头的命令和控制接口,我们可以消除源站点与API的对话以及任何显式配置的需要。由于没有配置(或UI),也不需要向特定于供应商的API发出任何出站请求,因此它还大大简化了原始逻辑的实现。Word Press插件的例子只有不到50行代码,其中绝大部分是为所有更改内容的事件联结回调指令。
立即使用WordPress和Workers开始缓存
我最喜欢Workers的一个点是,它给了你一个完全可编程的平台,可以试验各种想法并实现自己的逻辑。我创建了一个Worker脚本,它可以在Cloudflare边缘网络以及一个WordPress插件(用以实现WordPress的原始逻辑)上实现基于请求头的协议和边缘网络缓存。
这个Worker唯一棘手的部分是我们需要找到一种方法全局地从缓存中清除内容。Worker的缓存是每个边缘网络的本地缓存,不提供执行任何全局操作的接口。它的一种方法是使用Cloudflare API清除全局缓存,但这有点麻烦(清除缓存中的所有内容,包括脚本和图像),它需要进行一些配置。如果您知道变动的内容将会更改特定的url,那么通过API针对这些url进行定向清除可能是最佳的解决方案。
使用新的Workers KV商店,我们可以以一个不同的方式清除缓存。Worker脚本使用缓存的版本控制方案,其中每个URL都附加一个版本号(例如http://www.example.com/?cf_edge_cache_ver=32)。修改后的URL只在本地被工作人员用作缓存响应的键,当前版本号存储在作为全局存储的KV中。清除缓存时,版本号会递增,这将更改所有资源的URL。旧条目通常会移出缓存,因为它们将不再被访问。这种方式需要一些配置来为Worker设置KV,但我们希望在未来的某个时刻这也可以是自动实现的。
接下来是什么?
我认为,对于web来说,将边缘缓存和源站点的通信方式标准化从而缓存动态内容是具有巨大价值的。这将激励内容管理系统直接在平台上构建支持,并提供可在不同提供商之间使用的标准接口(甚至可用于负载平衡器或其他反向代理中的本地边缘网络缓存)。在对不同类型的站点进行更多测试后,我计划将该概念引入IETF HTTP工作组,看看我们是否可以为控制请求头提出官方标准(使用不同的名称)。如果您对它应该如何工作或需要公开哪些功能有意见,我都会很乐意地听取(比如清除特定的URL,移动/桌面或区域的不同内容,扩展它以涵盖所有内容类型等) 。