博客首屏性能优化探索

比赛文章在这: https://cloud.tencent.com/developer/article/1928122

当前现状

性能的好坏永远只是阶段性的,非永久的,随着一个项目的迭代,性能也会随之产生变化。这次结合腾讯云的 RUM 工具来优化。

配置

  • 1核 1GB 1Mbps
  • 系统盘:普通云硬盘
  • 网络:基础网络

同时运行了 6 个容器,同时 Nginx 开启了 HTTPS

  • nginx
  • php:fpm
  • egg:v1
  • cnpmjs.org
  • gitea/gitea:latest
  • mysql:5.5

站点是基于 WordPress 的,所以非纯静态,动态站点。抛开起点去谈目标不切实际,因此站在这个基础上再去做的一些优化:

本地测试环境: MacBook Pro 8G + Chrome 无痕模式 + 每次刷新页面 disabled cache

监测数据情况

近7天性能概况

优化内容

静态资源

三大类中主要关注静态资源,资源的加载有大小和懒加载这两个优化方向,看了一下服务器是开启了 GZIP 压缩的。

问题:

  • main.js 没有压缩,虽然加上注释也就只有 42 行代码,体积 1.3K
  • jquery 压缩后的代码体积 86K
  • lightbox.js 9.3K,首屏也是加载的
  • 文章图片没有懒加载
原始静态资源大小

优化方案

  1. main.js 压缩后 281B
  2. 首屏 JQ 会用到,可以引用公共CDN的路径,这样的好处是首次访问的用户有概率可以命中该版本的 JQ ,从而走本地缓存,同时也可以降低我的服务器带宽压力
  3. 鉴于博客的群体访问采用的浏览器版本不低,因此移除 lazyload.js,直接使用原生的的 lazyload 属性。Chrome 支持 ~
  4. 文章图片使用原生 lazyload

API 报错 来源于 Google Adense 广告模块,这个无解,属于第三方功能,需要整体移除,但观察后发现实际不影响首页的加载速度,本身是异步执行。

结果

隔了一天再看统计数据,发现首屏访问速度并没有多少提升,从资源统计数据上来看,依旧存在静态资源访问耗时较长的问题。这里面有一大部分图片是文章内容的图片,由于访问量不大,图床又想要额外费用,因此直接存本地了。这里没有使用 CDN 直接优化,因为这是外物,不能因为优化而优化,在没有找到优化点之前去一顿操作非明智之举。

细分一下我看到静态资源的加载耗时,发现普遍集中在图片资源中:

于是我按照排序拿了一个耗时相对较长的链接访问看看,确实挺长

有时候 TTFB 可以达到 1S 以上,这是一个优化方向。因为 TTFB 是反映服务端响应速度的重要指标。

SSL 连接配置

鉴于我的服务器配置较低,TTFB 值又很高,参照之前的瀑布图觉得SSL耗时占用了一定的比重,可以优化,于是调整了默认的配置

ssl_session_cache        shared:SSL:1m;
ssl_session_timeout      60m;
#ssl_buffer_size 	     4k;
ssl_session_tickets      on;

ssl_stapling             on;
ssl_stapling_verify      on;

resolver                 8.8.4.4 8.8.8.8  valid=300s;
resolver_timeout         5s;
ssl_prefer_server_ciphers on;

ssl_protocols  TLSv1.2 TLSv1.3;

ssl_ciphers "ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";

add_header Strict-Transport-Security "max-age=31536000;includeSubDomains;preload";
add_header  X-Frame-Options  deny;

但是根据结果反馈是负增长…. RUM 上 SSL 连接耗时是增长了,本地测试没有明显变化。

优化前:

优化后:

Nginx 耗时日志

设置日志输出:

log_format apm '"$time_local" client=$remote_addr '
               'method=$request_method request="$request" '
               'request_length=$request_length '
               'status=$status bytes_sent=$bytes_sent '
               'body_bytes_sent=$body_bytes_sent '
               'referer=$http_referer '
               'user_agent="$http_user_agent" '
               'upstream_addr=$upstream_addr '
               'upstream_status=$upstream_status '
               'request_time=$request_time '
               'upstream_response_time=$upstream_response_time '
               'upstream_connect_time=$upstream_connect_time '
               'upstream_header_time=$upstream_header_time';

值得关注的是下面几个参数:

  • $upstream_response_time : Time between establish a connection to the upstream server and receiving the last byte of the response body.
  • $request_time : This is the Full request time , Starting from , the nginx reading the bytes from the client , till Nginx sends the last byte of the response body to the client.
  • $upstream_connect_time : The time spent establishing a connection with the upstream server
  • $upstream_header_time : Time between establishing a connection to an upstream server and receiving the first byte of the response header.

取两条日志观察:

"28/Dec/2021:14:42:53 +0000" client=114.85.34.238 method=GET request="GET /%e7%ae%97%e6%b3%95-practice-day-1.html HTTP/2.0" request_length=464 status=200 bytes_sent=6306 body_bytes_sent=5886 referer=- user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36" upstream_addr=unix:/tmp/php7-fpm.sock upstream_status=200 request_time=0.206 upstream_response_time=0.204 upstream_connect_time=0.000 upstream_header_time=0.200
"28/Dec/2021:14:42:53 +0000" client=114.85.34.238 method=GET request="GET /wp-content/themes/neat/ajax-comment/app.css?ver=1.0.0 HTTP/2.0" request_length=140 status=200 bytes_sent=783 body_bytes_sent=520 referer=https://www.noxxxx.com/%e7%ae%97%e6%b3%95-practice-day-1.html user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36" upstream_addr=- upstream_status=- request_time=0.000 upstream_response_time=- upstream_connect_time=- upstream_header_time=-

第一条是需要执行 PHP 代码的,所以需要连接 php-fpm 的容器,可以看到 upstream_response_time 有 200ms 以上。

通过查阅资料,PHP-FPM 一共有三种工作模式:ondemand,static,dynamic(内存优先、静态池、服务优先)。

静态和内存优先的方式我调试了一下,发现耗时最低只能在200上下,而服务优先的这种模式,最低能到 140ms 的延时。

配置如下:

pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 6

测试截图:

然而这种模式对于小内存的机器来说是灾难,因为当我想关闭容器的时候发现内存不够,无法关闭。

内存占用直线飙升,通过占用内存换取低延时高处理性能,但是整体会影响服务器的稳定。默认 php-fpm 走的是 dynamic 模式。

采用 static 模式,经过测试 pm.max_children = 5 对于 1G 内存的机器来说相对合适一点,测试下来,普遍稳定在 400 ms上下,最高不超过 1.3s,最低可以到 207ms,而 max_children 设置为 5 10 20,在内存占用上差距不大,不过考虑到小流量的访问场景,5 的取值对应的耗时目前可以接受。

PHP 版本升级

7.3.6 升级到 7.4.27 从结果上来看,提升不大,如果是 5 => 7 ,那是一个质变,小版本的升级暂时看不出太多的性能问题,期待 8 版本的镜像后续能带来的性能变化。

Nginx 缓存

优化到这里的时候我思考了一下,首先静态资源是有缓存的,那么是否可以对动态语言进行缓存?也就是说我避开每次重复执行 PHP 代码来提高页面直出的速度。

首先在 /etc/nginx/nginx.conf: 添加如下代码,设置 fastcgi 缓存路径和缓存 key

fastcgi_cache_path /etc/nginx-cache levels=1:2 keys_zone=phpcache:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";

然后在处理 PHP 的配置文件中加入缓存配置:

location ~ [^/]\.php(/|$) {
    fastcgi_cache phpcache; # The name of the cache key-zone to use
    fastcgi_cache_valid 200 30m; # What to cache: 'Code 200' responses, for half an hour
    fastcgi_cache_methods GET HEAD; # What to cache: only GET and HEAD requests (not POST)
    add_header X-Fastcgi-Cache $upstream_cache_status; # Add header so we can see if the cache hits or misses
}

带来的效果非常可观:

文章内页速度

降幅达 15倍,而仅仅需要配置一下 Nginx,收益很高。

未来可能存在优化的点:

Mysql 版本,目前使用的是 5.5,但就像大多数的公司项目面临的问题,不太可能升级所有的依赖,风险是其中一个因素,更何况还没有做过相关测试,改动代价太高,暂时不准备动了。

总结一下:

在利用内存的情况下,可以将之前的 300 多 ms 降低到 150ms 再到 15 ms,可以在低内存的机器上兼顾服务器的利用率。

还有一种优化思路就是利用语言、框架、依赖、服务器、http1.1 ->2 等周边,通过版本提升来试图提高性能,或者替换性能更优服务来降低首屏耗时。这种方式在现实项目中有的成本会高一些,但是相对的收益也高,比起纯前端去做资源的打包压缩合并,见效来的更快,就好比切换到http2,原先的雪碧图方案重要性就会降低很多。

但这不是说不要做前端的性能优化,而是要权衡性价比,而不是忽略本身的问题。