Reading view

There are new articles available, click to refresh the page.

本站改版后的架构与优化——Hexo,分区解析,CDN,图像压缩等

去年,我把我的网站从 WordPress 迁移到了 Hexo。随后的一段时间里,我对 Hexo 进行了大量定制,包括修改主题,实现图片自适应,适配视频等。

基础架构

本站使用 Hexo 博客框架生成静态网页,使用 Nginx 服务器分发静态网页、图片等。本站的静态文件和图片同时部署在全球四台服务器上,使用 Route 53 实现了分区解析和宕机后自动切换,使用 CloudFront 和 UPYUN CDN 分发视频。

主题

本站主题是在 Claudia 的基础上进行定制与魔改。原主题就有如下功能:

  • 移动端适配
  • 评论插件集成
  • 页面边栏 (个人信息,最近文章等)
  • 自适应设备的深色模式
  • 目录自动生成
  • 现代化的设计语言
Claudia 主题封面

我对其增加的功能主要如下:

图片

  • 自动为文章中图片增加 srcset 和 sizes 属性
  • 支持全屏展示
  • 支持全屏后缩放,并按需加载缩放后的分辨率
  • 移动端 100vw 宽度无边距
  • 自动将图片转化为 <figure> 并展示图片描述

视频

  • 自动解析 <iframe>src= 的内容并将其放在 srcdoc= 中减少网络请求
  • 可以实现在首页显示视频预览并自动静音播放(集成了 HLS.js

搜索

原本主题的搜索功能在电脑端只会在最右侧的边栏展示搜索结果,我对其进行了改进,实现了用户在搜索时搜索栏和搜索结果自动延长。各位可以直接前往首页体验,或者查看下方的效果视频:

网站搜索演示

分区解析与 CDN

我的网站目前同时部署在国内外多个主机上,使用着相同的配置。域名使用 Route 53 的延迟记录进行分区解析,并开启了 “运行状况检查” 实现宕机后自动切换服务器目前本站使用了 5 个服务器,分别部署在北京、东京(日本)和拉斯维加斯 (美国)、蒙特利尔 (加拿大) 和法兰克福 (德国)。

选择使用 Route 53 作为解析服务器的原因是:

  • 它使用了 Anycast 技术,在全球访问速度都很快
  • 国内连接 Route 53 的 DNS 服务器可以连接到延迟小于 50ms 的日本节点,
  • 支持 DNSSEC 和 IPv6
  • 支持按地区的分区解析、延迟解析等。在美国可以细分到州,其他地方也可以细分到国家
  • 支持 “运行状况检查”
  • 按量计费。起步价每个域名 $0.50 /月,“运行状况检查” 每个服务器 $0.50 /月。上述的所有功能也均是按量付费,没有固定月费。

视频 CDN

本站视频虽然是使用 Cloudflare Stream 存储和转码,但实际分发使用的是 CloudFront 和阿里云 CDN。主要是 Cloudflare 的视频是按量计费,其单价比 CloudFront 要贵。其次是 Cloudflare Stream 不提供国内节点,因此为国内提使用阿里云 CDN。

双 CDN 配置——Nginx 替换 CDN URL

实现双 CDN 有两种方法,第一种是使用 Nginx 根据访客国家替换 CDN URL。这样做相比分区解析的好处是即便用户配置的 DNS 服务器非用户所在地服务器,替换 CDN URL 方式依然会让用户使用正确的 CDN。可以采用了 Nginx 根据客户端 IP 的国家进行 CDN 域名的替换,具体配置——在 http 中:

geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {    auto_reload 1d;    $geoip2_data_country_code default=CN country iso_code;}map $geoip2_data_country_code $tlo_domain {    default    "tlo.xyz";    "CN"       "tloxygen.com";}

server 中:

sub_filter '//videodelivery.net/' "//video.${tlo_domain}/";

这样一来,用户访问网站时,服务器会根据用户访问网站时的 IP 地址(而非 DNS 提供的 IP 地址)来选择合适的 CDN。

双 CDN 配置——分区解析

这就不用多说了,将两个 CDN 绑定在一个域名上,使用 GeoDNS 对不同地区的访客返回不同的结果。

图像 CDN

本站的图片均使用 Cloudflare Images 进行压缩与存储,并通过 Nginx 进行代理与缓存。Nginx 配置如下,实现了根据设备兼容性优先提供 AVIF、WebP 和 JPEG。这是因为 Cloudflare Images 在国内的速度不佳。此外 Cloudflare Images 可以在访问时自动将图片调整分辨率和格式。当用户访问相对路径 /cdn-cgi/imagedelivery/ 时,网站就会加载相应的图片,还省去了与新的域名建立 HTTP 连接的时间。

Nginx 配置

http 中:

map $http_accept $suffix {    default        "jpeg";    "~image/avif"  "avif";    "~image/webp"  "webp";    "~image/apng"  "apng";}map $http_accept $png_suffix {    default        "png";    "~image/avif"  "avif";    "~image/webp"  "webp";    "~image/apng"  "apng";}map $http_accept $gif_suffix {    default        "gif";    "~image/avif"  "avif";    "~image/webp"  "webp";    "~image/apng"  "apng";}map $http_accept $default_suffix {    default        "default";    "~image/avif"  "avif";    "~image/webp"  "webp";    "~image/apng"  "apng";}map $sent_http_Content_Type $file_name {    default        "default";    "image/avif"   "avif";    "image/webp"   "webp";    "image/apng"   "apng";    "image/jpeg"   "jpeg";    "image/png"    "png";    "image/gif"    "gif";}

server

proxy_store /var/www/images$uri/image.$file_name;location /cdn-cgi/imagedelivery/6T-behmofKYLsxlrK0l_MQ/ {    root /var/www/images/;    try_files $uri/image.$suffix $uri/image.$png_suffix $uri/image.$gif_suffix $uri/image.$default_suffix @proxy;}location @proxy {    proxy_ssl_name imagedelivery.net;    proxy_ssl_server_name on;    proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;    proxy_set_header Host imagedelivery.net;    proxy_set_header Upgrade $http_upgrade;    proxy_set_header Connection upgrade;    proxy_hide_header Upgrade;    proxy_hide_header Alt-Svc;    proxy_hide_header Expect-CT;    proxy_http_version 1.1;    proxy_set_header Connection "";    if ($uri ~* '^https://cdn.tlo.xyz/6T-behmofKYLsxlrK0l_MQ/(.*)$') {         add_header X-Cache-Status "MISS";        proxy_pass https://cdn.tlo.xyz/6T-behmofKYLsxlrK0l_MQ/$1;        break;    }}

通过 proxy_store,图片将会在第一次请求后永久的存储在本地,并在下次访问时从本地提供。在存储时,图片的变体名和格式会作为文件名的一部分进行存储。在访问时,服务器会根据客户端发送的 http_accept 请求头去查找对应文件。

AVIF 格式比 WebP 的压缩效率更好,而 WebP 格式比 JPEG 的压缩效率更好。然而对于兼容性而言,JPEG > WebP > AVIF。尽量不要在网站上使用 GIF,如果需要展示动画,可以使用静音自动循环播放的 <video> 代替。

WebP/AVIF 自适应之 CDN 配置

如果原站支持 WebP/AVIF 自适应,那么在配置 CDN 的时候,需要选择根据客户端的 Accept 头进行缓存。CloudFront 和阿里云 CDN 的配置分别如下:

CloudFront Accept 配置阿里云 CDN Accept 配置

自动部署

本站使用 GitHub Action 在服务器上运行 SSH 脚本实现自动部署。在服务器上配置了 post-merge 的 git hooks,在有改动后自动运行脚本。具体来讲,是在一个部署专用的服务器上生成静态页面,然后将这些静态页面分发到对应的服务器。

统计

本站使用自建的 Matomo网站统计。Matomo 是一个基于 PHP 和 MySQL 的非常强大的开源统计软件。

Matomo 后台管理界面截屏

除此之外,本站还通过 JavaScript 实现了对视频播放,图像查看和搜索的统计。本站还使用了 Nginx 的 post_action 功能实现了异步发送统计,具体在 server 中的配置如下:

location /api/a {    post_action @tracker;    return 204;}location @tracker {    internal;    proxy_pass https://origin/matomo.php$is_args$args;    proxy_set_header Upgrade $http_upgrade;    proxy_set_header Connection upgrade;    proxy_set_header Host matomo.tloxygen.com;    proxy_set_header TLO-Connecting-IP $remote_addr;}

HTTP/2 Server Push

本站还启用了 HTTP/2 Server Push,用户在访问网站时,服务器会一次性直接推送首屏渲染必要的 CSS 和 JS 文件,由于用户不需要在获取到 HTML 页面后再去获取 CSS 和 JS 文件,首屏渲染的时间大大缩短。实测在模拟 Fast 3G 环境下,启用 HTTP/2 Server Push 后首屏时间减少了约 0.3 秒:

仅启用 HTTP/2,首屏耗时 1.48 秒启用 HTTP/2 Server Push,首屏耗时 1.20 秒

具体 Nginx 中 server 的配置如下

listen 443 ssl http2;listen [::]:443 ssl http2;location ~* \.(?:html)$ {    http2_push /style/common/bulma.css;    http2_push /style/base.css;    http2_push /style/common/helper.css;    http2_push /style/post.css;    http2_push /style/widget-header.css;    http2_push /style/widget-post-list.css;    http2_push /style/themes/highlight-theme-light.css;    http2_push /js/common.js;    http2_push /js/sdk.latest.js;    http2_push /js/post.js;}

使用 AMP 构建超快的移动页面

AMP (Accelerated Mobile Pages) 是 Google 的一个开源的项目,通过使用 AMP HTML,可以极其显著的提升网页加载速度。最近我在移动设备上使用 Google 的时候,发现很多网站都开始使用了 AMP。在从 Google 上进入一个 AMP 页面时速度更是异常的快,快到超乎想象——延迟几乎为零。本站也配置了 AMP,本文对 AMP 的一些特性进行一些分析,并介绍如何在 WordPress 上支持 AMP,并开发插件对其自定义。

AMP HTML

AMP HTML 也是一种 HTML,一个符合标准的 AMP 页面只能使用有限的标签,而且很多标签都并非 HTML 标准,如 <amp-img><amp-video> 等。 一个 Hello, AMPs 的例子:

<!doctype html><html amp lang="en">  <head>    <meta charset="utf-8">    <script async src="https://cdn.ampproject.org/v0.js"></script>    <title>Hello, AMPs</title>    <link rel="canonical" href="http://example.ampproject.org/article-metadata.html" />    <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">    <script type="application/ld+json">      {        "@context": "http://schema.org",        "@type": "NewsArticle",        "headline": "Open-source framework for publishing content",        "datePublished": "2015-10-07T12:02:41Z",        "image": [          "logo.jpg"        ]      }    </script>    <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>  </head>  <body>    <h1>Welcome to the mobile web</h1>  </body></html>

此代码可以在主流浏览器中直接运行,和普通的 HTML 相比,它有以下区别:

  • AMP HTML 在 <html> 中增加了 amp 属性,来标记这是个 AMP 页面
  • <head> 中必须引用一个 JS:<script async src="https://cdn.ampproject.org/v0.js"></script>
  • 需要有amp-boilerplate 属性的 style,其中一个在 <noscript> 中。
  • <head> 中需要有一个 <script type="application/ld+json"> 去标记文档的 metadata。

在 AMP HTML 中插入一个图片的例子:

<amp-img src="450px.jpg" srcset="450px.jpg 450w, 1200px.jpg 1200w"></amp-img>

会发现其实这和普通的 img 标签的语法没有什么区别,只是换了一个名字,实际上通过这样引入图片,AMP Runtime(就是在头部所引用的 v0.js) 就会自动地解析这个 AMP Components,并会根据设备的分辨率去选择一张图片加载(原理只不过是通过 js 再创建一个 img 标签去让浏览器识别),甚至都不需要浏览器去支持 srcset 属性;而且还能够自动的延迟加载图片。插入视频和音频同样也需要使用 AMP 特定的方法去做。如果浏览器禁用了 JS,那么网页上的所有图片和视频等使用非标准 HTML 标签的内容都无法显示。

在 AMP 中引入更多的交互

AMP 中禁止使用任何自己的 JS,这样做只是为了保证 JS 的加载速度——因为移动设备的性能不高,尽量减少 JS 或避免使用低质量 JS 的使用能够提高用户体验。所以,如果想使用更多的交互,那么必须通过 AMP HTML Extensions 的功能来实现,所以只能使用有限的交互功能。或者可以通过纯 CSS 来实现各种复杂的功能。

验证 AMP

这个网站上可以在线对 AMP 页面进行验证

为何那么快?

你其实会发现,网页中可能会阻碍加载的内容只有一个外部的 JS(也就是 AMP Runtime),而且这个 JS 还被标记上了异步加载。所有的 CSS 都是 Inline 的。

在国内的应用

AMP 所必须要引用的 AMP Runtime 这个核心 JS 必须要使用 Google 提供的那个特定的域名,现在 Google 已经针对中国解析到了中国服务器,中国访问还挺快! 现在,我发往微信公众号上的文章就使用了 AMP 技术,直接推送 AMP 版本的网页,放弃使用微信素材库,实际测试加载速度比微信素材库的文章还要快。并且由于 AMP 页面适合缓存,我也针对中国进行了缓存的优化。

在自己的网站上实现 AMP

为了支持 AMP,需要让每个文章拥有两个页面:一个正常版本的页面,一个符合 AMP 标准的 AMP 页面。当然,也可以通过直接修改页面本身让其符合 AMP 规范,只不过这种方法做的改动会比较大。最简单实现 AMP 的方式就是通过插件的方式去实现,尤其是如果你正在使用 WordPress 的话:

在 WordPress 上实现

Automattic(其实就是 WordPress 官方)制作了一个 AMP 插件,安装并激活这个插件之后,无需任何配置——这是我喜欢这个插件的原因之一,就能够激活 AMP 功能了。这个插件会自动生成每个文章的对应页面,并会相应的在原始页面添加 AMP 的 metadata。安装后表面上没有任何变化,但是当在一个文章页面后添加上 /amp/ 或者 ?amp=1 后,就能看到文章对应的 AMP 页面了。 这个插件之所以免去任何配置,是因为它实际上所提供的功能只是一个框架。用户(实际上是开发者)可以自己开发一个插件对其进行自定义。官方已经给出了具体的介绍,我列举一些我所自定义的东西:

使用无衬线字体,不引用 Google 字体库

这个插件默认使用了一个衬线字体,然而对于中文的使用引用外部字体毫无作用,甚至还会导致中英文字体错乱的问题。 下载这个 CSS,找到:

font-family: 'Merriweather', 'Times New Roman', Times, Serif;

替换为:

font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen-Sans", "Ubuntu", "Cantarell", "Helvetica Neue", sans-serif;

把修改后的文件保存在你制作的插件的文件夹中的 templates目录(如果没有,就创建一个)下的 my-amp-style.php。 在插件中添加下面代码:

add_filter( 'amp_post_template_file', 'tlo_amp_set_custom_template', 10, 3 );function tlo_amp_set_custom_template( $file, $type, $post ) {    if ( 'style' === $type ) {        // Use stystem font        $file = dirname( __FILE__ ) . '/templates/amp-style.php';    }    return $file;}add_action( 'amp_post_template_head', 'tlo_remove_fonts', 2 );function tlo_remove_fonts(){    remove_action( 'amp_post_template_head', 'amp_post_template_add_fonts' ); // Remove Google fonts.}

这将使用系统默认的无衬线字体,在苹果设备上中文字体就是苹方,同时还会移除页面上的 Google 字体。

添加自定义的统计内容

AMP 本来就支持 Google Analytics,但是如果需要添加自己的统计,虽不能通过 JS 的方法,但是最简单的方式是通过添加一个空的 Gif:

add_action( 'amp_post_template_footer', 'tlo_amp_add_pixel' );function tlo_amp_add_pixel( $amp_template ) {    //$post_id = $amp_template->get( 'post_id' );    $piwik_id = $GLOBALS\['wp-piwik'\]->getOption( 'site_id' );    ?>    <amp-pixel src="https://piwik.example.com/piwik.php?idsite=<?php echo $piwik_id; ?>&rec=1&action_name=TITLE&urlref=DOCUMENT_REFERRER&url=CANONICAL_URL&rand=RANDOM"></amp-pixel>    <?php}

这是一个使用 Piwik 工具进行统计的例子,与 WP-Piwik 插件配合,可以自动获取站点 ID(需要手动替换域名)。

实际测试(视频)

在激活了 AMP 一段时间后,使用移动设备在 Google 上搜索网站的内容,就可以从 AMP 中显著获益了,视频右侧包含了资源列表,使用的是 iOS 10 自带的 Safari 浏览器(不要眨眼!):

可以看到,还在停留在搜索结果的时候,AMP Runtime 等 AMP 内容就已经开始下载了,同时开始下载的还有我网站上的 Logo 以及网页中的第一张图片。 在点击第一条搜索结果时,页面没有任何重新加载的迹象,直接通过类似 ajax 的方式呈现出来,此时整个页面已经预加载完毕,所以加载时间几乎为零(由于使用的是 iOS 模拟器,配置较低,实际真机测试白屏时间更短,可以说是毫无感知)。页面顶部可以看到网址域名。在滚动页面时,可以看到之后的几张图片是延迟加载的,延迟加载的时间恰到好处,几乎很难感受到是在延迟加载。 值得注意的是,这个页面的所有内容几乎都是由 Google 的服务器提供的,包括 HTML 和图像在内(经测试,视频资源依然回源),只有我的统计代码没有被 Google 代理。如果此时分享 URL,那么分享出去的域名仍是 Google 的,在打开时(或者刷新一次页面之后)左上角的关闭将不再有,其余都完全一样。如果这个网址在非移动设备上打开,那么会跳转到原始文章(不是 AMP 的)页面。 点击左上角的关闭按钮之后,就又返回到搜索结果页面了,整个过程十分顺滑。如果搜索结果有多个 AMP 页面,那么进入这些其他的页面也同样秒开。 可以想到,之所以 AMP 做这么多限制,很大一部分原因是为了防止在 google.com 上呈现 AMP 页面时网站所有者 “作恶”,即使浏览器不是 Google 可控的(如 Safari)。

总结

Google 的 AMP 功能非常类似于 Facebook 的 Instant Article,只不过后者甚至都不需要自己去托管服务器。AMP 中大量的 CSS 仍能自己控制,这相比国内绝大多数搜索引擎的转码页面的效果还是要好不少。网页中仍能自己投放广告内容,这也是 AMP 能被接受的那么快的原因。它在各种不开放的限制中也保留了众多开放的东西。 启用 AMP 不仅能在 Google 搜索结果页面中载入获益,AMP 页面本身其实还可以作为专门给移动设备适配的页面——如果网站本身没有移动版页面,那么移动版页面可以直接拿 AMP 做,然后自动跳转。 当然,就像之前说的,网页可以通过直接修改页面本身让其符合 AMP 规范,或者在制作之初就按照 AMP 的规范作,AMP 官网就是一个典型的例子。然而这种做法未免过于激进,兼容性也不是很好(比如其对于 JS 的强烈依赖),使用前应该做好充分考虑。

关于 MIP 的补充

百度从 AMP 借鉴了很多技术并做了 MIP,其与 AMP 十分类似。 经过简单的实验,我发现 AMP 可以很轻松的变为 MIP,只需要将 AMP 页面做以下内容的替换或删除即可:

  • <html amp 替换为 <html mip
  • <script src="https://cdn.ampproject.org/v0.js" async></script> 替换为 <link rel="stylesheet" type="text/css" href="https://mipcache.bdstatic.com/static/v1/mip.css">
  • amp-pixel 替换为 mip-pix
  • amp- 替换为 mip-
  • </body> 替换为 <script src="https://mipcache.bdstatic.com/static/v1/mip.js"></script></body>
  • 删除 <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>

修改完的 AMP 页面可以直接通过 MIP 测试,这相当于不需要二次开发就能够同时兼容 AMP 和 MIP,多方便!启用了 MIP 的网站在百度搜索结果上也会预加载,于是就能实现秒开的效果。

MIP 的一些坑

CSS 的不同

MIP 自带的 CSS 竟然声明了这个样式:

* {    margin: 0;    padding: 0}

由于它的存在,会导致很多地方的排版出现问题,并会覆盖掉浏览器的默认样式(例如 h1、h2 这些标签),所以你可能还要为百度单独添加一些样式。

video 标签的 Bug

经测试,<mip-video> 标签不能够支持 <source> 标签,使用了这种标签的视频无法加载。

几个 WordPress 的加速建议

WordPress 是目前最流行的内容管理系统,本网站正是使用着它。但对于一个全新安装的 WordPress 来说,它的性能并不是很高,当网站的访问量突然增加时,优化性能就显得十分重要了。通过实施以下几个方案,可以大大提升 WordPress 访问速度:

配置缓存

WordPress 是一个动态的系统,如果不配置缓存,每次请求都需要服务器去读取数据库,生成页面内容,对于不同性能的主机,这可能就需要 20ms~1000ms 甚至更慢。如果能够正确配置缓存,就可以明显的加速,并且减少主机的运算资源。

配置页面缓存

使用插件来配置缓存是最简单的方法。在此推荐 WP Super Cache,这是 WordPress.com 出品的缓存插件,就页面缓存来说,功能非常全面,它支持多种缓存模式,包括 mod_rewrite,如果你使用 Nginx,那么可以使用我这个配置文件,这样可以直接跳过 PHP 而直接服务静态文件,页面相应速度有显著提升。 同时,为浏览器返回正确的 Cache-Control 也是十分有必要的,尤其是 CSS 和 JS 文件。

配置对象缓存

对象缓存比页面缓存更灵活,使用范围更广,但速度肯定不如页面缓存。在此推荐 APCu 缓存系统。在 Ubuntu/Debian 安装方法如下:

$ apt install php-apcu

然后重启 Web server,安装 APCu Object Cache Backend 即可。 Redis、Memcached 等都算此类型的缓存,不过 APCu 配置最为简单,速度也很快。

建立分布式缓存系统

比如我的网站使用北美东岸(主要)和亚洲的 VPS,主服务器配置了 Nginx,PHP 和 MySQL;亚洲的服务器只配置了 Nginx。在这些服务器上都配置好缓存,并用 lsyncd 同步缓存内容。每次访问时 Nginx 检查缓存,仅当没有缓存时代理,这样可以大大减少首页面的延迟。

使用 CDN

使用全站 CDN

使用全站 CDN,可以免去在自己的服务器上配置缓存的问题,还可以为服务器增加 HTTPS、HTTP/2 等功能,同时还能过滤非法流量,防御 DDOS(前提是你的 IP 没有被暴露,或者你设置好了白名单)。 除此之外,使用 CDN 后还能更加明显的提高网站加载速度,让访客从中受益。

使用 CloudFlare

CloudFlare 是可以免费使用的,使用 CloudFlare 前需要更改 DNS 服务商,然后无需额外配置就能使用了。但是它只会缓存 CSS、JS 和多媒体文件,不会缓存 HTML 页面,也就是说用户每次访问时还是会返回到原始服务器,页面本身的速度不会有明显提高,在原始服务器上配置缓存也是必要的。

使用 KeyCDN 并配合插件

KeyCDN 是一个按流量使用付费的 CDN 提供商,使用我制作的插件 Full Site Cache for KeyCDN 可以简单的对其配置,这个插件会自动刷新缓存。 KeyCDN 相比 CloudFlare,可以缓存 HTML 页面,大大减少源服务器的压力,同时在刷新缓存时可以通过 Tag 方式刷新,避免不必要的刷新。 在网站访问量较大时,使用 KeyCDN 就能明显的提高速度,缓存命中率也会有很大的提高。

仅资源部份使用 CDN

你可以配置另一个域名,在那个域名上使用 CDN,然后通过插件重写页面地址实现部分 CDN。上文提到的 WP Super Cache 就能配置 CDN,或者使用 CDN Enabler 也能实现部分 CDN 功能。至于 CDN 的选择无所谓,只要支持 Origin Pull(也就是请求回源)就行。

服务器性能

提高服务器本身的性能是最简单的方法,使用更大的内存,更多核心的 CPU 能明显提速。除此之外,提高服务器上应用的性能也很重要:

脚本性能

PHP 脚本的处理速度是 WordPress 的一大瓶颈,使用最新版本的 PHP,可以获得更高的性能,例如 7.0 就比 5.6 快了 3 倍

其次,少用插件能减少 PHP 需要执行的脚本,因为在加载每一个页面时,WordPress 都会加载一遍所有的插件。少量的插件(10个以下)对 WordPress 速度的影响不大,当然也取决于插件本身。

数据库性能

数据库是 WordPress 性能的瓶颈之一,在数据库上优化能提高一定的速度。 一般情况下,如果正确的使用 WordPress,并不需要清理数据库。但可能会有某些插件可能在数据库中创建了太多没用的表,这时服务器的响应速度就会大大降低(约 1~3 倍),推荐使用 WP-Optimize 进行清理。 不是太多的文章数量,是不太会的影响加载速度(1 万篇文章以下速度其实都还能接受,不过你写那么多文章干嘛,质量比数量更重要嘛)。

图片优化

图片占据着网页中很大一部分的大小,同时也关系着用户体验。

图片压缩

对图片压缩不仅可以提高访问时图片加载速度,还能减少服务器带宽 推荐使用免费的 EWWW Image Optimizer,可以在服务器上对图片进行处理。如果你的服务器性能有限,可以使用 Optimus 在线处理,免费版功能十分有限。

响应式图片

对于不同的设备加载不同的图片,比如在手机上加载的图片,就可以比视网膜屏幕的电脑上要加载的图片小的多。使用本站曾经提到过的 srcset 技术可以最简单的实现这个功能,只要你的主题支持就可以了(官方的最新默认主题已经支持),如果主题本身不支持,也可以通过插件实现。

禁用不需要的服务

WordPress 中有一些自带的服务你可能并不需要,禁用它们可以减少页面所需要加载的资源以及服务器需要做的事情。

Emoji 支持

WordPress 支持 Emoji 表情符号,但会因此在页面中引入额外的 CSS,使用这个脚本可以禁用它(如果你不需要的话)。

Google Fonts

Google Font 在国内加载非常慢,而且加载完成之前页面会一直白屏。你可以专门安装 Disable Google Fonts 的插件,或者在下文要提到的 Autoptimize 插件中的设置里禁用它。

Pingback

进入 设置 => 讨论 中可以禁用 “尝试通知文章中链接的博客” 和 “允许其他博客发送链接通知(pingback和trackback)到新文章” 功能(如果你不需要的话)。 此功能并不影响页面加载时间。

减少请求数和页面大小

减少请求数在也一些情况下也能提高加载速度,减少页面大小能缩短下载页面所需要的时间。推荐使用 Autoptimize,它可以 minify CSS、JS 和 HTML,还能 combine CSS 和 JS 以减少请求数。

然而,如果你的博客启用了 HTTP/2 协议,那么减少请求数就没什么必要了,不过为了启用 HTTP/2,必须要使用 HTTPS,所以最终下来是快是慢也不好说。不过还是强烈推荐使用 HTTPS,为了安全,牺牲点速度算什么。

总结

做到上面几点,就能有效提速了,我的网站做到以上几点,在国内无缓存的 Wi-Fi 情况下本网站的时间线如下:

在 1 秒钟内完成包括图片在内的加载

使用 srcset + sizes 属性与 w 标识符解决一切响应式图片问题

使用 srcset 属性可以解决一切响应式图片问题,但这里分为两种情况,确定和不确定宽度的照片。相比之下,不确定宽度的照片更复杂些。

确定宽度的照片

此处指的确定宽度照片,是指样式属性宽度设置为一个确定了多少 px 的照片。 随着越来越多的高设备像素比(指 device pixel ratio,下同)显示器出现,网站需要更高像素的照片来适配这些显示器。 比如有一张照片显示宽度为 200px,它在@1x(即设备像素比为 1 的显示器,下同) 的显示器上,是占了 200 个物理像素(即实际所占的像素,下同);它在 @2x 的显示器上,实际上是占了 400 个物理像素;在 @3x 的显示器上,实际上是占了 600 个物理像素;同样的,在 @4x 的显示器上就是占了 800 个物理像素。 如果这个照片只提供 200 像素的版本,那么在 @2x~@4x 的显示器上看起来就很模糊。如果只提供 800 像素的版本,那么在 @1x~@3x 的设备上会加载不必要的内容,也就意味着更长的不必要时间(尤其是在手机上)。 此时,就需要使用响应式图片这个方法,最简单高效的方式是使用 srcset 来解决。比如现在提供了 4 个宽度分别为 200、400、600、800 像素的照片,通过 srcset 属性,能够实现在 @1x~@4x 设备像素比的显示器上分别显示这 4 张照片,这样在每个显示器上都能显示最适合它的照片。这四张图片的文件名分别为 200px.png, 400px.png, 600px.png, 800px.png。 总结一下,就是确定宽度的照片所占物理的像素只与设备像素比有关,所以只需要 srcset 属性的 x 标识符。

<img src="200px.png" srcset="400px.png 2x, 600px.png 3x, 800px.png 4x">

这样加上 srcset 属性,浏览器就会根据自己的设备像素比来加载不同的照片,不支持 srcset 属性的浏览器就默认加载 200px.png 这张照片。这个方法是向下兼容的。

不确定宽度的照片

此处指的不确定宽度的照片,是指样式属性宽度设置为一个确定了多少百分比的照片。 如果是不确定宽度的照片的话(或者是手机上不确定图片宽度,电脑上确定宽度等),也可以通过 srcset 解决。由于像素是不能确定的,所以很难达到照片本身和照片所占物理像素是一样的,因为用户的窗口大小是不确定的。 如果只准备 5 张照片,宽度分别是 400~3200 像素,文件名是400px.png, 800px.png, 1600px, 2400px, 3200px。通常,如果没有对应图像所占的物理像素的照片的话,则会加载比所占的物理像素稍大一些的图片。例如照片占屏幕的 500 个物理像素,那么就应该选择加载 800px.png。这样加载稍大的照片仍然可以达到最佳的显示效果,同时最大限度的节省网络资源。 总结一下,就是不确定宽度的照片所占物理的像素与设备像素比和所占宽度有关,然而使用了 srcset 属性的 w 标识符后,你只需要指定宽度,浏览器会自动根据设备像素比来选择最优图片。 如果这个图片占整个窗口的 100% 大小(即 100vw 宽,下同),那么只需要这样设置 srcset 就能达到效果。 抛开设备像素比的概念吧,这个全新的 srcset 属性的 w 标识符能够让浏览器自己适应所要加载的照片。

<img src="800px.png" srcset="400px.png 400w, 800px.png 800w, 1600px.png 1600w, 2400px.png 2400w, 3200px.png 3200w" alt="" />

不过此时还有一个小问题,浏览器比较笨,它会认为图片宽度始终是整个窗口的 100% 宽,如果图片不是占整个窗口的 100%,两边留有边框怎么办呢?此时就需要使用 size 属性。 sizes 属性里制定图片的宽度,不能使用百分比单位,但可以使用 vw、px 等单位(100vw 就是整个窗口的宽度),也可以使用 calc 运算,同时支持媒体查询。要保证使用 sizes 里计算出来的宽度始终是图片所占屏幕宽度,剩下的事情都只需要浏览器完成了。 以下几种典型案例示范:

两边边框为百分比

如果图片两侧边框(指图片边缘到窗口边缘长度)始终是占屏幕的一定的百分比时,那么图片总会有一个相对于整个窗口的百分比。比如图片两侧边框始终是整个窗口的 10%,那么 size 属性就可以如下设置:

sizes="80vw"

如果图片已经在一个宽度是整个窗口的 70% 的元素下,而且元素内图片两侧还留有 10% 的边框,那么 size 属性就可以如下设置:

sizes="50vw"

两边边框为确定的像素

比如图片两侧边框始终是 10px,那么 size 属性就可以如下设置:

sizes="calc( 100vw - 10px )"

如果图片已经在一个宽度是整个窗口的 70% 的元素下,而且元素内图片两侧边框始终是 10p,那么 size 属性就可以如下设置:

sizes="calc( 70vw - 10px )"

图片有最大尺寸

比如图片两侧边框始终是 10px,但是图片最大只能是 1000px,那么 size 属性就可以如下设置:

sizes="(min-width: 1020px) 1000px, calc( 100vw - 10px )"

其中 (min-width: 1020px) 1000px 代表当屏幕像素宽度大于 1020px 时,图片宽度为 1000px。

实际效果

此处以我的网站为例,我的网站图片边框适中是 1.875rem,这个图片本身就是在一个元素下,这个元素默认宽度是整个窗口的 100%,但在屏幕宽度大于 40.063rem 的情况下,这个元素占整个窗口的 50%,并且这个元素有最大尺寸 500px,现在把 size 属性和用 w 标识符的 srcset 属性放在一起,就完美解决了。

<img src="800px.png" sizes="(min-width: 1000px) calc( 500px - 1.875rem ), (min-width: 40.063rem) calc( 50vw - 1.875rem ), calc( 100vw - 1.875rem )" srcset="400px.png 400w, 800px.png 800w, 1600px.png 1600w, 2400px.png 2400w, 3200px.png 3200w" alt="" />

然后你就可以在浏览器中测试了,达到了适配所有屏幕尺寸,所有设备像素比的效果。

关于兼容性

通常情况下,srcset 属性的 x 标识符支持的比 w 标识符更好,因为 w 标识符是一个新的属性。不过好消息是 Chrome 38+、Firefox 38+、Safari 9+、Opera 30+、Android 5.0+、iOS 9+(没错,IE/Edge、Windows Phone 截止到写文章之日都不支持) 都纷纷支持了 w 标识符,所以我认为现在很适合换用 srcset 属性的 w 标识符来做响应式图片了。而且这个方法是完美向下兼容的,即使不支持也没关系。 你也可以在 CanIUse.com 上查询关于 secset 属性的完整的兼容性列表

移动优先 – 速度

移动设备上的网速通常并不快,他们通常会有比宽带更慢的速度、更高的延迟,那么移动端的浏览器是如何加载页面的呢?这便是本文要介绍的内容。 其实无论是在移动端还是桌面端加载页面,都会需要以下几个步骤:

1. 解析域名

域名通常对应着一个 IP 地址,浏览器只有在知道了这个 IP 地址后才能与服务器通讯。为了解析域名,客户端会向 DNS 解析服务器发送一个关于这个域名的请求,就能查询到这个域名的 IP 地址。DNS 解析是存在 “TTL 生存时间” 的,也就是缓存时间,这个内容会被缓存在 DNS 解析服务器上、路由器和客户端上,按照缓存时间存储。DNS 解析服务器通常会由运营商提供,如果存在缓存,那么解析速度则是相当快的。(目前的 DNS 解析是以十分不安全的明文的方式传输的,任何中间人都能破坏或者是修改解析结果)

2. 发起请求

有了 IP 地址之后,浏览器会与服务器建立 TCP 链接,然后发出一个请求。请求中会包含需要访问网站的域名(得以使 IP 可以给多个域名提供不同内容)、请求方式、网址路径、所能接受的数据,压缩类型和编码方式、 Cookie 以及浏览器信息(能够判断你是否在手机上访问网站)。

3. 下载页面

浏览器开始下载页面本身,下载页面本身后又会开始下载页面中依赖的其他内容,包括 CSS 、JavaScript 、图片等。随着优先级不同,它们可能是在渲染之前或者之后下载。

4. 渲染页面

浏览器会等待加载了最重要的依赖内容(也就是在 head 部分中的 CSS 和 JavaScript,之后会重点提到)加载完毕后,进行渲染。因为渲染时要依赖这些内容, CSS 也就是我们常说的样式表,这些样式表十分重要,比如百度这个网页:

没有样式表的百度

没有 CSS 样式表后,整个网页的布局是 “失控” 的,页面不会以预期的方式被加载,这种体验十分糟糕。没有样式表与有样式表的网页通常是两种完全不同的样子。然而 JavaScript 通常与布局无关,可以放在之后加载,不影响页面样式。

提升速度

网页中的每一个内容都要经历这样的过程,以被客户端得到。只有到了渲染页面这一步时,用户才能够看到页面的内容,在此之前,页面是一片空白。要知道如果一个页面在 3 秒之内都是一片空白,那么多半用户会退出。 移动优先会考虑高延迟移动网络下的用户体验,然而当到了桌面端则也能享受到移动有限带来的速度提升。为了提升速度,有以下几种常用方法:

将不必要的 JavaScript 放在页面末尾

如果 JavaScript 放在末尾,可以在页面被渲染后再加载,大大减少了页面空白的时间。这是最简单,且提升效果最明显的一条。

使用 CDN

使用 CDN 的意思是尽可能把静态文件放在 CDN 服务器上,一个 CDN 包含很多个节点,用户下载 CDN 上的文件时会从物理位置最近或者是相同运营商的节点上下载,如果这个节点上已经有缓存则直接传给用户,如果没有缓存则从最近的数据中心下载并缓存,再传给用户。 我的做法是把网站上所有图片、视频、CSS 样式表和 JavaScript 放在 CDN 上,网站本身不在 CDN 上。这样做能大大减少加载时间,而且成本不大。无论是个人博客还是行业网站,使用 CDN 都很有必要。如果没有钱买自己的 CDN,没关系,你可以免费使用常用开源 CSS 和 JavaScript 的 CDN,官网地址地址:cdnjs.com

妥善利用缓存机制

将图片、CSS 样式表和 JavaScript 设置缓存。我习惯于设置相当长的时间(一周、一年等),有的人为了能够经常修改而把缓存期限调小。我在修改文件时会直接换文件目录,这样文件从能保持是最新的。利用好缓存,可以让用户在第二次访问时大大加速。

总结

对于网站速度,既要注重首次启动速度,还要利用缓存提升下一次访问速度。而且应该注意在提升速度的同时也要注重用户体验。

移动优先 – 起源

现有的桌面上的操作系统大多都有浏览器,浏览器可以让我们方便的浏览一个网页。 然而最初的手机浏览器访问网页是很困难的,因为它们分辨率不高、屏幕很小而且浏览器不支持众多前沿的 CSS 以及 JavaScript,使它们没有能力访问桌面版网站。那时的手机访问的网站都是专门为设备大幅度简化的版本,就像这样:

简化版本的移动网页

不过,直到 iPhone 的发布,这一切都得到了很大的改变。因为 iPhone 上的浏览器 —— Safari 是一个真正的且非常前沿的 Web 浏览器,而且它还支持 HTML5 标准。它配备有几乎和电脑一样的浏览器 —— 以至于它支持 CSS3 和 JavaScript。但是这一切,却是在一个宽度仅有 320 个像素的设备上显示。 然而当一个桌面浏览器宽度缩小到 320 个像素时,它就变的大不一样,当他访问一个没有对它优化的网页时,它就会将其缩放(宽度将缩放至 1/3),以百度新闻为例,若没有适配,则将现实电脑版网站,这样页面的字会很小,但是用户可以通过缩放就能够解决,所以并没有什么问题。然而,百度新闻做了一个专门为 iPhone 的版本,像这样:

百度新闻 iPhone 版

它能够更好的适应触摸屏,并且没有了字体小的问题,也不需要缩放。但是请要注意,这个页面已经不是刚才的那个页面了,也就意味着百度需要专门重新设计一个为 iPhone 版本的网页,成本是很高的。 然而在 CSS 的帮助下,不需要准备两个网页就可以同时适配电脑和手机,它们加载到的页面相同,但是应用的样式表并不同。例如没有针对移动而优化的 Wikipedia 导航页,iPhone 上的 Safari 访问时将会缩放显示,布局就如同电脑访问一样:

没有针对移动而优化的 Wikipedia 导航页

然而通过增加的 CSS 样式表(当然还包括 head 中的 viewport),这个页面在手机上就会完美的显示了,就像下图:

针对移动优化了的 Wikipedia 导航页

像 Wikipedia 导航页这样先根据电脑布局设计,再新增 CSS 使其支持移动设备的网页,就是桌面优先而不是移动优先。而移动优先则正好相反,页面根据手机而设计,然后再让它适配电脑,电脑与手机也是访问同一个页面,这就属于移动优先。由于移动优先的网站是根据手机设计,自 iPhone 发布之后,很多智能手机也使用了前沿的浏览器,所以移动优先的网站自然会使用 HTML5、CSS3 中的众多新特性,大大缩短了一个网页所需要的开发时间。而且移动优先的网站根据手机的高延迟网络而设计,减少了页面大小,无论身处什么网络环境,加载速度都有很大的提升。移动优先的优点众多,这就是导致其流行起来的原因。

❌