本来我觉得 Ghost 响应速度是很不错的,在同一个机器的环境下, Ghost 安装后不做任何修改,主页的 TTFB 就能压在 200ms-400ms 左右,而 WordPress 安装完后初始状态下,TTFB 能飙到 1s-2s,令人感动。

一般来说,用户体验最佳的响应时间是 200ms 以内,超过 500ms 的话体验就算差了,除非是刚需,否则很可能就(至少我会)直接把页面关掉了。当然,无论是 Ghost 还是 WordPress,都是可以通过优化把 TTFB 压到 100ms 以下的,网络状态好的情况下低于 50ms 问题也不大。之前我的博客因为大部分时间下能达到 200ms 左右响应,不算太慢,所以一直没有想着去优化,不过最近逛到不少静态博客,实在是对它们的响应速度充满羡慕。

说到优化的方向和方案,其实都特别的多,比如:

  • 网络方面:除了机房本身的线路优化外,还可以尝试更换 TCP 拥塞算法来提供大文件更快速的传输效率;
  • 数据库方面:博客系统本身关系并不复杂的情况下,尽量避免使用 MySQL 这类大型的数据库,比如 Ghost 默认是使用的更为小巧的嵌入式的 SQLite 数据库,这样可以提高整体上数据增删改查的速度,同时降低系统资源的消耗;
  • etc ...

不过在后端的环境内,尽管 Ghost 使用了嵌入式数据库,降低了一部分的资源消耗,但是作为一个动态性博客系统,用户的每一次请求都会要经过 node 来重新构建页面。在更新并不频繁的博客应用场景下,重复请求相同的资源造成的消耗就显得特别的多余。为了降低这一部分原因增加的系统消耗和延时,我最先想到的是像众多静态博客一样的实现方法:预生成全站的静态页面。

Ghost 官方其实提供了静态化站点的方案的。最开始的时候我就是去了解了一下官方提供静态化方案,比如:利用 Eleventy Gatsby 的这些方案。看 Demo 的感觉,效果是挺不错的,但是静态化后会遗失部分功能,而且主题模板也需要做一些改动,需要花时间去折腾。

Anyway,我选择放弃……。

既然全站静态化需要花时间折腾,为了节约一些时间,我决定采用更简单的方案:走缓存,这是现有工具支持度很好的方案了……。

因为我是使用 Nginx 来做的 Web 代理(应该大部分人也用的这个),而 Nginx 本身就支持缓存功能,所以直接用 Nginx 的缓存功能来加快站点响应速度。优化的全程就是稍微改改配置就 OK 了,省事儿。


下面开始说说我的 Nginx 的配置哦,

首先第一件事嘛,给 Ghost 分配一个缓存区域:

# /tmp/nginx_cache 为缓存存放的路径,可以自行修改
# keys_zone=ghost_cache 为缓存区名称,可自行修改
proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=ghost_cache:32m max_size=128m inactive=24h;

接下呢,就来给默认路径进行配置:

location / {
	# 在原有配置内容下面添加
	
    proxy_set_header Host $host;
	proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	proxy_set_header X-Forwarded-Proto https;
	proxy_pass http://your-proxy-upstream;

	
	# 忽略 Cache-Control 请求头信息
	proxy_ignore_headers Cache-Control;
	# 隐藏 Set-Cookie 信息
	proxy_hide_header Set-Cookie;
    
	# 添加缓存命中信息到响应头 (MISS/HIT/BYPASS)
    # 测试用,平时可以注释掉。我这边开启后会影响 HSTS 请求头。
	add_header X-Cache-Status $upstream_cache_status;
    
	# 设置跳过缓存的配置(搭配后面调整登录用户功能的代码使用)
	proxy_no_cache $skip_cache;
    proxy_cache_bypass $skip_cache;

	# 指定缓存空间,使用上面代码块中配置的 keys_zone
	proxy_cache ghost_cache;
	# 默认的缓存 TTL 失效时间 1 天
	proxy_cache_valid 1d;
	# 缓存 404 响应页面 TTL 时间
	proxy_cache_valid 404 1h;
	# 使用额外条件的 GET 请求从源服务器刷新内容
	proxy_cache_revalidate on;
	proxy_buffering on;
    # 允许后台自动更新缓存信息
	proxy_cache_background_update on;
	# 一定程度的容错配置,发生错误时引向缓存中的错误页面
	proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
}

配置完主路径下的设置,现在需要排除部分不用缓存的路径:

# 排除部分路径,下面依次是后台管理页面、文章预览页面、用户管理页面
location ~^/(ghost|p|members)/ {
	proxy_set_header Host $host;
	proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	proxy_set_header X-Forwarded-Proto https;
	proxy_pass http://your-proxy-upstream;
}

目前为止,大致的配置就算完了,不过还有一些细节可以改进一下,比如:

静态资源:默认情况下一些静态资源,比如 css、js、image 这类文件的请求和响应,会要通过 Ghost 也就是 node 来传递,多了一个中间人自然就浪费了不必要的请求和响应时间,所以静态资源也是可以优化的。我们可以把静态资源直接交给 Nginx 来代理,跳过 Ghost 的 node 这个中间层:

# 配置类的一些静态资源,根据自己的情况修改引用路径
location ^~ /assets/ {
        root /usr/share/nginx/ghost-content/imwc.me/themes/custom;
}

# 图片类的静态资源,根据自己的情况修改一下路径信息
# 排除 images 下的 size 是因为修改这项会影响 Ghost 的 Image resize 功能
location ^~ /images/(!size) {
        root /usr/share/nginx/ghost-content/imwc.me;
}

登录用户:Ghost 提供订阅用户功能,可以设置用户文章权限以及进行内容推广。如果站点提供订阅用户服务的话,Nginx 全局缓存会对用户登录功能造成影响,比如:

  • 登录成功后页面返回的是未登录的缓存页面
  • 非公开文章被缓存,导致可以公开访问
  • etc...

综上,我们需要让已登录的用户直接像后端请求数据,不走缓存:

# 下面配置放在 server 配置中
# 设置默认的跳过缓存配置为关闭
set $skip_cache 0;
# 当请求头中包含已登录用户的特定 cookie 信息的时候,跳过缓存
if ($http_cookie ~* "ghost-members-ssr.sig") {
        set $skip_cache 1;
}

这一段代码需要搭配默认路径也就是 location / {...} 中:「设置跳过缓存的配置」这一段。这样,当浏览器中存有登录用户的 cookie 时,Nginx 就会略过缓存,直接请求原始数据。当用户登出后,对应 cookie ghost-members-ssr.sig 会被自动清理掉,这样的话,再次访问的话就恢复调用缓存来提供数据啦。

不过这样操作也有个问题,就是订阅用户的速度反而会比普通访客慢,一点儿也不像是 VIP 该有的待遇,哈哈哈哈,不过 200 多毫秒其实还好,其他一些固定的静态内容也可以通过浏览器本身的对象缓存加速响应,所以……。

And,反正我也没有几个订阅用户,who cares?


Anyway,一顿配置写下来,博客访问速度有了「小小的提升」,哈哈哈哈毕竟本来也不算很慢。重启完 Nginx,在浏览器里面看了一下,动态页面的 TTFB 基本都压在了 50ms 以内,直接观感上来说,还是会令人愉悦的。

只是,优化这个也就纯粹自我满足,毕竟博客没内容也没访客,优化这一两百毫秒的 TTFB 重要吗?好像真的不重要,nobody cares。哭泣……。