其实 Ghost 本身响应速度是很不错了,在相同配置和网络线路的机器下使用, Ghost 在安装完成后不做任何修改,页面的 TTFB 就能达到 200ms-300ms 左右,而 WordPress 在安装完后初始状态下进行访问,TTFB 能飙到 1s-2s,非常感人。

一般来说,想要用户体验不差,响应时间至少得在 500ms 以内,超过了 500ms 那页面的加载就会有很明显的迟缓。当然,无论是 Ghost 还是 WordPress,都还是有进一步优化的空间的,如果在机器的网络线路好的情况下,把 TTFB 控制到低于 50ms 也没有任何问题。之前我这个自嗨站因为大部分情况能达到 200ms 左右响应速度,也不算太慢,所以一直没有想去做优化,但是最近闲逛看了很多人的静态博客,实在是太羡慕他们站点的「极速响应」,所以也来好好优化一下吧。


要说到优化响应速度的方法,一般就是两样:网络 和 缓存。

网络自然不必多说,香港、台湾、日本、韩国这些亚太地区的机器,一般来说效果肯定是比美洲和欧洲的要好,另外还有各种精品网和特殊线路的机器,这个不用说太多,总结就是:花钱解决,买更好线路的机器。

至于现成的机器,线路一时半会儿不会变的情况,这时就可以试试缓存加速啦。

因为 Ghost 是一个动态博客系统,无论它多么轻量化,都避免不了要在用户的每一次请求后执行一连串工作来返回数据给用户,它先要去读取数据库,然后利用读取回来的数据构建好页面,最后才把页面分发到客户端。这一点,对于更新并不频繁的博客,无疑是有很多的资源浪费,同时也使得客户端收到回应的时间被拉长。

重复请求相同的资源造成的消耗总是特别的多余的,为了降低因为这部分原因增加的系统消耗和延时,最好的办法就是利用缓存来做数据响应。

不过缓存要怎么做呢?Wordpress 这个「巨无霸」倒是有现成的插件可以直接使用,Ghost 该怎么操作来使用缓存呢?其实很简单,大部份人绑定域名和反向代理的时候应该都是用的 Nginx 吧,嗯,它本身就提供了缓存的功能。


至于缓存这一套东西是怎么运行的呢,正常来说,Nginx 处理响应的顺序是:

  1. 用户发送请求;
  2. Nginx 接收请求;
  3. Nginx 转发请求给 Ghost;
  4. Ghost 读取数据库,生成页面,返回页面给 Nginx;
  5. Nginx 将 Ghost 返回的页面传送给用户。

大致就是这样 5 个步骤,乍一看其实没什么,但是实际上,用户的所有页面请求,服务器都需要完整的执行一次这个流程,即便刚刚才访问过一个页面,之后再访问一次,服务器还是要重复再执行一次这个流程。

那么如果使用了缓存功能,Nginx 又会怎么处理数据请求呢?其实第一次访问还是相同的,Nginx 还是需要像 Ghost 去请求数据的,只不过多了一些缓存的操作:

  1. 用户发送请求;
  2. Nginx 接收请求,检查是否有对应资源的缓存;
  3. 第一次请求,检测不到缓存,把请求发送给 Ghost;
  4. Ghost 读取数据库,生成页面,返回页面给 Nginx;
  5. Nginx 将 Ghost 返回的页面传送给用户,同时创建对应资源的缓存;

在初次访问过后,Nginx 留存了对应资源的缓存,那么之后的访问流程就变成:

  1. 用户发送请求;
  2. Nginx 接受请求,检查是否有对应资源的缓存;
  3. 检测到缓存,Nginx 直接将缓存数据传回给用户。

很明显流程减少了,而且减少的还是相对比较耗时的那一部份流程,这样下来,即便使用 Ghost,访问体验也会和那些静态博客没什么差别拉。


给 Ghost 配置 Nginx 缓存

那么要怎么使用 Nginx 缓存呢,其实很简单的,只需在 Nginx 的配置文件里面添加一些内容就好了。首先,打开站点的 Nginx 配件,然后在顶部设置一个缓存区:

proxy_cache_path /tmp/blog_pub_cache levels=1:2 keys_zone=blog_pub_cache:16m max_size=256m;
配置缓存区域:存储路径、名称、限制等等

其中 /tmp/blog_pub_cache 是缓存数据存放位置,这个可以根据自己想法改,keys_zone 后面的 blog_pub_cache 是缓存区的名称,这个也可以改,更详细的参数可以去看看官方的文档。

缓存区有了,接下来要在 location 块里添加配置来使用缓存功能了:

# 检查缓存命中状态
add_header X-Cache-Status $upstream_cache_status;

## 缓存配置
# 忽略源的缓存控制,不影响 add_header Cache-Control 的传递
proxy_ignore_headers Cache-Control;
# 调用的缓存区域的名称
proxy_cache blog_pub_cache;
proxy_cache_key $uri$is_args$args;
proxy_cache_lock on;

# 设置不同响应的缓存的有效期
proxy_cache_valid 200 1d;
proxy_cache_valid 404 1m;

# 验证缓存是否过期和是否被修改
proxy_cache_revalidate on;
# 后台更新缓存
proxy_cache_background_update on;

# 在服务故障时(返回错误响应)使用缓存来响应请求(避免服务彻底失效)
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
都是一些缓存相关的设置,更多可选参数和细节可以去看看官网的手册

添加完成之后,保存刚刚编辑的配置文件,用 nginx -t 检查一下配置文件是否有问题,如果没有问题的话,systemctl restart nginx 重启一下 Nginx。

重启完 Nginx 之后,新建一个隐私模式的浏览器窗口并访问站点,之后打开浏览器的调试工具,找到「网络」选项卡,关掉缓存,然后再刷新一次页面。正常情况下已经能很明显的感觉到响应速度变快了。

此时点击「网络」选项卡下的请求,看一下响应头里面的数据,正常情况下会有一个 X-Cache-Status 的条目,如果后面的数据是 HIT,那正面缓存响应成功了。


缓存设置好了之后还有两点需要注意的,一个是可能存在部份页面需要设置「跳过缓存」来保证实时性,另一个则是使用了「会员阅读权限」功能的用户。

跳过部份页面缓存

Ghost 是不适合全站缓存的,比如后台登陆页面,账户信息页面等等,这一些需要实时信息的页面都是不适合缓存的,所以如果是在 location / {...} 下配置的缓存,一定要注意添加一些内容来让不适合缓存的页面跳过的缓存。

操作的话很简单,直接在 location / 的里面嵌套一个新的 location 块:

location ~ /(ghost/account|members|signin|signup) {
	proxy_no_cache 1;
	proxy_cache_bypass 1;
}
排除 ‘ghost’ ‘account' 'members' 'signin' signup' 页面(可以根据自己需求增减)

不同级别用户如何分开缓存

Ghost 自带了阅读权限配置的功能,可以给内容设置「所有人可见」「订阅用户可见」「付费用户可见」等权限,但使用缓存功能后,可能存在权限交叉的情况。

这时候的处理办法,就是要对不同等级的用户来单独设置缓存区了。

首先得在配置文件的顶部再添加一个缓存区,直接加在之前设置的缓冲区下面就可以。我这边的两个缓存区,一个名字是是 blog_pub_cache ,意思是常规的游客缓存,下面新添加的: blog_mem_cache ,用来存放订阅用户的缓存。

proxy_cache_path /tmp/blog_pub_cache levels=1:2 keys_zone=blog_pub_cache:16m max_size=256m;
proxy_cache_path /tmp/blog_mem_cache levels=1:2 keys_zone=blog_mem_cache:16m max_size=256m;
同时配置两个缓存区域,用来分别存放不同用户的缓存

之后需要判断用户是否是订阅用户,我这边因为没有 Stripe,目前暂时只用了免费订阅的功能,所以目前来说只需要区分游客和普通订阅用户就可以,那么区分方法只用看看是否有 ghost-members-ssr.sig cookie 就好了:

# 默认使用公共缓存
set $ghost_cache_type blog_pub_cache;

# 如果包含会员信息 cookie 则改用会员缓存
if ($http_cookie ~* "ghost-members-ssr.sig") {
	set $ghost_cache_type blog_mem_cache;
}
根据 cookie 来分别调用不同的缓存空间

第一段配置是设置 $ghost_cache_type 这个变量为普通的「游客缓存」,第二段就是判断服务是否包含「ghost-members-ssr.sig」的 cookie,如果有的话就将变量改为 blog_mem_cache

最后,把之前在 location 配置的 proxy_cache 字段,把后面的缓存区名称改为变量 $ghost_cache_type 。这样,无论是普通用户还是订阅用户,在他们访问的时候,Nginx 都会调用对应的缓存区来响应用户的缓存,避免了权限交叉。


就这样,其实很简单。不过因为没有系统地学过 Nginx,都是拼拼凑凑弄的,所以刚开始也折腾了挺久。不清楚有没有可能有错误(应该是没有的吧😂)。