这是当前的 Lighthouse 评分,在此基础上探索一些细节优化。
当前现状
性能的好坏永远只是阶段性的,非永久的,随着一个项目的迭代,性能也会随之产生变化。
配置
- 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
监测数据情况
优化内容
静态资源
三大类中首先关注静态资源,资源的加载有大小和懒加载这两个优化方向,看了一下服务器是开启了 GZIP 压缩的。
问题:
main.js
没有压缩,虽然加上注释也就只有 42 行代码,体积1.3K
jquery
压缩后的代码体积 86Klightbox.js
9.3K,首屏也是加载的- 文章图片没有懒加载
原始静态资源大小
优化方案
- main.js 压缩后 281B
- 首屏 JQ 会用到,可以引用公共CDN的路径,这样的好处是首次访问的用户有概率可以命中该版本的 JQ ,从而走本地缓存,同时也可以降低我的服务器带宽压力
- 鉴于博客的群体访问采用的浏览器版本不低,因此移除 lazyload.js,直接使用原生的的 lazyload 属性。Chrome 支持 ~
- 文章图片使用原生 lazyload
API 报错 来源于 Google Adense 广告模块,这个无解,属于第三方功能,需要整体移除,但观察后发现实际不影响首页的加载速度,本身是异步执行。
结果
隔了一天再看统计数据,发现首屏访问速度并没有多少提升,从资源统计数据上来看,依旧存在静态资源访问耗时较长的问题。这里面有一大部分图片是文章内容的图片,由于访问量不大,图床又想要额外费用,因此直接存本地了。这里没有使用 CDN 直接优化,因为这是外物,不能因为优化而优化,在没有找到优化点之前去一顿操作非明智之举。
细分一下我看到静态资源的加载耗时,发现普遍集中在图片资源中:
于是我按照排序拿了一个耗时相对较长的链接访问看看,确实挺长
有时候 TTFB 可以达到 1S
以上,那 TTFB 是一个优化方向。因为 TTFB 是反映服务端响应速度的重要指标,但其他的静态资源有时候并没有这么慢。
SSL 连接配置
鉴于我的服务器配置较低,TTFB 值又很高,参照之前的瀑布图觉得 SSL 耗时占用了一定的比重,可以优化,于是调整了默认的配置
ssl_session_cache shared:SSL:1m; ssl_session_timeout 60m; 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;
但是根据结果反馈是负增长…. SSL 连接耗时反而增长了,但是本地测试数据并没有延长。
优化前:
优化后:
Nginx 耗时日志
由于 SSL 配置并没有效果,所以还是要结合日志查看具体的耗时链路
设置日志输出:
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';</code></pre></div></div><p>值得关注的是下面几个参数:</p><ul class="ul-level-0"><li><strong>upstream_response_time : Time between establish a connection to the upstream server and receiving the last byte of the response body.request_time</strong> : 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.</li><li><strong>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. 取两条日志观察:
代码语言:javascript复制"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 的延时。
配置如下:
代码语言:javascript复制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。
代码语言:javascript复制fastcgi_cache_path /etc/nginx-cache levels=1:2 keys_zone=phpcache:100m inactive=60m;
fastcgi_cache_key "schemerequest_methodhostrequest_uri";
然后在处理 PHP 的配置文件中加入缓存配置
代码语言:javascript复制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,原先的雪碧图方案重要性就会降低很多。
但这不是说不要做前端的性能优化,而是要权衡性价比,而不是忽略本身的问题。
所以说性能优化这件事,在某种程度上依旧是空间换时间的方式,通过内存消耗来换取更快的响应速度,只不过针对具体问题要具体分析,要到问题结合实际再做适度优化,以最少的成本换取最快的速度,没有银弹这件事同样适用于性能优化,不能眉毛胡子一把抓。