SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor

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 更快。
  • Redis 与 PHP 部署在同一台机器上。低延迟是关键,经过网络跳转后就失去了这一优势。

以下命令适用于 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 ping

redis-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,则可直接使用下文内容。Valkey 是在 2024 年许可证变更后启动的分支,使用相同的协议。

将 Redis 绑定到本机,避免其他主机访问

Redis 默认不设置密码。任何能够连接 6379 端口的主机,都可以读取所有缓存值并执行 FLUSHALL。暴露在互联网中的实例通常会在数小时内被扫描器发现,因此应先配置网络访问,再进行性能调优。

打开 /etc/redis/redis.conf,确认包含以下行:

bind 127.0.0.1 -::1
protected-mode yes

然后检查实际监听情况,因为配置文件只是声明,ss 才是实际证据。

sudo ss -lntp | grep 6379

127.0.0.1:6379 才是正确结果。0.0.0.0:6379 表示 Redis 正在公网接口上响应:修正 bind 行,然后重启服务。

当 PHP 和 Redis 位于同一台主机时,Unix 套接字比 loopback TCP 更合适。通信路径中不经过 TCP 协议栈,访问权限由文件权限控制,而不是由以后可能被修改的防火墙规则控制。

unixsocket /run/redis/redis-server.sock
unixsocketperm 770

该套接字的所有者和所属组是 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

该命令也必须输出 PONGCould not connect to Redis at /run/redis/redis-server.sock: Permission denied 表示该组权限尚未生效。检查 id www-data,并注意正在运行的 PHP-FPM 会保留启动时所属的用户组,因此列表中需要包含重启操作。在确认套接字可用之前,请保留 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-lru

redis-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 status

wp 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-contentA 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 实例,每个实例分别使用自己的 socket 和内存限制。

让 staging 不要使用 production 的缓存

staging 站点通常是 production 文件和数据库的副本,也就是 wp-config.php 的副本,并使用相同的前缀和数据库索引。将它指向同一个 Redis 后,它会使用 staging 的值写入 production 的键。测试价格或修改后的选项随后会出现在线上站点中,整个过程无需部署,也不会留下痕迹。

手动为每个环境设置不同的隔离值。在 staging 的 wp-config.php 中:

define( 'WP_REDIS_PREFIX', 'shop_staging:' );
define( 'WP_REDIS_DATABASE', 5 );

更好的做法是为 staging 配置独立的 Redis 实例,或完全不使用对象缓存。define( 'WP_REDIS_DISABLED', true ); 会在运行时关闭缓存,同时保留 drop-in 文件。这也是最快确认问题是否由缓存导致的方法。

旧教程会为此设置 WP_CACHE_KEY_SALT。该插件的 readme 将此常量标记为已弃用,并说明应改用 WP_REDIS_PREFIX,因此请使用新名称。

验证,而不是盲目信任

先从插件自身的诊断信息开始。

wp redis status

最重要的一行是 Drop-inDrop-in: Valid 表示 WordPress 正在加载此插件的文件。Drop-in: Not installed 表示复制从未完成,网站没有持久化缓存,即使管理界面看起来全部正常。Status 报告连接状态,Client 指明正在使用的扩展;在这里确认使用的是 PhpRedis,而不是 Predis。

然后直接询问 WordPress core,因为它不关心插件的判断。

wp eval 'var_dump( wp_using_ext_object_cache() );'

bool(true) 表示 core 正在使用外部对象缓存。

接着使用已配置的前缀,确认键确实正在写入。

redis-cli -n 0 dbsize
redis-cli -n 0 --scan --pattern 'shop_prod:*' | head

在网站中点击浏览时,dbsize 持续上升就是证据。有效的 drop-in 配合 0 个键,表示连接在静默失败,或者使用的前缀不是你以为的那个。

最后查看 Redis 为你统计的数据。

redis-cli info stats | grep -E 'keyspace_hits|keyspace_misses|evicted_keys|expired_keys'

命中率为 keyspace_hits / (keyspace_hits + keyspace_misses),Redis 文档给出了该公式。解读时要注意两点。计数器统计的是整个实例自上次重启以来的数据,因此会混合其中所有共享该实例的网站和应用。flush 或重启后立即得到的比率没有意义,因为缓存仍在填充。让它运行一个正常流量日。

不要将你的数值与托管服务商公布的命中率或查询次数比较。那些数据描述的是其网站和插件组合。真正重要的是你自己的数据:在页面缓存无法提供服务的页面上,分别在启用前后进行测量。

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 的私有网络中。

自动加载选项表过大是第三个原因,在旧网站中很常见。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,这也是请求变慢的独立原因:请趁此将其迁移到真正的系统 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 的常驻大小,最后再减去几百 MB,用于内核和 Web 服务器。将剩余内存的一部分分配给 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 服务器位于远程主机上,导致每个请求产生的数百次往返请求累积延迟;以及自动加载的 options 值达到数 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 已停止运行或行为异常,且您无法访问管理后台,手动删除文件是正确的应急处理方式。

#wordpress#redis#caching#性能#vps