用wordpress好些年了,这玩意插件和主题很多,升级什么的也很方便,加上自己也不会搞界面和图形,也就这么一直用下来了。
但是这玩意有个所有平台类软件的通病,就是接口抽象太多,加上注入点太多,导致自身性能很捉急。
基于这么多年的经验,最近又捣鼓了几套方案,分享给用wp的朋友。
如果你只有一个机器,那么能做的很有限,但是也能通过一些合理设置来大幅优化性能。这种单机环境的做法在最后面。
第一套方案,后端+前端
这个方案最常见,也是许多人正在用的,前端也可以用cf这种cdn代替。
效果取决于前后端之间的速度和前端的网络状态。
第二套方案,后端主备切换+前端
与第一套基本一样,只是加了容错和自动切换,这个没什么好说的。
第三套方案,后端前台+后端后台+前端
这个很适合你有一个稳定但是流量受限的机器(后端后台),还有一个性能较强但可能稳定性差一些的机器(后端前台)。这时可以在前端节点将网站进行前台的主备切换,后台指定为后端后台机器。同时还需要后端后台向后端前台进行数据库和文件的自动同步。
数据库很容易,以前的文章介绍过主从复制。文件同步也很简单,rsync开一个模块,然后用inotify-tools做文件系统监控就行了,网上文章很多。
我的脚本可以设置多个同步目标,可以设置忽略某些路径,还通过临时性的锁文件来处理突发性的大量更新,供参考:
#!/bin/bash # 定义源目录 SRC_DIR="/var/www/www.tingtao.org/" # 日志文件路径 LOG_FILE="/var/log/web_sync/tingtao.log" # rsync密码文件 PASS_FILE="/root/rsyncuser.txt" # rsync端口 RSYNC_PORT=7788 # 锁文件 LOCK_FILE="/tmp/web_sync_tingtao.lock" # 定义需要跳过监控的目录列表(使用空格分隔,支持通配符) SKIP_DIRS="/var/www/www.tingtao.org/webcache" # 定义最多5个目标服务器(为空则跳过) DEST1="webuser@后端前台ip::tingtao/" DEST2="" # 第二个目标 DEST3="" DEST4="" DEST5="" # 构建inotifywait排除参数(更可靠的实现) EXCLUDE_ARGS="" if [ -n "$SKIP_DIRS" ]; then # 为每个要排除的目录构建inotifywait的排除参数 for dir in $SKIP_DIRS; do # 确保目录以斜杠结尾 if [[ "$dir" != */ ]]; then dir="$dir/" fi # 计算相对于源目录的路径 rel_path="${dir#$SRC_DIR}" # 使用更可靠的排除格式,确保正确匹配 EXCLUDE_ARGS="$EXCLUDE_ARGS --exclude '^$rel_path'" EXCLUDE_ARGS="$EXCLUDE_ARGS --exclude '^$rel_path.*'" done fi # 构建rsync排除参数 RSYNC_EXCLUDE="" if [ -n "$SKIP_DIRS" ]; then for dir in $SKIP_DIRS; do # 确保目录以斜杠结尾 if [[ "$dir" != */ ]]; then dir="$dir/" fi # 计算相对于源目录的路径 rel_path="${dir#$SRC_DIR}" # 添加rsync排除参数 RSYNC_EXCLUDE="$RSYNC_EXCLUDE --exclude='$rel_path'" done fi # 增加一个函数来检查文件是否在排除目录中 is_skipped_file() { local file_path="$1" if [ -n "$SKIP_DIRS" ]; then for dir in $SKIP_DIRS; do # 确保目录以斜杠结尾 if [[ "$dir" != */ ]]; then dir="$dir/" fi # 检查文件路径是否以排除目录开头 if [[ "$file_path" == "$dir"* ]]; then return 0 # 在排除目录中 fi done fi return 1 # 不在排除目录中 } # 确保日志目录存在 mkdir -p $(dirname "$LOG_FILE") # 监控文件变化 inotifywait -mrq --timefmt '%Y-%m-%d %H:%M:%S' --format '%T %w%f %e' \ $EXCLUDE_ARGS \ -e create,delete,modify,move,attrib "$SRC_DIR" \ | while read date time file event; do # 检查文件是否在排除目录中 if is_skipped_file "$file"; then echo "[$date $time] 跳过同步 - 文件在排除目录中: $file 事件: $event" >> "$LOG_FILE" continue fi # 延迟10秒合并批量操作 sleep 10 # 检查锁文件 if [ -f "$LOCK_FILE" ]; then LOCK_PID=$(cat "$LOCK_FILE") if ps -p "$LOCK_PID" > /dev/null 2>&1; then echo "[$date $time] 同步跳过 - 前一次同步尚未完成(PID: $LOCK_PID)" >> "$LOG_FILE" continue else echo "[$date $time] 发现残留锁文件,自动清理(PID: $LOCK_PID)" >> "$LOG_FILE" rm -f "$LOCK_FILE" fi fi # 创建锁文件并执行多目标同步 ( echo $$ > "$LOCK_FILE" sync_success=0 # 记录整体同步结果(0=全部成功,非0=至少一个失败) # 循环处理5个目标(跳过空值) for dest in "$DEST1" "$DEST2" "$DEST3" "$DEST4" "$DEST5"; do # 跳过空目标 if [ -z "$dest" ]; then continue fi # 记录当前目标开始同步 echo "[$date $time] 开始同步到 $dest" >> "$LOG_FILE" # 执行同步 rsync --port="$RSYNC_PORT" -azh --delete \ --password-file="$PASS_FILE" \ $RSYNC_EXCLUDE \ "$SRC_DIR" "$dest" >> "$LOG_FILE" 2>&1 # 记录单个目标结果 if [ $? -eq 0 ]; then echo "[$date $time] 成功同步到 $dest" >> "$LOG_FILE" else echo "[$date $time] 同步到 $dest 失败" >> "$LOG_FILE" sync_success=1 # 标记整体失败 fi done # 清理锁文件 rm -f "$LOCK_FILE" # 传递整体结果(0=全部成功,1=至少一个失败) exit $sync_success ) # 记录最终同步结果 if [ $? -eq 0 ]; then echo "[$date $time] 所有目标同步完成 - 触发文件: $file 事件: $event" >> "$LOG_FILE" else echo "[$date $time] 部分目标同步失败 - 触发文件: $file 事件: $event" >> "$LOG_FILE" fi done
设置了10秒延迟,因为当wp进行更新或者插件更新的时候,会短时间更新大量文件,不做延迟会导致文件监控+同步的cpu突发。
这个方案,nginx的前端反代需要能够区分网站前后台流量,来导向不同的机器,下面是我的,供参考:
#前后台分离,前台使用tingtao_servers进行主备,后台指定为tingtao_admin_search_backend #这样大部分承载压力给到分流服务器,包括前台的页面、分类和静态文件;而后台为唯一服务器,这样方便数据库同步 ####################################################### # www.tingtao.org # 定义主备后端服务器组 upstream tingtao_servers { server 后端前台ip:443 weight=5; server 后端后台ip:443 backup; keepalive 32; keepalive_timeout 60s; keepalive_requests 1000; } # 定义专用后端服务器组 upstream tingtao_admin_search_backend { server 后端后台ip:443; } server { listen 80; listen [::]:80; server_name tingtao.org www.tingtao.org; keepalive_timeout 120; return 308 https://$host$request_uri; } server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name tingtao.org www.tingtao.org; keepalive_timeout 120; include /etc/nginx/badrequest.conf; ssl_certificate /var/www/ca/tingtao.org/fullchain.pem; ssl_certificate_key /var/www/ca/tingtao.org/privkey.pem; access_log syslog:server=日志服务器1:514,facility=local6,tag=tingtao,severity=info mainjson; access_log syslog:server=日志服务器2:514,facility=local6,tag=tingtao,severity=info mainjson; error_log /dev/null; resolver 8.8.8.8; more_clear_headers X-Powered-By Server; ############################################## # 共用代理设置 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 $scheme; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_connect_timeout 30s; proxy_send_timeout 30s; proxy_read_timeout 30s; proxy_ignore_headers Cache-Control Expires Set-Cookie Vary ; # 忽略后端缓存头 ############################################## location = /xmlrpc.php { return 444; } ############################################## # 设置缓存变量 set $should_cache 0; set $cache_skip 1; # 对静态文件设置缓存 if ($request_uri ~* "\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)(\?|$)") { set $should_cache 1; set $cache_skip 0; } # 对这些路径设置缓存 if ($request_uri ~* "^/(|myserver|platform|servermanager_agents|trade-mt5|trade-mt4|server-template|domain|about|(archives|page)(/.*)?)(|\\?.*)?$") { set $should_cache 1; set $cache_skip 0; } ############################################## # 1. 处理指定的路径 - 使用主备服务器 location ~* "^/(|myserver|platform|servermanager_agents|trade-mt5|trade-mt4|server-template|domain|about|(archives|page)(/.*)?)(|\\?.*)?$" { proxy_pass https://tingtao_servers; proxy_cache staticfile; proxy_cache_valid 200 302 301 6h; proxy_cache_valid 404 2m; proxy_cache_valid 500 1m; proxy_cache_use_stale error timeout invalid_header; proxy_no_cache $cache_skip; proxy_cache_bypass $cache_skip; add_header X-Cache $upstream_cache_status; #add_header X-Backend-Server $upstream_addr; } ############################################## # 2. 处理静态文件 - 使用主备服务器 location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)(\?|$) { proxy_pass https://tingtao_servers; proxy_cache staticfile; proxy_cache_valid 200 302 301 6h; proxy_cache_valid 404 2m; proxy_no_cache $cache_skip; proxy_cache_bypass $cache_skip; add_header X-Cache $upstream_cache_status; #add_header X-Backend-Server $upstream_addr; } ############################################## # 3. 所有其他请求 - 默认使用专用后端 location / { proxy_pass https://tingtao_admin_search_backend; # 非静态内容默认不缓存 proxy_no_cache 1; proxy_cache_bypass 1; #add_header X-Backend-Server $upstream_addr; } }
我这里了用了两个变量来确认某些地址、目录和某些文件类型进行缓存。
然后这些url和静态文件实际上也可以合并,但是因为缓存时间可能不同,所以我分开写了。
具体的路径什么的根据情况自己修改。
第四套方案,远程唯一后台+多个前端本地前台
聪明的你一定和我一样会想到,第三套方案的后端前台这个节点实际上可以省下来,在前端节点运行一个wp来执行网站前台,这样的话静态文件也可以从本地直接提供,有时候可能整体性能更高。
因为php只要预热过(也就是执行过页面),那么在某些插件的加持下,本地页面的速度是可以接近静态文件的。
那么这个方案同样需要数据库和文件系统的自动同步。
文件系统的脚本和前面一样。
nginx的设置文件需要做调整:
####################################################### # 站点:www.tingtao.org # 功能:本地前端+反代后端 ####################################################### # -------------------------- # 1. 后端服务器组定义 # -------------------------- # 专用后端组(非本地路径请求默认使用) upstream tingtao_admin_backend { server 后端后台ip:443; # 可根据实际需求修改为专用后端IP } # -------------------------- # 2. HTTP服务(仅用于重定向HTTPS) # -------------------------- server { listen 80; listen [::]:80; server_name tingtao.org www.tingtao.org; keepalive_timeout 120s; # 强制HTTP转HTTPS return 308 https://$host$request_uri; } # -------------------------- # 3. HTTPS主服务配置(核心逻辑) # -------------------------- server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name tingtao.org www.tingtao.org; keepalive_timeout 120s; # 网站根目录(全局设置) root /var/www/www.tingtao.org; # 索引文件配置 index index.php index.html index.htm; # -------------------------- # 3.1 SSL安全配置 # -------------------------- ssl_certificate /var/www/ca/tingtao.org/fullchain.pem; ssl_certificate_key /var/www/ca/tingtao.org/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; # -------------------------- # 3.2 日志配置 # -------------------------- access_log syslog:server=日志服务器1:514,facility=local6,tag=tingtao,severity=info mainjson; access_log syslog:server=日志服务器2:514,facility=local6,tag=tingtao,severity=info mainjson; #access_log /var/www/logs/www.tingtao.org-access.log mainjson; #error_log /var/www/logs/www.tingtao.org-err.log; error_log /dev/null ; # -------------------------- # 3.3 基础配置 # -------------------------- more_clear_headers X-Powered-By Server; include /etc/nginx/badrequest.conf; autoindex off; # -------------------------- # 3.4 明确需要反向代理的路径(优先匹配) # 例如后台管理路径,根据实际情况调整 # -------------------------- location ~* ^/(wp-admin|admin|backend|api)(/.*)?$ { proxy_pass https://tingtao_admin_backend; 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 $scheme; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_ignore_headers Cache-Control Expires Set-Cookie Vary; proxy_connect_timeout 30s; proxy_send_timeout 30s; proxy_read_timeout 30s; proxy_no_cache 1; proxy_cache_bypass 1; } # -------------------------- # 3.5 特殊路径拦截(防攻击) # -------------------------- location = /xmlrpc.php { return 444; } # -------------------------- # 3.6 本地静态文件处理 # -------------------------- location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ { expires 30d; add_header Cache-Control "public, max-age=2592000"; } # -------------------------- # 3.7 本地PHP页面处理(精确匹配需要本地处理的路径) # -------------------------- # 包含空路径""匹配首页/ # 包含所有需要匹配的单级路径(myserver、platform 等) # 包含需要匹配子路径的目录(Catalog、Articles) # 通过(|\\?.*)?处理可能存在的查询参数 # -------------------------- location ~* "^/(|myserver|platform|servermanager_agents|trade-mt5|trade-mt4|server-template|domain|about|(archives|page)(/.*)?)(|\\?.*)?$" { # WP Super Cache 规则 set $cache_uri $request_uri; if ($request_method = POST) { set $cache_uri 'null cache'; } if ($query_string != "") { set $cache_uri 'null cache'; } if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") { set $cache_uri 'null cache'; } try_files /webcache/supercache/$http_host/$cache_uri/index.html $uri $uri/ /index.php?$args; } # -------------------------- # 3.8 PHP处理配置 # -------------------------- location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(.*)$; fastcgi_pass unix:/var/run/php8.4-fpm-www.tingtao.org.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name; include fastcgi_params; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param PHP_ADMIN_VALUE "cgi.fix_pathinfo=1"; fastcgi_param PHP_ADMIN_VALUE "include_path= .:/var/www/globals/v.haote.net/lib:/usr/share/php/"; fastcgi_param PHP_ADMIN_VALUE "open_basedir= $document_root/:/tmp:/usr/share/php/"; fastcgi_param PHP_ADMIN_VALUE "upload_max_filesize= 50M"; fastcgi_param PHP_ADMIN_VALUE "max_execution_time= 300"; fastcgi_param PHP_ADMIN_VALUE "max_input_time= 60"; fastcgi_param PHP_ADMIN_VALUE "memory_limit= 256M"; fastcgi_param PHP_ADMIN_VALUE "output_buffering= 4096"; fastcgi_param PHP_ADMIN_VALUE "disable_functions= system,exec,shell_exec,passthru,error_log,dl,sys_getloadavg,pfsockopen,openlog,syslog,readlink,symlink,link,leak,popen,escapeshellcmd,proc_close,proc_get_status,proc_nice,proc_open,proc_terminate,escapeshellarg,pcntl_exec,show_source,highlight_file,ini_restore,apache_child_terminate,apache_get_modules,apache_get_version,apache_getenv,apache_note,apache_setenv,virtual,mb_send_mail,set_time_limit,max_execution_time,php_uname,disk_free_space,diskfreespace,stream_copy_to_stream"; fastcgi_param PHP_ADMIN_VALUE "allow_url_fopen= on"; fastcgi_param PHP_ADMIN_VALUE "expose_php= Off"; fastcgi_param PHP_ADMIN_VALUE "display_errors= Off"; fastcgi_param PHP_ADMIN_VALUE "post_max_size= 50M"; fastcgi_intercept_errors on; fastcgi_ignore_client_abort on; fastcgi_read_timeout 180; } # -------------------------- # 3.9 默认路由:所有其他请求路由到专用后端 # -------------------------- location / { proxy_pass https://tingtao_admin_backend; 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 $scheme; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_ignore_headers Cache-Control Expires Set-Cookie Vary; proxy_connect_timeout 30s; proxy_send_timeout 30s; proxy_read_timeout 30s; proxy_no_cache 1; proxy_cache_bypass 1; add_header Cache-Control "no-store, no-cache, must-revalidate" always; } }
这套方案可以省下一个节点的成本,同时也不需要前端--后端这样拉取前台数据,只要前端节点的性能不是太辣鸡,那么速度杠杠的。
本站目前就是这套方案。
第五套方案,若干前端节点+一个远程共用数据库
这套方案需要借助proxysql这类读写分离的软件,将数据库写入操作导向共用数据库,本质上与第四套方案相同,但是我没想好文件系统同步该怎么搞,所以做了一半就懒得继续尝试了。
因为wp的后台可以在线更新自身以及插件,那么如果前端节点1进行了更新,就需要同步到其他节点,或者通过前端节点2上传了图片,也需要做类似操作。这样的话就缺少像数据库那样的唯一仲裁节点,所以这个方案会变的过于复杂。
======================================= 单机环境的优化
接着是单机部分,这部分优化在多节点也需要做的,可以大幅度提升性能。
第一个插件:Autoptimize
这个插件在合理设置以后可以整合页面资源,将引用的部分js、css和小图片都整合进页面内容,还可以将这些内容进行缩减以及删掉注释等,从而大幅度减少请求次数,以获得性能提升。
第二个插件:WP Super Cache
这个应该许多人都在用,可以将页面生成近似静态文件,而更新、评论的时候可以自动刷新相关页面。效果很不错哦
===================================================
最后就是开启br压缩了,这是个见仁见智的事,br算法比较消耗cpu,但是压缩率也好一些,这个自行选择吧:
load_module /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so; load_module /usr/lib/nginx/modules/ngx_http_brotli_static_module.so; ## brotli brotli on; brotli_comp_level 7; brotli_buffers 16 32k; brotli_min_length 20; brotli_types text/html text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml font/woff2 font/woff ;
将这部分放在gzip前面,那么优先级就会高于gzip。
也不用担心浏览器不支持什么的,现在的web服务器和反代服务器都会根据浏览器情况来匹配合理的算法(至少我开发的是这样)。
我倒是挺期待nginx官方支持zstd算法的,这个在cpu消耗和压缩率方面整体最佳。
本文中的脚本和配置肯定不能直接拿去用的,路径、域名、证书、ip什么的,你肯定要根据自己情况修改,这部分就不啰嗦了。
单机环境的话,能做的相对有限,不管是反代自身还是用fastcgi_cache或者wp-supercache,其实思路都是减少php页面的执行时间来获得性能提升。
至于再进一步的自动故障转移这些,就不是本文讨论的范畴了。
==========================================
后续,发现还是有些小问题,我的同步脚本实际上并不能批量处理,所以做了优化,现在可以自动等待每一批更新结束后进行提交,而且可以自动忽略临时文件,避免做很多无用功,很帅。
同时我也挺纳闷,wp-supercache的装机量超百万,却有很低级的bug,还不止一个,自己花了一下午把bug修了,还大幅度优化了方案,形成了第六套,目前完美了,前端节点进行本地读取,也不再需要php了,后端节点自动同步更新后的文件给前端,也很帅哦。
细节就不发了,完美的东西不会是开源或者分享的。
过周末了,休息一下。