WordPress VPS 配置 Redis 对象缓存
在自有 VPS 上为 WordPress 配置 Redis 对象缓存:绑定 localhost、设置 maxmemory 和淘汰策略,并用 WP-CLI 验证缓存确实生效。
WordPress 的 Redis 对象缓存的作用
WordPress 的 Redis 对象缓存会将数据库查询结果存储在内存中。后续请求会从 Redis 读取这些结果,而不是再次查询 MySQL。WordPress 核心本身已经提供对象缓存,WP_Object_Cache,但该缓存位于 PHP 内存中,请求结束后就会被丢弃。drop-in 文件会将其替换为可连接 Redis 的对象缓存,因此缓存可以在不同请求之间保留。
对象缓存不是页面缓存。这一区别决定了本指南是否适合您的场景。页面缓存会存储某个 URL 生成的完整 HTML,后续直接返回该 HTML,无需运行 PHP。这比 Redis 能做到的任何优化都更快,并且适用于未登录访客。用户登录、将商品加入购物车或打开管理后台后,页面缓存就无法继续处理请求,WordPress 必须完整执行请求流程:引导、插件和查询。对象缓存可以降低这类请求的成本。它适用于页面缓存无法覆盖的流量:已登录会话、购物车、结账和 wp-admin。对于 WooCommerce 商店,这通常是大部分高成本流量。
两者可以同时使用,繁忙站点通常也需要同时配置两者。请明确您要解决的问题。如果是面向匿名读者的展示型站点,几乎所有速度提升都来自页面缓存;为其添加 Redis 通常不会带来明显变化。
开始前还需明确一个限制。对象缓存不会让慢查询变快。它只能避免重复执行已经运行过的查询。缓存未命中后的第一个请求仍需承担完整查询成本,因此执行未建立索引查询的插件,在每个缓存生命周期内仍会运行该查询一次。
首先需要准备
- 一台带有 shell 和
sudo的 Linux VPS。不需要控制面板。 - 由 PHP-FPM 提供服务的 WordPress,例如运行在 Ubuntu 24.04 上的 LAMP 环境中。
- 服务器上已安装 WP-CLI。这里的每个步骤都可以在管理界面中完成,但使用 shell 更快。
- 与 PHP 位于同一台机器上的 Redis。低延迟是使用 Redis 的关键,经过网络跳转后这一优势会消失。
以下命令适用于 Ubuntu 24.04、PHP 8.3 和 www-data Web 用户。请根据服务器环境调整 PHP 版本和用户。请在 WordPress 目录中运行 wp 命令,即运行包含 wp-config.php 的目录。
安装 Redis 和 PHP 扩展
sudo apt update
sudo apt install -y redis-server php-redis
sudo systemctl enable --now redis-server
redis-cli pingredis-cli ping 应返回 PONG。如果输出 Could not connect to Redis at 127.0.0.1:6379: Connection refused,说明服务器未运行,因此继续操作前请先读取 systemctl status redis-server。
php-redis 是 PhpRedis,即来自 PECL 的 C 扩展。它比纯 PHP 实现的 Predis 更快。插件存在时会自动使用它。PHP-FPM 会在启动时加载扩展,因此重启 PHP-FPM pool 前,新扩展不会生效。
sudo systemctl restart php8.3-fpm
php -m | grep redis请注意最后一项检查:php -m 列出的是命令行 PHP 加载的模块,而 FPM 可能加载另一组模块。应以插件自身的诊断结果为准,相关检查见下文。
截至 2026 年 8 月,Ubuntu 24.04 提供 Redis 7.0.15,作为对象缓存已经足够。如果需要当前版本,Redis 也提供自己的 APT 仓库。
sudo apt install -y lsb-release curl gpg
curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
sudo chmod 644 /usr/share/keyrings/redis-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list
sudo apt update
sudo apt install -y redis如果你的发行版提供 Valkey,这个项目是在 2024 年许可证变更后启动的分支。它使用相同的协议,下面的内容无需修改即可适用。
将 Redis 绑定到本机,阻止其他主机访问
Redis 默认没有密码。任何能够连接到 6379 端口的主机,都可以读取所有缓存值并运行 FLUSHALL。暴露在互联网中的实例会在几小时内被扫描器发现,因此应先配置网络访问,再进行性能调优。
打开 /etc/redis/redis.conf,确认包含以下内容:
bind 127.0.0.1 -::1
protected-mode yes然后检查实际监听情况,因为配置文件只是声明,ss 才是实际依据。
sudo ss -lntp | grep 6379127.0.0.1:6379 才是正确结果。0.0.0.0:6379 表示 Redis 正在公网接口上响应:修正 bind 行,然后重启服务。
PHP 和 Redis 位于同一台主机时,Unix socket 优于回环 TCP。通信路径中不经过 TCP 协议栈,访问权限由文件权限控制,而不是由之后可能被修改的防火墙规则控制。
unixsocket /run/redis/redis-server.sock
unixsocketperm 770该 socket 的所有者和所属组是 redis,因此 Web 用户必须加入该组。
sudo systemctl restart redis-server
sudo usermod -aG redis www-data
sudo systemctl restart php8.3-fpm
redis-cli -s /run/redis/redis-server.sock ping该命令还必须输出 PONG。Could not connect to Redis at /run/redis/redis-server.sock: Permission denied 表示该组权限尚未生效。检查 id www-data,并注意正在运行的 PHP-FPM 会保留启动时拥有的组信息,因此列表中包含重启操作。在确认 socket 可用之前,请保留 TCP 访问;否则一个拼写错误可能会同时导致两种访问路径失效。
Redis 应分配多少内存?
根据服务器本身的数据计算这个数值。未设置 maxmemory 时,Redis 会持续增长,直到内核耗尽内存并触发 OOM killer,通常终止占用内存最多的进程;在 WordPress 服务器上,这个进程往往是 MySQL。journalctl -k | grep -i "out of memory" 只能事后显示此次终止,而此时网站通常已经无法访问。
从总 RAM 开始逐项扣除。MySQL 或 MariaDB 会预留 innodb_buffer_pool_size,并为每个连接分配缓冲区。PHP-FPM 的内存开销是 pm.max_children 乘以单个 worker 的实际常驻内存,插件较多的网站通常为 64 MB 到 128 MB。内核和 Web 服务器需要几百 MB。剩余内存就是上限,Redis 只能使用其中一部分。
一个运行单个商城的 4 GB VPS 的预算示例
以下是示例数值,不是从您的服务器测得的数据。请将每项替换为服务器报告的实际值。
- 使用 1 GB buffer pool 的 MariaDB:1024 MB
- PHP-FPM,10 个 worker,每个 96 MB:960 MB
- 内核、nginx 或 Apache、sshd、日志记录:512 MB
- 剩余:约 1.5 GB
在这种情况下,将 maxmemory 设置为 256 MB 是合理的起始值。这样可以保留足够的余量,而且单个 WordPress 网站通常不需要更多内存。
现在通过测量取代猜测。让服务器承受一天的实际流量后:
redis-cli info memory | grep -E 'used_memory_human|maxmemory_human|maxmemory_policy'
redis-cli dbsize如果 used_memory_human 明显低于限制值,请降低限制,并将内存还给 MySQL,因为 MySQL 能更有效地使用这些内存。如果它一直达到限制值,并且 evicted_keys 全天持续增长,请提高限制。将该值设置在 /etc/redis/redis.conf 中。
maxmemory 256mb
maxmemory-policy allkeys-lruredis-cli config set maxmemory 256mb 只会立即生效,下一次重启时就会丢失,和直接使用 sysctl -w 一样容易出错。编辑文件,然后执行 sudo systemctl restart redis-server,再读回该值进行确认。再设置一道限制更稳妥:systemd 单元的 MemoryMax 上限可以防止配置错误的 Redis 使整台服务器停止工作。将它设置为高于 maxmemory 的值,不能设置为相同值,因为 cgroup 限制会终止进程,而不会淘汰 key。如果 Redis 在 WordPress 旁边的容器中运行,则应将相同的数值写入Compose 文件中的内存限制;选择在 Docker 中运行数据库还是在主机上运行时,也应采用相同的判断方法。
有意选择淘汰策略
全新的 Redis 默认使用 noeviction。检查当前配置:
redis-cli config get maxmemory-policy使用 noeviction 时,实例内存耗尽后会停止接受写入,并返回以下内容:
(error) OOM command not allowed when used memory > 'maxmemory'.这是本指南中最严重的故障模式,因为网站不会直接宕机,而是变慢。每次缓存写入都会失败,因此 WordPress 会回到数据库读取值,然后在下一次请求中再次尝试存储该值,并再次失败。此时,网站既要承担原有的全部数据库操作,还要为每个键额外执行一次 Redis 往返请求。WordPress 管理后台不会提示发生了此问题。该字符串会出现在 PHP 错误日志中,因此网站添加缓存后变慢时,请搜索 OOM command not allowed。
allkeys-lru 是这里合适的默认策略。内存不足时,Redis 会删除最近最少使用的键。这正是对象缓存需要的行为,因为其中的每个值都是 MySQL 中仍然存在的数据副本。丢失一个键只会增加一次查询。拒绝写入则会导致每个请求中的所有查询持续失败,直到有人发现问题。
不要在此场景中使用 volatile-* 策略。这些策略只考虑设置了过期时间的键;如果没有任何键设置过期时间,Redis 文档说明它们的行为类似于 noeviction。WordPress 存储的大多数对象缓存条目都没有 TTL,因此对象缓存使用 volatile-lru 后可能会耗尽内存并开始拒绝写入。如果流量经常集中访问少量键,allkeys-lfu 也是合理的替代方案,因为它按访问频率而不是最近使用时间淘汰键。请有意选择一种策略,并记录选择原因。
持久化:除非有明确理由,否则将其关闭
打包的 redis.conf 会通过类似 save 900 1 的配置启用 RDB 快照,并关闭追加文件。对于纯对象缓存,快照没有任何价值。根据定义,这些数据可以重新生成;而从二十分钟前的文件恢复的缓存包含一组过期值,WordPress 会直接信任这些值。
快照还会产生开销。BGSAVE 会派生进程,而写入期间的写时复制可能导致内存使用量大幅增加。在小型 VPS 上,这会显示在 Redis 日志中:
Can't save in background: fork: Cannot allocate memory启动时通常还会出现以下警告。这表示 Redis 认为后续派生进程可能失败:
WARNING overcommit_memory is set to 0! Background save may fail under low memory condition.要关闭快照,请在 /etc/redis/redis.conf 中设置空的保存计划,重启,然后确认该值恢复为空。
save ""sudo systemctl restart redis-server
redis-cli config get save只有当同一实例还保存着无法重建的数据(例如作业队列或速率限制计数器)时,才保留持久化。在这种情况下,应将两者拆分。缓存需要淘汰键,而持久化数据需要保留键;maxmemory 和淘汰策略作用于整个实例,而不是某个数据库索引。使用两个监听不同套接字的实例是最合理的方案。
安装插件并了解 drop-in
wp plugin install redis-cache --activate
wp redis enable
wp redis statuswp redis enable 成功时会输出 Object cache enabled.。它实际执行的操作是将 wp-content/plugins/redis-cache/includes/object-cache.php 复制到 wp-content/object-cache.php。这个副本就是 drop-in,而真正执行工作的也是 drop-in。WordPress 会在非常早的阶段加载 wp-content/object-cache.php,早于任何插件代码运行,因此缓存对整个请求都可用。已启用但没有安装 drop-in 的插件不会缓存任何内容。
错误消息会告诉您哪一部分失败。Object cache could not be enabled. 表示复制失败,因此运行 WP-CLI 的用户无法写入 wp-content。A foreign object cache drop-in was found. 表示另一个缓存插件已经占用了该文件名,解决方法是 wp redis update-dropin。如果消息以 Redis server is unreachable: 结尾,随后又出现客户端错误,则表示连接设置不正确,请返回 redis-cli ping。
如果复制因权限问题失败,请手动放置该文件,并将其所有者设为 Web 用户。
cp wp-content/plugins/redis-cache/includes/object-cache.php wp-content/object-cache.php
sudo chown www-data:www-data wp-content/object-cache.php删除插件不会删除 drop-in。先运行 wp redis disable,该命令会输出 Object cache disabled. 并删除文件。在 drop-in 仍然存在时删除插件目录,网站仍会运行旧的缓存代码,但没有插件可以更新它。
wp-config.php 中的连接设置
将以下内容添加到读取 /* That's all, stop editing! */ 的行之前,因为在该行之后定义的常量为时已晚。
define( 'WP_REDIS_HOST', '127.0.0.1' );
define( 'WP_REDIS_PORT', 6379 );
define( 'WP_REDIS_DATABASE', 0 );
define( 'WP_REDIS_PREFIX', 'shop_prod:' );对于 Unix socket,请设置方案和路径。此时会忽略主机和端口。
define( 'WP_REDIS_SCHEME', 'unix' );
define( 'WP_REDIS_PATH', '/run/redis/redis-server.sock' );
define( 'WP_REDIS_PREFIX', 'shop_prod:' );WP_REDIS_MAXTTL 会为每个键设置过期时间,单位为秒。使用 allkeys-lru 时不需要设置它;如果希望严格限制缓存值允许过期的最长时间,它会很有用。
一个 Redis,多个站点:前缀和数据库
Redis 默认提供 16 个编号数据库,每个数据库内只有一个扁平键空间。两个 WordPress 安装都连接到数据库 0,且未设置前缀时,会在同一空间中写入相同的键名。因此,一个站点可能读取并使用另一个站点的选项。请为每个站点设置独立前缀。
define( 'WP_REDIS_PREFIX', 'shopA_prod:' );
define( 'WP_REDIS_DATABASE', 1 );前缀用于区分键名。数据库索引用于区分键空间,这在清空数据时很重要:清空一个索引不会影响其他索引。该插件还记录了 WP_REDIS_SELECTIVE_FLUSH,它只删除与前缀匹配的键,而不是清空整个数据库,但需要扫描这些键。
前缀和索引无法隔离内存。maxmemory 和驱逐策略作用于整个 Redis 实例,因此繁忙站点可能挤出空闲站点的键,而且双方都不会报告这一情况。不能相互影响的站点需要使用独立的 Redis 实例,每个实例都有自己的 socket 和内存限制。
让预发布环境不要写入生产环境缓存
预发布站点通常是生产环境文件和数据库的副本,因此也是 wp-config.php 的副本,并使用相同的前缀和数据库索引。将它指向同一个 Redis 后,它会使用预发布环境的值写入生产环境的键。测试价格或修改选项随后可能直接出现在线上站点,无需部署,也不会留下记录。
手动为每个环境设置不同的标识。在预发布环境的 wp-config.php 中:
define( 'WP_REDIS_PREFIX', 'shop_staging:' );
define( 'WP_REDIS_DATABASE', 5 );更好的做法是为预发布环境使用独立的 Redis 实例,或者完全不使用对象缓存。define( 'WP_REDIS_DISABLED', true ); 会在运行时关闭缓存,同时保留 drop-in 文件。这也是最快确认问题是否由缓存导致的方法。
较早的教程会为此设置 WP_CACHE_KEY_SALT。该插件的 readme 将此常量标记为已弃用,并说明应改用 WP_REDIS_PREFIX,因此请使用新名称。
验证,而不是盲目信任
先从插件自身的诊断信息开始。
wp redis status最重要的是 Drop-in。Drop-in: Valid 表示 WordPress 正在加载该插件的文件。Drop-in: Not installed 表示复制从未完成,网站没有持久对象缓存,即使管理界面显示为绿色也是如此。Status 报告连接状态,Client 指明正在使用的扩展;在这里确认使用的是 PhpRedis,而不是 Predis。
然后直接询问 WordPress 核心,因为它不关心插件自身的判断。
wp eval 'var_dump( wp_using_ext_object_cache() );'bool(true) 表示核心正在使用外部对象缓存。
接着使用你配置的前缀,确认键确实正在写入。
redis-cli -n 0 dbsize
redis-cli -n 0 --scan --pattern 'shop_prod:*' | head你在网站中操作时,dbsize 持续增加,这就是证明。有效的 drop-in 配置下仍然没有任何键,说明连接在静默失败,或者实际使用的前缀与你认为的不同。
最后查看 Redis 记录的指标。
redis-cli info stats | grep -E 'keyspace_hits|keyspace_misses|evicted_keys|expired_keys'命中率为 keyspace_hits / (keyspace_hits + keyspace_misses),Redis 文档提供了该公式。解读时要注意两点。计数器覆盖整个 Redis 实例自上次重启以来的数据,因此会混合统计共享该实例的所有网站和应用。清空缓存或重启后立即查看的比率没有意义,因为缓存仍在重新填充。让它运行一个正常流量日。
不要将你的数值与托管公司发布的命中率或查询次数比较。那些数据反映的是其网站和插件组合。真正重要的是你自己的结果:在无法由页面缓存提供服务的页面上,分别测量启用前后的数据。
curl -o /dev/null -s -w '%{time_starttransfer}\n' -b cookies.txt https://example.com/my-account/使用已登录的 cookie jar 运行多次,先关闭缓存(WP_REDIS_DISABLED),再启用缓存。两者的差值就是结果。
Redis 导致 WordPress 变慢的情况
使用错误策略的实例已满是主要原因,上文已经介绍过:日志中出现 OOM command not allowed when used memory > 'maxmemory'.,同时网站还要为数据库和缓存支付资源成本。
Redis 位于另一台主机上是第二个原因。WordPress 在一次请求中会发起数百次对象缓存调用。如果一次请求发起 500 次调用,且每次往返耗时 1 ms,那么等待时间就是半秒;本地套接字不会产生这部分延迟。将 Redis 保持在同一台服务器上,或放在延迟低于 1 ms 的专用网络中。同机部署能从内核中获得多少收益,则是另一个问题:Linux 7.2 中新增的缓存感知调度会尝试让 PHP-FPM 和 Redis 等通信频繁的进程运行在共享缓存的 CPU 核心上,而 VPS 客户机从中获得的收益低于裸机。
自动加载选项表过大是第三个原因,旧网站上很常见。WordPress 会将所有自动加载的选项作为一个键进行缓存,因此每次请求都会通过连接传输数 MB 的数据。测量方法如下:
wp db query "SELECT ROUND(SUM(LENGTH(option_value))/1024) AS kb FROM wp_options WHERE autoload IN ('yes','on','auto','auto-on');"WordPress 6.6 新增了自动加载值,因此在现代安装中,只匹配 'yes' 的旧查询会低估结果。超过 1 MB 的数据应在选项表中处理,而不是在 Redis 中处理。
重启会清空所有缓存,因此 systemctl restart redis-server 之后的几分钟内,所有请求都会未命中,并触发数据库操作。请在流量较低时重启。此外,对象缓存不会阻止 wp-cron.php 在访客加载页面时触发;这也是请求变慢的独立原因:趁此将 WP-Cron 迁移到真正的 system cron 作业中:将 WP-Cron 迁移到真正的 system cron 作业。
日常维护
部署修改选项或主题代码后,使用 wp cache flush 清除缓存。如果插件更新后 drop-in 未自动更新,请运行 wp redis update-dropin,因为旧版插件的 drop-in 与新版插件配合使用,确实会导致异常行为。使用 redis-cli --stat 监控运行中的服务器;该命令每秒输出一行。redis-cli monitor 会输出每条命令,并在繁忙实例上消耗大量 CPU,因此只在复现问题时运行几秒,然后停止。
还有一个值得了解的数字:redis-cli info clients 会报告 connected_clients。PHP-FPM 为每个 worker 保持一个连接,因此该数值应与 pm.max_children 大致一致,不应高出一个数量级。如果高出很多,说明某个进程正在打开连接但没有关闭。
FAQ
如果使用 Redis 对象缓存,还需要页面缓存吗?
对于匿名流量,仍然需要。页面缓存无需运行 PHP 即可直接提供已存储的 HTML,这始终比在预热对象缓存的情况下运行 WordPress 成本更低。对象缓存负责处理页面缓存必须跳过的请求:已登录用户、购物车、结账和 wp-admin。在商城或会员网站上,两者都值得启用。如果网站访客从不登录,页面缓存几乎可以承担全部工作。
WordPress 应为 Redis 分配多少内存?
应根据您自己的服务器计算,而不是直接套用某个数值。先取总 RAM,减去 MySQL 缓冲池和每个连接的缓冲区,再减去 pm.max_children 乘以单个 PHP-FPM worker 的常驻内存大小,最后为内核和 Web 服务器预留几百 MB。将剩余内存的一部分分配给 Redis,然后在运行一天并产生流量后,检查 redis-cli info memory 中的 used_memory_human 并进行调整。单个 WordPress 网站通常只需几十 MB,因此在 4 GB 服务器上,256 MB 的 maxmemory 是一个宽裕的起始值。
为什么启用 Redis 对象缓存后,网站反而变慢了?
常见原因是实例使用了 noeviction 策略并已耗尽内存。Redis 会拒绝新的写入并返回 OOM command not allowed when used memory > 'maxmemory'.,因此 WordPress 会针对每个值回退到数据库,同时还会额外承担一次无效的 Redis 往返开销。检查 redis-cli config get maxmemory-policy,设置 allkeys-lru,并确认 maxmemory 不是很小。其他常见原因包括:Redis 服务器位于远程主机上,每个请求的数百次往返会累积明显延迟;以及自动加载的选项值达到数 MB,每个请求都会通过连接传输该值。
多个 WordPress 网站可以共用一个 Redis 服务器吗?
可以,但需要谨慎配置。为每个网站设置唯一的 WP_REDIS_PREFIX,避免键名冲突;同时使用单独的 WP_REDIS_DATABASE 索引,避免清空一个网站的数据时删除另一个网站的数据。它们仍会共享内存:maxmemory 和驱逐策略作用于整个实例,因此繁忙的网站可能驱逐空闲网站的键。相互之间不能产生影响的网站,应使用独立的 Redis 实例,并分别设置资源限制。
删除 wp-content/object-cache.php 安全吗?
安全。它是一个 drop-in 文件,不属于 WordPress 核心。删除后,WordPress 会恢复使用内置的每请求缓存。网站仍可正常运行,只是会执行更多数据库查询。优先使用 wp redis disable,它会安全删除该文件,并报告 Object cache disabled.。如果 Redis 已停止运行或行为异常,且您无法访问管理后台,手动删除该文件是正确的应急措施。