上个月接到一个客户的“特别要求”:将网站从云服务器迁移至百度智能云的BCH虚拟主机,并进行优化。这要不是一位老客户,还真以为在开玩笑。放着配置更高的云服务器不用,要迁移至虚拟主机?简单交流后,才知道是看上了BCH的百度快速收录功能。
目录索引
WordPress 使用BCH虚拟主机的瓶颈在内存
经过一番交流,确认了客户需求后,开始上手。原网站是一个宣传型的内容网站,大约有500多篇文章,日PV访问量约2000-3000左右。
客户初步选定的BCH优选型配置为:5G网页空间、512M内存、1000MB数据库、1核CPU、2M带宽。
在全新安装WordPress且不启用其它插件的情况下,该配置确实能胜任了。但当将客户使用的主题与插件导入并启用后,网站瞬间卡死,并返回504错误。
检查内存状况一直跑满512M,内存已经不够了。这里吐槽一下,BCH价格不便宜,但配置实在是很低。
WordPress网站内存跑满的原因
1、主题集成了太多的功能,代码臃肿。解决方案:使用轻量化主题替换,本案例中最终改用 GeneratePress主题。
2、页面编辑器插件。解决方案:尽量使用WordPress自带的块编辑器,因为其它任何第三方页面编辑器虽然功能强大,但后台会消耗大量内存。512M的主机根本扛不住,如果实在要用一些额外的功能,推荐使用古腾堡增强插件,例如: The Plus Addons for Block Editor、 Otter、 Stackable 或 GenerateBlocks 。
3、WP Rocket的缓存预加载。静态化缓存可以节约主机的内存与CPU消耗,并提升前端访问速度,但前提是已经缓存好的网站。当 WP Rocket 进行预缓存时,默认每批次装载45个URL,批次之间间隔60秒。一般配置较高的云服务器不会有什么压力,但对于小内存虚拟主机主机而言,跑满内存然后卡死一点也不意外。解决办法:将rocket_preload_cache_pending_jobs_cron_rows_count的值降低至20,rocket_preload_pending_jobs_cron_interval上调至80秒。详情见: 自定义 WP Rocket 预加载参数 降低CPU占用率。
运行WordPress的BCH推荐配置
经过一番调整优化后,仅在后台使用编辑器时(内存会瞬间吃掉200M-300M),同时WP Rocket在进行预缓存的情况下偶尔会跑满内存。所以决定将BCH升级至高配型,也就是内存升级到1G后,不会再出现504或502错误了,只要优化得当,还是游刃有余的。
所以总结一下使用BCH跑WordPress的一些建议,避免其他人踩坑。
内存不要低于1G
虽然使用缓存插件将全站静态缓存后,一般情况下,网站的内存使用量会维持在150-200M左右。但当后台打开编辑器的时候,内存会瞬间增加200M-300M,如果是512M的主机,这时候极有可能会爆掉。
BCH的 PHP 7 不支持 OPcache
经过实测BCH的PHP 7.x 不支持OPcache缓存(哪怕在后台开启,也无效),仅5.x支持。但对于WordPress而言,如果PHP版本低于 7.2 有可能存在兼容性问题。
有关于这个问题,笔者已向百度智能云反馈,是否会调整现在还不清楚。
推荐的 PHP.ini 配置
display_errors = Off
error_reporting = E_ALL & ~E_NOTICE
cgi.fix_pathinfo = 0
max_execution_time = 60
max_input_time = 120
memory_limit = 128M
post_max_size = 64M
upload_max_filesize = 64M
其中 max_execution_time 的值不宜设置太高,可以让PHP及时释放内存,同时避免一些由于编写有问题的PHP文件在执行时过度耗费内存资源。
但 max_input_time 的值不宜太低,否则会导致WordPress后台升级主题或插件的时候失败。
至于 memory_limit 是PHP的执行内存限制值,如果安装的插件或主题需要更高的内存,可以适当修改。但本着够用的原则,否则容易将内存耗尽。这个值要注意的是并不是PHP的总内存限制,而是单个PHP程序的内存最大限制,所以不建议设置太高的值。
cgi.fix_pathinfo=0,PHP 解释器只尝试给定的路径,如果找不到文件则停止处理,极大加强了安全性。
安装WordPress静态缓存插件
强烈建议安装 WP Rocket缓存插件,否则访问量大的时候,内存是扛不住的。
对于BCH虚拟主机,可以将下列配置添加到/webroot/bcloud_nginx_user.conf文件中,以实现绕过PHP直接调用WP Rocket缓存,仅当缓存不存在或失效时才通过PHP生成缓存。
# 站长帮Nginx直接调用缓存配置
# 是否开启调试功能
set $rocket_debug 0;
# 不要改变这些值
#
set $rocket_bypass 1; # NGINX应该绕过WordPress直接调用缓存文件吗?
set $rocket_encryption ""; # 客户接受GZIP吗?
set $rocket_file ""; # 要查找的文件名
set $rocket_is_bypassed "MISS"; # 添加了Header文本以检查绕过是否有效。 标头:zhanzhangb-Nginx-Serving-Static
set $rocket_reason ""; # 未使用缓存文件的原因。 如果使用了缓存文件,使用了什么文件
set $rocket_https_prefix ""; # 缓存文件使用 HTTPS 时使用的 HTTPS 前缀
set $rocket_has_query_cache 0; # 检查是否从缓存的查询字符串中找到来自 URL 的查询字符串
set $rocket_is_https 0; # 检查请求是否为 HTTPS
set $rocket_support_webp 0; # 检查请求是否支持 WebP
set $rocket_dynamic ""; # 添加到缓存文件名的动态值
# 页面缓存
# 定义 Rocket-Nginx $is_args
set $rocket_is_args $is_args;
set $rocket_uri_path "";
if ($request_uri ~ "^([^?]*)(\?.*)?$") {
set $rocket_uri_path $1;
}
# 客户端接受 GZIP 吗?
if ($http_accept_encoding ~ gzip) {
set $rocket_encryption "_gzip";
}
# 如果Brotli支持则关闭GZIP调用
if ($http_accept_encoding ~ br) {
set $rocket_encryption "";
}
# 是 HTTPS 请求吗?
if ($https = "on") { set $rocket_is_https 1; }
if ($http_x_forwarded_proto = "https") { set $rocket_is_https 1; }
if ($http_front_end_https = "on") { set $rocket_is_https 1; }
if ($http_x_forwarded_protocol = "https") { set $rocket_is_https 1; }
if ($http_x_forwarded_ssl = "on") { set $rocket_is_https 1; }
if ($http_x_url_scheme = "https") { set $rocket_is_https 1; }
if ($http_forwarded ~ /proto=https/) { set $rocket_is_https 1; }
if ($rocket_is_https = "1") {
set $rocket_https_prefix "-https";
}
# 检查请求是否支持 WebP?
if ($http_accept ~* "webp") {
set $rocket_support_webp "1";
}
# 设置移动端检测文件路径
# 调试去掉$rocket_uri_path的前后斜杠
set $rocket_mobile_detection "$document_root/wp-content/cache/wp-rocket/$http_host$request_uri.mobile-active";
# 要忽略的查询字符串
set $rocket_args $args;
if ($rocket_args ~ (.*)(?:&|^)utm_source=[^&]*(.*)) { set $rocket_args $1$2; }
if ($rocket_args ~ (.*)(?:&|^)utm_campaign=[^&]*(.*)) { set $rocket_args $1$2; }
if ($rocket_args ~ (.*)(?:&|^)utm_medium=[^&]*(.*)) { set $rocket_args $1$2; }
if ($rocket_args ~ (.*)(?:&|^)utm_expid=[^&]*(.*)) { set $rocket_args $1$2; }
if ($rocket_args ~ (.*)(?:&|^)utm_term=[^&]*(.*)) { set $rocket_args $1$2; }
if ($rocket_args ~ (.*)(?:&|^)utm_content=[^&]*(.*)) { set $rocket_args $1$2; }
if ($rocket_args ~ (.*)(?:&|^)fb_action_ids=[^&]*(.*)) { set $rocket_args $1$2; }
if ($rocket_args ~ (.*)(?:&|^)fb_action_types=[^&]*(.*)) { set $rocket_args $1$2; }
if ($rocket_args ~ (.*)(?:&|^)fb_source=[^&]*(.*)) { set $rocket_args $1$2; }
if ($rocket_args ~ (.*)(?:&|^)fbclid=[^&]*(.*)) { set $rocket_args $1$2; }
if ($rocket_args ~ (.*)(?:&|^)_ga=[^&]*(.*)) { set $rocket_args $1$2; }
if ($rocket_args ~ (.*)(?:&|^)gclid=[^&]*(.*)) { set $rocket_args $1$2; }
if ($rocket_args ~ (.*)(?:&|^)age-verified=[^&]*(.*)) { set $rocket_args $1$2; }
if ($rocket_args ~ (.*)(?:&|^)ao_noptimize=[^&]*(.*)) { set $rocket_args $1$2; }
if ($rocket_args ~ (.*)(?:&|^)usqp=[^&]*(.*)) { set $rocket_args $1$2; }
if ($rocket_args ~ (.*)(?:&|^)cn-reloaded=[^&]*(.*)) { set $rocket_args $1$2; }
# 删除开头的&符号(如果需要)
if ($rocket_args ~ ^&(.*)) { set $rocket_args $1; }
# 如果是缓存参数的一部分,则不计算参数
if ($rocket_args ~ ^\?$) {
set $rocket_is_args "";
}
if ($rocket_args = "") {
set $rocket_is_args "";
}
# 要缓存的查询字符串
# 如果必须绕过 WordPress,要返回的文件/URL
# Desktop: index.html
# Gzip: index.html_gzip
# HTTPS: index-https.html
# Mobile: index-mobile-https.html
set $rocket_file_start "index$rocket_https_prefix";
# 调试去掉$rocket_uri_path的前后斜杠
set $rocket_pre_url "/wp-content/cache/wp-rocket/$http_host$rocket_uri_path$rocket_args/";
set $rocket_pre_file "$document_root/wp-content/cache/wp-rocket/$http_host$rocket_uri_path$rocket_args/";
# 标准缓存文件格式
set $rocket_url "$rocket_pre_url$rocket_file_start$rocket_dynamic.html";
set $rocket_file "$rocket_pre_file$rocket_file_start$rocket_dynamic.html";
# 检查 gzip 版本缓存文件是否可用
if (-f "$rocket_file$rocket_encryption") {
set $rocket_file "$rocket_file$rocket_encryption";
set $rocket_url "$rocket_url$rocket_encryption";
}
# 如果缓存文件不存在,不要绕过WP
if (!-f "$rocket_file") {
set $rocket_bypass 0;
set $rocket_is_bypassed "MISS";
set $rocket_reason "File not cached";
}
# 如果是POST请求,不要绕过WP
if ($request_method = POST) {
set $rocket_bypass 0;
set $rocket_is_bypassed "BYPASS";
set $rocket_reason "POST request";
}
# 如果找到参数,不要绕过(例如?page=2)
if ($rocket_is_args) {
set $rocket_bypass 0;
set $rocket_is_bypassed "BYPASS";
set $rocket_reason "Arguments found";
}
# 如果站点处于维护模式,不要绕过
if (-f "$document_root/.maintenance") {
set $rocket_bypass 0;
set $rocket_is_bypassed "BYPASS";
set $rocket_reason "Maintenance mode";
}
# 如果找到其中一个 cookie,则不要绕过
# wordpress_logged_in_[hash] :当用户登录时,会创建此 cookie(宁愿让 WP-Rocket 处理)
# wp-postpass_[hash] :当受保护的帖子需要密码时,会创建此 cookie。
if ($http_cookie ~* "(wordpress_logged_in_|wp\-postpass_|woocommerce_items_in_cart|woocommerce_cart_hash|wptouch_switch_toogle|comment_author_|comment_author_email_)") {
set $rocket_bypass 0;
set $rocket_is_bypassed "BYPASS";
set $rocket_reason "Cookie";
}
if (-f "$rocket_mobile_detection") {
set $rocket_bypass 0;
set $rocket_is_bypassed "BYPASS";
set $rocket_reason "Specific mobile cache activated";
}
# 如果绕过token仍然存在,使用缓存的 URL 绕过 WordPress
if ($rocket_bypass = 1) {
set $rocket_is_bypassed "HIT";
set $rocket_reason "$rocket_url";
}
# 如果不需要调试则清除变量
if ($rocket_debug = 0) {
set $rocket_reason "";
set $rocket_file "";
}
# 如果bypass token还在,根据请求链接的文件重写
if ($rocket_bypass = 1) {
rewrite .* "$rocket_url" last;
}
# Add header to HTML cached files
location ~ /wp-content/cache/wp-rocket/.*html$ {
etag on;
charset UTF-8;
add_header Vary "Accept-Encoding, Cookie";
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header zhanzhangb-Nginx-Serving-Static $rocket_is_bypassed;
add_header zhanzhangb-nginx-Reason $rocket_reason;
add_header zhanzhangb-nginx-File $rocket_file;
}
# Do not gzip cached files that are already gzipped
location ~ /wp-content/cache/wp-rocket/.*_gzip$ {
etag on;
gzip off;
types {}
default_type text/html;
charset UTF-8;
add_header Content-Encoding gzip;
add_header Vary "Accept-Encoding, Cookie";
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header zhanzhangb-Nginx-Serving-Static $rocket_is_bypassed;
add_header zhanzhangb-nginx-Reason $rocket_reason;
add_header zhanzhangb-nginx-File $rocket_file;
}
# Debug header (when file is not cached)
add_header zhanzhangb-Nginx-Serving-Static $rocket_is_bypassed;
add_header zhanzhangb-nginx-Reason $rocket_reason;
add_header zhanzhangb-nginx-File $rocket_file;
修改完成后,记得在BCH管理面板中点“重载站点服务”按钮。
如果配置生效,可以在网页的响应头中看到:zhanzhangb-nginx-serving-static:HIT
。
BCH主机使用总结
BCH虚拟主机主要优点
1、不论是Nginx配置还是PHP的配置权限非常开放,不像其它的共享主机或虚拟主机那么限制严格,用户可以完全自定义配置。
2、通过客户网站的实测,BCH主机确实能让百度提升收录速度,从日志中可以明显看到抓取量大幅提升。但并不太影响排名效果,排名还得靠内容质量。
3、1核的CPU还是比较强的,几乎没碰到CPU占用达到100%的情况,所以BCH的瓶颈只在内存。
4、MYSQL配置也不错(版本Mysql 5.7),对于WordPress一般应用而言,没有任何问题。
- 请求数:200000个/分钟
- CPU时间:400秒/分钟
- 流入流量:300MB/分钟
- 流出流量:600MB/分钟
BCH虚拟主机主要缺点
1、价格贵,性价比较低。存储空间:10GB / 内存:1GB / 带宽:2Mbps,599元/年。
2、PHP 7.x 不支持opcache,虽然官方文档仅注明php 7.4不支持,但实际上php 7环境均不支持opcache,无法开启。
3、不支持imagick组件,但支持GD库。
4、境外访问BCH的线路较慢。