使用 Noto Sans CJK 做为博客的默认字体

Noto Sans CJK 是 Google 和 Adobe 在去年发布的一系列新字体,它包含了中日韩三种文字的字体,CJK 为中文(Chinese)、日文(Japanese)和韩文(Korean)的缩写。在 Google 的定义中,它属于 Noto Sans 系列,而 Adobe 则把它包含进了自家的 Source Sans 家族,实际上是一回事。

在英文网页中,切换字体,哪怕是一种没有广泛流传的新字体,也非常简单。因为英文只有 26 个字母,一个字体文件充其量不过上百 KB。而汉字则不一样,常用字就至少有 2000 个,全集大概超过 8 万,Noto Sans 中光简体中文的字库就高达 18MB。想在网页中使用一种新的中文字体并不容易,总不能让用户访问一个网页,就消耗 18MB 的流量吧。

于是,在网页中加载中文字体,还要使用特殊的方法。简单来说,就是事先统计出需要显示哪些文字,再动态地在原字库中删除那些不必要的,只下载必要的字形数据。一篇博文中不同的汉字数量大概 1000 个不到,这样可以有效地减少下载量,也提高了加载速度。

我到处看了一圈,现在比较成熟的工具是 Adobe 提供的 TypeKit。在 TypeKit 中可以动态创建一个 JS 文件,这个 JS 文件加载之后,会动态扫描网页的内容,并从服务器端按需加载字体数据。一个网页大约需要下载 300K 左右的数据,这比 18MB 要好多了。如果你想体验一下 TypeKit 的效果,在可以左上角目录中选择“Noto Sans”,等几秒钟之后,就可以看到效果了。 TypeKit 的用法还在进一步调整中,所以没有默认就加载。

当然,一劳永逸的方式还是直接把字体文件安装了:

  • Windows、Mac OS: 安装说明看这里,GitHub 的下载分流地址
  • Ubuntu: sudo apt-get install noto-sans
  • Chrome OS:版本 38 已自带
  • iOS:貌似没有不越狱直接安装的方法
  • Android:5.0 自带,4.x 之前原生 ROM 没有提供替换字体的功能,第三方 ROM 可能有

用回分页模式

大约一年前,我使用了 Infinite Scroll 插件,而取消了分页模式。本以为这样可以给用户体验带来一些提升。但事实上,Jetpack 是一个灾难,而我自己移植的独立版本,也是问题多多。更严重的是,“无限滚动”的效果,是无法被 CDN 缓存的。本来这个插件只是为了提高一下加载速度,但无法被缓存,反而减慢了后续页面的加载。

在花了大量时间打补丁之后,我决定停止使用“无限滚动”,换回分页的设定。相比起“无限滚动”,分页的好处有:

  • 提供了页面链接,方便搜索引擎抓取;
  • 静态页面,有利于 CDN 缓存;
  • 用户可以更方便的跳转页面,不用一点一点地加载;

停用之后有任何使用上的问题,请留言。

欢迎来到 HTTP/2 的世界

直到几个月前,本博客一直在使用 SPDY,后来 Apache 升级到了 2.4,Google 的 SPDY 库却不能用了。这个蛋疼的设定是因为 Google 把 mod_spdy 捐献给了 Apache 项目,此后自己就去专心做 HTTP/2 了。而 Apache 项目组自己却不知道在干啥,几个月都没有新代码提交,就让 mod_spdy 烂在自己的代码库里。

今年二月,在 Google 的努力推动下,HTTP/2 成为下一代 HTTP 通信标准,而它的前身,正是 SPDY 协议。SPDY 的最新版本是 3.1,目前已经较为流行,比如 KeyCDN 和 CloudFlare 都默认提供这项支持。HTTP/2 实际上是 SDPY 4.0,目前还没有广泛流行,预计到今年年底,Apache 和 nginx 才可能提供支持。

相较于 HTTP/1.1,HTTP/2 加载 SSL 的速度更快,对于多文件/资源的加载速度也有所提高,还有对流式传输的支持(本站不怎么用得到)。

由于本站使用了 Google 的网络支持,自动获得了 HTTP/2 的支持。如果你想体验一下 HTTP/2,请安装最新版本的 Chrome 或 Firefox,开启 HTTP/2 支持,然后访问本站即可。Chrome 中开启的方法是在 chrome://flags 找到 SPDY/4 的选项并启用。在访问的同时,打开 chrome://net-internals/#spdy,即可验证本站已活在 HTTP/2 的世界中。

从无到有实现 WordPress 全站 CDN

WordPress 的设计有很多莫名其妙的地方,其中之一就是它的域名设置。WordPress 的每一个站点要设置一个独立的主页(Home)地址,而且只能设置一个。也就是说,即使有两个域名指向同一个博客(比如 a.com 和 b.com),如果博客把主页设置成了 a.com,用户在访问 b.com 的时候,也会被重定向到 a.com。这样造成了 b.com 实际上完全无法访问到。

对于使用 WordPress 的博客主而言,通常一个域名就够了,可能最初 WordPress 的开发人员也是这么想的。但现在的互联网,单一域名已经不足以支撑一个网站了。简单来说,WordPress 配置 CDN 相当麻烦。

CDN,又名内容分发网络,一个把网站内容快速传递给用户的工具。它的工作原理是,预先把内容加载(缓存)到离用户较近的数据中心(通常称为 Edge Server),当用户访问网站时,直接从数据中心读取内容,而不是从源网站(Orginal Server),这样就减少了数据传输的时间。如下图:

一个 CDN 包含多个 Edge Server,如果每个用户都访问离自己较近的 Edge Server,则会比直接访问源网站要快很多。而现在的技术又可以做到当用户访问某一个域名的时候,被自动解析到较近的 Edge Server,用户则完全感受不到差别。

现在做得比较好的 CDN 服务如 CloudFlare。它家提供了一条龙服务,从域名解析到内容分发,一个帐号全搞定。这样做的好处是“傻瓜式”,不需要太多的专业知识就可以配置 CDN;而坏处则是一损俱损,CloudFlare 的服务器经常被墙,一旦被墙了,想脱离 CloudFlare 就重新设置一堆东西,很麻烦。

如果你像我一样不喜欢一键搞定的服务,那我们就来一步一步自己配置全站 CDN 吧。

初始化 CDN 服务

首先,你得有一个 CDN 服务,我用的是 KeyCDN,介绍看这里

初始化(KeyCDN 的术语叫“Zone”)完成之后,一般会得到一个二级域名,比如我的是 leonax-1800.kxcdn.com。然后在自己的域名解析中创建一个 CNAME,指向之前的域名,比如我用的是 www.leonax.net。解析刷新之后,初始化就完成了。

修改链接和资源地址

如上文所说,WordPress 限定了网站的地址,比如本博客设置为“leonax.net”,则所有的资源(JS、CSS 等)和链接都会以“//leonax.net/”开头。即使用户访问了主页“www.leonax.net”,其中的各种资源也会从“leonax.net”加载,这样就达不到 CDN 的效果。于是我们要把页面中所有的“leonax.net”全部替换成“www.leonax.net”。下面有一些方法,可选择用其中的一种或多种组合来达到效果:

修改资源地址

WordPress 提供了 style_loader_src 和 script_loader_src 分别来过滤 JS 和 CSS 的地址,我们要做的是把其中的域名删掉,从而使用相对路径。代码如下:

替换正文中的链接

使用 WordPress 的 the_content 过滤器,可以修改正文内容,具体要改什么,视具体情况而定。注意 WordPress 的短代码(shortcode)解析的优先级为 11,所以下面的代码使用了优先级“8”来避免短代码带来的改动。

Apache 输出全文替换

Apache 提供了 mod_substitute 来对输出内容进行修改。如果你的站点中提供了这项功能,则可以轻松改动网页内容:

mod_pagespeed 域名替换

mod_pagespeed 也提供了类似的域名替换功能,比 mod_substitute 智能的是,mod_pagespeed 可以识别出链接字段,比如 <img src=""> 中的内容,而不像 mod_substitute 那样全局替换:

CDN Pull

有些时候,我们可能需要在 CDN 模式下输出 www.leonax.net,而在访问源站的时候则依然得到 leonax.net。这时就需要对 CDN 发来的请求进行定向输出。一般 CDN 方面会给出一些特殊的信息,比如 KeyCDN 会设置一个特殊的 X-Pull 头,标记这个请求是从 KeyCDN 发来的。于是我们可以对这一类请求做特殊处理(Apache):

CORS

Cross-origin resource sharing,跨来源资源共享,是一个浏览器的特性。简单来说,leonax.net 和 www.leonax.net 是两个域,如果 leonax.net 引用了来自 www.leonax.net 的资源,而 www.leonax.net 没有允许这么做的话,浏览器会主动拒绝加载相应资源的。所以,如果两个域都用了 CDN 的资源,则在 www.leonax.net 中,需要允许 CORS。这一项在 KeyCDN 中默认是开启的,我也就没有多关心。

CDN Purge

CDN 的主要原理是缓存,也就是把网页内容缓存在 Edge Server 中,每次响应用户请求的时候,不必再向源站请求数据。这样就产生了缓存过期的问题。比如博客中有人留言,博客页面更新了之后,CDN 却不知道这件事,依然输出不包含留言的页面,这样就不对了。为了解决这个问题,CDN 通常都会提供一种清除缓存的方式,称为 CDN Purge。 KeyCDN 的做法是发出一个如下的 HTTPS 请求,用于清除一个特定页面的缓存:

CDN 方面搞定了,WordPress 依然成问题,因为我们也不知道页面什么时候更新。研究了一下发现,我可以利用 WP Super Cache,在 Super Cache 的缓存页面被删除的同时,也发送一个请求给 KeyCDN,同时删除那边的缓存,一举两得:

上述的代码会在博文更新、或是有新留言的时候触发,会稍微减慢响应速度,更好的办法应该利用 WordPress 的 wp_schedule_single_event 方法,不过暂时还没有研究。

总结

把上述所有内容串起来,就有了现在的全站 CDN

HHVM 近期频繁崩溃

近期 HHVM 频繁崩溃,一天数次,导致博客不稳定。尽管我有一个后台程序重启 HHVM,但毕竟有那么几分钟的不能响应时间,多少有点不爽。于是就研究了一下崩溃的原因。

在 /var/log/hhvm/error.log 中能看到类似下面的信息,Core dump 了:

然后再去看 /tmp/stacktrace.1173.log,发现如下信息:

说是要增加 Eval.JitASize 的大小。Google 了一下,这个数值指定了 HHVM Translation Cache 的大小。对 HHVM 不是特别了解,猜测是 HHVM 会把 PHP 翻译成 HH 的代码,并缓存在 Translation Cache 中。这个 Core Dump 的原因大概就是 Translation Cache 用完了。

根据 Issue #2851 的讨论,在 server.ini 中加入如下设定,把 JitASize 提高到 128M:

讨论中也有人说这个设定并不能完全解决问题,顶多就是把每天一次的崩溃延长到几周一次,后续进一步的完善还在 Issue 5233 中讨论。

本站节点分布

2016.05.26 更新:本站已使用 CloudFlare 作为 DNSGoogle CDN 做内容分发,这两个系统都过于庞大无法画出来,故下图已失效。

自从使用了第三方的 DNS 服务CDN 之后,本站就成了(伪)分布式的站点了。全球分布如下图:

其中红色为主站点,所有内容从此处输出;蓝色为 CDN,加速静态内容加载;绿色为 DNS,加速域名解析。

本站开启 CDN 加速

本站的流量以每个月 10GB 的速度上升,而 Google Cloud 对中国地区的流量收费又比较贵($0.23/GB)。这样下去大有流量费用高过主机的势头,没办法,只能上 CDN 了。

之前一直不用 CDN 的主要原因有两个:

  • 一是没有一个 CDN 可以兼顾国际和国内的流量,一般国内的 CDN 都要求域名备案,国外的 CDN 都没有大陆节点;
  • 二是多数 CDN 服务对自定义 SSL 的收费都比较高,比如 CloudFlare 要收取每个月 $200 的费用。

不过最近发现一个 CDN 服务:KeyCDN

KeyCDN 的模式是按用量收费,$0.04/GB,也就是一美元可以有 25GB 的流量,比 Google Cloud 要便宜多了。没有月租,充多少用多少。新用户还有一个月的试用期,送 25GB 流量,非常吸引人。虽然 KeyCDN 也没有大陆的节点,不过在香港和东京都有,勉强可以接受。另外关键的一点是,它家对自定义 SSL 是免费的,直接上传证书即可,5 分钟内自动配置完成,很方便。

为了解决 CDN 服务可能被墙的困扰,本站默认还是从主站加载。然后会自动侦测 CDN 是否可用,如果可用,则会自动切换成从 CDN 加载的模式。对于本站的读者,您将感受不到任何差别,默默地帮我省了流量而已。