用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了,后端节点自动同步更新后的文件给前端,也很帅哦。

细节就不发了,完美的东西不会是开源或者分享的。

过周末了,休息一下。

作者 听涛

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注