本文约 5,342 字,预计阅读 14 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:装了 Redis 却不知道它在哪一层生效
配置 Redis 做对象缓存时,很多人按教程上传一个 object-cache.php 到 wp-content/,刷新一下就生效了。这个「上传即生效」的行为看起来有点神奇——WordPress 怎么会知道要去加载这个文件?
答案是 drop-ins。这是 WordPress 预留的一组按固定文件名探测的特殊文件,放在 wp-content/ 目录下,WordPress 启动时如果检测到对应文件名存在,就会加载它来替换或接管某个核心功能。它比插件更底层:不需要激活,没有后台界面,也不走插件的加载流程。
理解 drop-ins 有两个实际价值。一是排障——当你发现「Redis 没生效」「缓存插件装了但页面还是每次查数据库」时,需要知道去哪里看它到底有没有被加载。二是做高级优化——在只有一台服务器、又想避免装重型缓存插件的场景下,直接用 object-cache.php 加 Redis 往往比插件更轻更稳。
原理分析:drop-ins 与插件、mu-plugins 的区别
三者都能往 WordPress 里加代码,但介入的位置完全不同:
| 类型 | 放置位置 | 加载时机 | 能否后台停用 | 典型用途 |
|---|---|---|---|---|
| drop-ins | wp-content/ 根目录 |
核心启动的特定检查点 | 否 | 替换缓存、数据库层、多站点路由 |
| mu-plugins | wp-content/mu-plugins/*.php |
核心加载后、插件前 | 否 | 强制生效的站点规则 |
| 普通插件 | wp-content/plugins/ |
plugins_loaded 前后 |
能 | 业务功能 |
drop-ins 的识别方式极其简单粗暴:按文件名硬匹配。核心代码里到处是这样的判断——if ( file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ),存在就 include。所以文件名错一个字母就完全不生效,而且不会有任何提示。
完整的 drop-in 清单(这些文件名是固定的):
| 文件名 | 作用 | 加载位置 |
|---|---|---|
advanced-cache.php |
页面缓存的高级入口,命中则直接输出并终止 | wp-settings.php 早期 |
object-cache.php |
替换默认对象缓存实现(Redis/Memcached) | wp_start_object_cache() |
db.php |
替换数据库抽象层(自定义 wpdb 子类) |
require_wp_db() |
db-error.php |
数据库连接失败时的自定义错误页 | wpdb 连接失败后 |
install.php |
自定义安装流程 | 安装阶段 |
maintenance.php |
自定义维护模式页面 | 核心升级期间 |
sunrise.php |
多站点域名映射的早期接管 | ms-settings.php 之前 |
blog-deleted.php / blog-inactive.php / blog-suspended.php |
多站点子站对应状态的页面 | 多站点加载时 |
与缓存相关的两个需要额外条件:advanced-cache.php 只有在 wp-config.php 里定义了 define( 'WP_CACHE', true ); 时才会被加载;object-cache.php 则只要文件存在就会接管。
接管对象缓存后,核心的 wp_cache_get()、wp_cache_set()、wp_cache_delete()、wp_cache_flush() 这些函数都会被替换为 drop-in 里的实现,缓存也就从「单请求内存内」变成「跨请求持久化」。可以用 wp_using_ext_object_cache() 判断是否已启用外部对象缓存。
保姆级实操
第一步:先确认当前站点已经启用了哪些 drop-ins
后台「工具 → 站点健康 → 信息」标签页里有 Dropped-in 相关的展示,也可以用命令行列出:
<?php
// list-dropins.php
require_once ABSPATH . 'wp-admin/includes/plugin.php';
foreach ( get_dropins() as $file => $info ) {
echo str_pad( $file, 24 ) . $info['Name'] . PHP_EOL;
}
wp eval-file list-dropins.php
get_dropins() 返回的是「已存在」的 drop-in,没列出的就是没启用。
第二步:部署 object-cache.php(Redis 示例)
从 Redis 对象缓存插件(如 Redis Object Cache)那里拿到 object-cache.php,或直接使用其提供的版本。放在 wp-content/object-cache.php,然后在 wp-config.php 里配置连接信息:
// wp-config.php —— 这些常量要在 WordPress 加载之前定义
define( 'WP_REDIS_HOST', '127.0.0.1' );
define( 'WP_REDIS_PORT', 6379 );
define( 'WP_REDIS_TIMEOUT', 1 );
define( 'WP_REDIS_READ_TIMEOUT', 1 );
define( 'WP_CACHE_KEY_SALT', 'example_com_' ); // 多站共用 Redis 时必须加前缀,避免 key 冲突
第三步:验证对象缓存是否真的生效
wp eval 'var_dump( wp_using_ext_object_cache() );'
# 写入再读回,验证跨请求持久化
wp eval 'wp_cache_set("mmf_test","hello","mmf",300);'
wp eval 'var_dump( wp_cache_get("mmf_test","mmf") );'
# 看后端里 key 是否真的存在
redis-cli --scan --pattern '*mmf_test*'
第二条命令跨了两次进程还能读回来,才说明是真的持久化缓存,而不是每次请求的内存缓存。
第四步:启用 advanced-cache.php
// wp-config.php
define( 'WP_CACHE', true );
然后在 wp-content/advanced-cache.php 里写命中逻辑。这个文件在 WordPress 极早期加载,能拿到的全局变量有限,通常直接读 $_SERVER['REQUEST_URI']:
<?php
// wp-content/advanced-cache.php(简化示意)
$mmf_key = md5( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] );
$mmf_dir = WP_CONTENT_DIR . '/cache/pages/';
$mmf_ttl = 3600;
if ( ! empty( $_COOKIE ) ) {
return; // 带 Cookie 的请求(登录态、购物车)不命中缓存
}
if ( is_file( $mmf_dir . $mmf_key ) && filemtime( $mmf_dir . $mmf_key ) > time() - $mmf_ttl ) {
header( 'X-Cache: HIT' );
readfile( $mmf_dir . $mmf_key );
exit;
}
真实生产环境不会这么简陋,需要处理 gzip、HEAD 请求、失效策略,但原理就是这个。
第五步:自定义 db.php(高级用法)
如果需要在数据库层做统一处理(比如慢查询日志、读写分离),可以继承 wpdb:
<?php
// wp-content/db.php
require_once ABSPATH . WPINC . '/class-wpdb.php';
class MMF_wpdb extends wpdb {
public function query( $query ) {
$start = microtime( true );
$result = parent::query( $query );
$cost = microtime( true ) - $start;
if ( $cost > 0.5 ) {
error_log( sprintf( '[slow-query] %.3fs %s', $cost, substr( $query, 0, 200 ) ) );
}
return $result;
}
}
$GLOBALS['wpdb'] = new MMF_wpdb( DB_USER, DB_PASSWORD, DB_NAME, DB_HOST );
第六步:自定义 db-error.php
<?php
// wp-content/db-error.php
header( 'HTTP/1.1 503 Service Unavailable' );
header( 'Retry-After: 600' );
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="utf-8"><title>服务暂时不可用</title></head>
<body><h1>服务暂时不可用</h1><p>正在维护中,请稍后再试。</p></body>
</html>
常见踩坑
-
文件名写错或放错目录:必须是
wp-content/根目录,不是wp-content/plugins/。一个字母错了完全静默失效。 -
忘了
define( 'WP_CACHE', true ):advanced-cache.php不会被加载,表现为缓存插件装了但没效果。 -
多个缓存插件争抢同一个 drop-in:页面缓存插件会覆盖
advanced-cache.php,两个同时启用必然冲突。同一时刻只能有一个所有者。 -
多站共用 Redis 没加 key 前缀:两个站会互相污染缓存,表现为内容串站。用
WP_CACHE_KEY_SALT隔离。 -
object-cache.php 版本与插件不匹配:WordPress 更新后旧 drop-in 可能不兼容,表现为后台报
wp_cache_*相关错误。 -
案例: 某站点把 Redis 缓存插件卸载后,站点反而每页多出几百毫秒延迟。原因是
object-cache.php文件留在了wp-content/里,插件没了但 drop-in 还在生效,它连接的 Redis 实例已经下线,每个缓存操作都要等连接超时。删除该 drop-in 后立刻恢复。教训是:卸载缓存类插件后必须检查 drop-in 残留——插件的卸载钩子不一定会删除它,而 drop-ins 不会出现在插件列表里,极易被忽略。 -
把 drop-in 当成放业务代码的地方:它不是插件,没有激活流程、没有更新机制,业务代码放这里会难以维护。
总结
记住三句话:drop-ins 是按固定文件名探测的核心替换点;它在 wp-content/ 根目录、不出现在插件列表、无法后台停用;缓存相关的两个里,advanced-cache.php 依赖 WP_CACHE 常量,object-cache.php 只要文件存在就接管。
日常运维建议:把「已启用的 drop-ins」加入巡检清单,用 get_dropins() 定期核对,避免卸载插件后留下失效的 drop-in 拖慢站点。
如果你需要的是「强制生效但不替换核心组件」的代码,应该走 mu-plugins 必用插件 而不是 drop-ins;而排查站点慢时,先确认对象缓存是否真的生效,再去看 插件数量与性能 里提到的 autoload 等瓶颈。
