精华 XIUNOX 缓存系统优化 20260701

贰先生 管理 18小时前

当前缓存系统的痛点

1. 插件每次都要写防御性样板代码

从 grep 结果看,每个插件都要写这种重复代码:

$cached = function_exists('cache_get') ? cache_get($cacheKey) : NULL;
if($cached !== NULL && $cached !== FALSE) { return $cached; }
// ... 查库 ...
if(function_exists('cache_set')) { cache_set($cacheKey, $result, 300); }

TagService、FriendLinkService、CheckinService 都在重复这套模式。

2. 清除缓存要枚举所有可能的 limit 值

TagService.php:369-374:

cache_delete('xnx_tag_hot_5');
cache_delete('xnx_tag_hot_10');
cache_delete('xnx_tag_hot_15');
cache_delete('xnx_tag_hot_20');

如果调用方传 findHot(8),缓存永远不会被清除。FriendLinkService 同样问题。

3. 没有命名空间隔离

所有插件缓存键直接挂全局,键名冲突风险,也无法按插件整体清空。

4. cache_truncate()flushdb() 太暴力

cache_redis.class.php:67:会清空整个 Redis 数据库,如果 session 和 cache 共用 Redis 会把 session 也清掉。

5. 缓存键 > 32 字符自动 md5

cache.func.php:26:导致无法通过前缀批量删除,也无法从 Redis 中识别键的含义。

6. TTL 硬编码,无调试模式

每个 cache_set 硬编码 TTL(60/300/600),无法统一调整;没有命中率统计,插件开发者不知道缓存是否生效。


缓存系统优化

新建核心组件

lib/CacheHelper.php — 缓存辅助类,提供 5 大能力:

API

用途

示例

remember($key, $ttl, $callback, $plugin)

消除样板代码

CacheHelper::remember('hot', 300, fn() => db_find(...), 'tag')

pluginKey($key, $plugin)

插件命名空间前缀

生成 p_tag_hot / core_forumlist

pluginDeletePrefix($plugin)

通配符删除插件缓存

CacheHelper::pluginDeletePrefix('checkin') 一行清除整个插件

registerKeys($plugin, $keys)

注册缓存键(统计用)

后台面板显示插件缓存清单

getStats()

运行时命中率统计

后台面板显示命中/未命中次数

驱动层优化

  • cache_redis.class.php:新增 deleteByPrefix() 用 SCAN 代替 KEYS(生产安全)

  • cache_redis.class.phptruncate() 不再用 flushdb,改为 deleteByPrefix(cachepre) 避免误删 session

  • cache.func.php:新增 cache_delete_prefix() 全局函数

  • cache.func.php:md5 阈值从 32 放宽到 200,允许更长的明文键名

后台面板

other_cache_setting.htm 新增:

  • 缓存命中率统计(命中/未命中/命中率)

  • 已注册缓存键清单(按插件分组,显示 TTL/说明/命中次数)

  • 缓存预热按钮(手动触发核心缓存生成)

  • 按插件清除缓存按钮(每行一个删除按钮)

插件改造效果对比

改造前(TagService clearHotCache,需枚举所有 limit):

cache_delete('xnx_tag_hot_5');
cache_delete('xnx_tag_hot_10');
cache_delete('xnx_tag_hot_15');
cache_delete('xnx_tag_hot_20');  // 漏了 limit=8 的场景

改造后(一行清除整个插件):

CacheHelper::pluginDeletePrefix('tag');  // 自动清除所有 p_tag_* 键

改造前(CheckinService getRankList,10 行缓存逻辑):

$cacheKey = 'xnx_checkin_rank_' . $type;
$cached = function_exists('cache_get') ? cache_get($cacheKey) : NULL;
if ($cached !== NULL && $cached !== FALSE) { ... } else {
    // 查库
    if (function_exists('cache_set')) { cache_set($cacheKey, ..., 300); }
}

改造后(一行搞定):

$rankData = CacheHelper::remember('rank_' . $type, 300, function() use ($type) {
    // 查库
    return $data;
}, 'checkin');

插件开发者使用指南

新插件只需 3 步即可接入缓存系统:

// 1. 注册缓存键(构造函数中)
CacheHelper::registerKeys('myplugin', array(
    'hot_list' => array(300, '热门列表'),
    'user_data' => array(60, '用户数据'),
));

// 2. 使用缓存(一行代替 10 行样板)
$data = CacheHelper::remember('hot_list', 300, function() {
    return db_find('my_table', ...);
}, 'myplugin');

// 3. 清除缓存(一行清除整个插件)
CacheHelper::pluginDeletePrefix('myplugin');

后台「缓存设置」页面查看 统计面板,可以进行缓存预热和按插件清除 。

最新回复
  • outsider Lv2
    面对如此强贴,论遇到多大阻力,只要我一息上尚存,就绝不会让它沉沦下去,这一点请楼主放心。 
    16小时前

请先登录后再回复 登录

uid:1 管理
关注
随遇而安,随缘而行
发帖 39
评论 220
粉丝 9
关注 1
发新帖
目录

扫码手机打开本帖