正在加载文档...
文档内容较大,正在处理中,请稍候
正在加载文档...
文档内容较大,正在处理中,请稍候
本文档详细说明了后端服务实现的缓存管理模块,涵盖缓存提供器、缓存策略、缓存键规范、模块时间戳等核心概念,以及缓存使用、性能优化、最佳实践等内容,确保系统的高性能和高可用性。
缓存管理模块是后端服务的核心组件,它提供了统一的缓存服务接口,支持 Redis 和 内存 两种缓存实现。通过智能的提供器选择和自动降级机制,确保系统的高可用性和高性能。
在 Web 应用中,数据库查询往往是性能瓶颈。缓存可以:
const cacheService = require("../cache");
const { generateCacheKey, getCacheStrategyName } = require("../cache/utils/cacheKeyHelper");// 生成缓存键
const cacheKey = generateCacheKey("user", "item", `user_123`);
const strategyName = getCacheStrategyName("user", "item");
// 尝试从缓存读取
const cachedData = await cacheService.get(cacheKey, strategyName);
if (cachedData) {
return cachedData; // 缓存命中,直接返回
}
// 缓存未命中,从数据库查询
const data = await User.findById(123);
// 写入缓存
await cacheService.set(cacheKey, data, strategyName);
return data;const { clearCacheKeys } = require("../cache/utils/cacheKeyHelper");
// 更新数据后清除相关缓存
await User.updateById(id, updateData);
await clearCacheKeys("user", "update", userId, id);恭喜! 你已经掌握了缓存的基本使用。接下来让我们深入了解各个概念。
缓存模块支持两种提供器:
系统会自动选择最优的提供器:
应用启动
↓
初始化内存缓存(确保始终可用)
↓
尝试连接 Redis
↓
连接成功? → 是 → 使用 Redis(优先)
↓ 否
使用内存缓存(降级)重要:Redis 不可用时,系统会自动降级到内存缓存,确保服务可用。
缓存策略定义了不同数据的缓存行为,包括:
// config/base/cache.js
strategies: {
// 默认策略
defaults: {
ttl: 3600, // 1小时
useRedis: true,
enabled: true
},
// 用户模块策略
user: {
item: { ttl: 3600, useRedis: true }, // 用户详情:1小时
list: { ttl: 1800, useRedis: true }, // 用户列表:30分钟
}
}// 使用策略名称(推荐)
await cacheService.set(key, value, "user.item");
// 不使用策略(使用默认配置)
await cacheService.set(key, value);缓存键采用统一的命名规范,确保可读性和可维护性。
{prefix}:{version}:{module}:{resourceType}:{identifier}[:operation]示例:
pc:v1:user:item:user_123 - 用户详情pc:v1:user:list:user_1:{"page":1,"limit":10,"fh":"abc123","dp":"def456","epoch":"1234567890"} - 用户列表pc:v1:permission:codes:user_123 - 用户权限码const { generateCacheKey } = require("../cache/utils/cacheKeyHelper");
// 生成标准缓存键
const key = generateCacheKey("user", "item", "user_123");
// 结果: "pc:v1:user:item:user_123"
// 生成模式(用于批量删除)
const pattern = getCacheKeyPattern("user", "list");
// 结果: "pc:v1:user:list:*"重要:始终使用工具函数生成缓存键,避免硬编码字符串。
模块时间戳是缓存失效的核心机制。当数据变更时,更新模块时间戳,使所有相关缓存自动失效。
用户数据更新
↓
更新 user 模块时间戳(epoch)
↓
所有包含旧 epoch 的缓存键自动失效
↓
下次查询时使用新 epoch,缓存未命中
↓
从数据库重新加载数据并缓存const { getModuleEpoch, bumpModuleEpoch } = require("../cache/utils/cacheKeyHelper");
// 获取当前时间戳
const epoch = await getModuleEpoch("user");
// 更新数据后,提升时间戳
await User.updateById(id, data);
await bumpModuleEpoch("user"); // 所有 user 相关缓存失效这是最常见的缓存使用场景。
const {
generateCacheKey,
getModuleEpoch,
getCacheStrategyName,
} = require("../cache/utils/cacheKeyHelper");
const cacheService = require("../cache");
async function getUser(req, res) {
const { id } = req.params;
// 1. 获取模块时间戳
const epoch = await getModuleEpoch("user_item");
// 2. 生成缓存键
const cacheKey = generateCacheKey("user", "item", `user_${id}`, epoch);
const strategyName = getCacheStrategyName("user", "item");
// 3. 尝试从缓存读取
const cachedData = await cacheService.get(cacheKey, strategyName);
if (cachedData !== null && cachedData !== undefined) {
// 缓存命中,直接返回
return success(req, res, "从缓存获取用户信息成功", cachedData);
}
// 4. 缓存未命中,从数据库查询
const userinfo = await User.findById(id);
if (!userinfo) {
return notFound(req, res, "用户不存在");
}
// 5. 写入缓存
await cacheService.set(cacheKey, userinfo, strategyName);
return success(req, res, "获取用户信息成功", userinfo);
}null 和 undefined 都表示缓存未命中列表缓存需要考虑分页、过滤条件、数据权限等因素。
const {
buildListKeys,
readListResponse,
writeListResponse,
getModuleEpoch,
buildDataPermHash,
getCacheStrategyName,
} = require("../cache/utils/cacheKeyHelper");
const cacheService = require("../cache");
async function getAllUsers(req, res) {
const currentUserId = req.user?.id;
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
// 1. 构建过滤条件
const filters = {};
if (req.query.username) {
filters.username = req.query.username;
}
// 2. 构建数据权限哈希(用于隔离不同数据域)
const dataPerm = req.dataPermission || null;
const dpHash = buildDataPermHash(dataPerm);
// 3. 获取模块时间戳
const epoch = await getModuleEpoch("user");
// 4. 构建列表缓存键
const keys = buildListKeys(
"user", // 模块名
"list", // 资源类型
`user_${currentUserId}`, // 标识符
page, // 页码
limit, // 每页数量
filters, // 过滤条件
dpHash, // 数据权限哈希
epoch // 模块时间戳
);
const strategyName = getCacheStrategyName("user", "list");
// 5. 尝试从缓存读取
const cacheData = await readListResponse(page, limit, keys, strategyName);
if (cacheData) {
// 缓存命中,刷新 TTL
await cacheService.set(keys.responseKey, cacheData, strategyName);
return success(req, res, "从缓存获取用户列表成功", cacheData);
}
// 6. 缓存未命中,从数据库查询
const [users, total] = await Promise.all([
User.findList(currentUserId, page, limit, filters, dataPerm),
User.findCount(currentUserId, filters, dataPerm),
]);
// 7. 写入缓存
const responseData = await writeListResponse(page, limit, keys, users, total, strategyName);
return success(req, res, "获取用户列表成功", responseData);
}buildListKeys 会自动将过滤条件序列化并计算哈希dpHash 确保不同数据域的缓存隔离写入缓存通常在查询数据库后进行。
// 使用策略名称(推荐)
await cacheService.set(cacheKey, data, "user.item");
// 使用默认策略
await cacheService.set(cacheKey, data);// 覆盖策略的 TTL(秒)
await cacheService.set(cacheKey, data, "user.item", 300); // 5分钟数据更新后需要清除相关缓存,确保数据一致性。
const { clearCacheKeys } = require("../cache/utils/cacheKeyHelper");
// 创建用户
await User.create(userData);
await clearCacheKeys("user", "create", userId);
// 更新用户
await User.updateById(id, updateData);
await clearCacheKeys("user", "update", userId, id);
// 删除用户
await User.deleteById(id);
await clearCacheKeys("user", "delete", userId, id);// 删除单个键
await cacheService.del(cacheKey, "user.item");
// 按模式清除
await cacheService.clearByPattern("pc:v1:user:list:*");缓存穿透是指查询不存在的数据,导致每次请求都穿透缓存直接查询数据库。
// 恶意请求:查询不存在的用户ID
GET / api / users / 999999; // 用户不存在
GET / api / users / 888888; // 用户不存在
// ... 大量请求导致数据库压力// 定义空值标记
const NULL_MARKER = { __isNullMarker: true };
async function getUser(req, res) {
const { id } = req.params;
const cacheKey = generateCacheKey("user", "item", `user_${id}`, epoch);
const strategyName = getCacheStrategyName("user", "item");
// 1. 尝试从缓存读取
const cachedData = await cacheService.get(cacheKey, strategyName);
if (cachedData !== null && cachedData !== undefined) {
// 2. 检查是否是空值标记
if (cachedData.__isNullMarker === true) {
return notFound(req, res, "用户不存在"); // 直接返回,不查数据库
}
return success(req, res, "从缓存获取用户信息成功", cachedData);
}
// 3. 从数据库查询
const userinfo = await User.findById(id);
if (!userinfo) {
// 4. 数据不存在,缓存空值标记(短TTL)
await cacheService.set(cacheKey, NULL_MARKER, strategyName, 300); // 5分钟
return notFound(req, res, "用户不存在");
}
// 5. 数据存在,缓存实际数据
await cacheService.set(cacheKey, userinfo, strategyName);
return success(req, res, "获取用户信息成功", userinfo);
}当数据更新时,需要清除所有相关的缓存,确保数据一致性。
const { clearCacheKeys } = require("../cache/utils/cacheKeyHelper");
await clearCacheKeys(module, operationType, userid, id, ids);module: 模块名称(如 'user', 'role', 'permission')operationType: 操作类型('create', 'update', 'delete', 'bindRoles' 等)userid: 当前操作用户IDid: 操作对象IDids: 关联对象ID数组(可选)// 创建用户
await User.create(userData);
await clearCacheKeys("user", "create", userId);
// 自动清除:用户列表缓存
// 更新用户
await User.updateById(id, updateData);
await clearCacheKeys("user", "update", userId, id, roleIds);
// 自动清除:用户列表缓存、用户详情缓存、相关角色缓存
// 绑定角色
await User.bindRoles(userId, roleIds);
await clearCacheKeys("user", "bindRoles", userId, userId, roleIds);
// 自动清除:用户绑定角色缓存、用户权限缓存、用户菜单缓存系统会根据操作类型自动清除相关缓存:
| 操作类型 | 清除的缓存 |
|---|---|
create |
模块列表缓存 |
update |
模块列表缓存、详情缓存、关联缓存 |
delete |
模块列表缓存、详情缓存、绑定关系缓存 |
bindRoles |
绑定关系缓存、权限缓存、菜单缓存 |
批量操作可以提高性能,减少网络往返。
const keys = ["user:1", "user:2", "user:3"];
const values = await cacheService.mget(keys, "user.item");
// 返回: [userData1, userData2, userData3]await cacheService.mset([
{ key: "user:1", value: userData1, strategyName: "user.item" },
{ key: "user:2", value: userData2, strategyName: "user.item" },
{ key: "user:3", value: userData3, strategyName: "user.item" },
]);await cacheService.mdel(["user:1", "user:2", "user:3"], "user.item");权限验证是高频操作,使用缓存可以大幅提升性能。
// src/middlewares/permissionMiddleware.js
const cacheService = require("../cache");
const { generateCacheKey } = require("../cache/utils/cacheKeyHelper");
async function checkPermission(req, res, next) {
const currentUserId = req.user.id;
// 1. 生成权限缓存键
const cacheKeyCodes = generateCacheKey("permission", "codes", `user_${currentUserId}`);
const permStrategyName = "permission.codes";
// 2. 尝试从缓存读取
let userPermissionCodes = await cacheService.get(cacheKeyCodes, permStrategyName);
if (!userPermissionCodes) {
// 3. 缓存未命中,从数据库查询
const sql = `
SELECT DISTINCT p.permission_code
FROM permission p
INNER JOIN role_permission rp ON p.id = rp.permission_id
INNER JOIN user_role ur ON rp.role_id = ur.role_id
WHERE ur.user_id = ? AND p.is_delete = 0 AND p.status = 1
`;
const [rows] = await db.execute(sql, [currentUserId]);
userPermissionCodes = rows.map((row) => row.permission_code);
// 4. 写入缓存(5分钟)
await cacheService.set(cacheKeyCodes, userPermissionCodes, permStrategyName, 300);
}
// 5. 验证权限
const hasPermission = userPermissionCodes.includes(requiredPermission);
if (!hasPermission) {
return forbidden(req, res, "权限不足");
}
next();
}┌─────────────────────────────────────────────────────────┐
│ 应用层 (Controllers) │
│ 使用统一的 cacheService API,无需关心底层实现 │
└──────────────────────┬──────────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────────┐
│ 缓存服务层 (cacheService.js) │
│ • 提供器选择 • 策略管理 • 统一接口 │
└──────────────┬──────────────────────┬───────────────────┘
│ │
┌──────────▼──────────┐ ┌────────▼──────────┐
│ MemoryCache │ │ RedisCache │
│ (内存缓存) │ │ (Redis缓存) │
└─────────────────────┘ └────────────────────┘
│ │
┌──────────▼──────────┐ ┌────────▼──────────┐
│ Map 数据结构 │ │ Redis 客户端 │
└─────────────────────┘ └────────────────────┘// 1. 默认使用内存缓存(确保始终可用)
this.memoryProvider = new MemoryCache();
// 2. 尝试连接 Redis
if (redisModuleAvailable && !explicitlyDisabled) {
this.redisProvider = new RedisCache();
// 3. 重试机制(最多3次,每次间隔100ms)
let retryCount = 0;
while (retryCount < 3) {
if (this.redisProvider.isConnected()) {
this.redisAvailable = true;
break;
}
await sleep(100);
retryCount++;
}
}
// 4. 选择默认提供器
this.defaultProvider = this.redisAvailable ? this.redisProvider : this.memoryProvider;// 根据策略选择提供器
function getCacheProvider(strategyName) {
const strategy = getCacheStrategy(strategyName);
// 策略明确禁用 Redis
if (strategy.useRedis === false) {
return this.memoryProvider;
}
// Redis 可用且策略允许使用
if (this.redisAvailable && strategy.useRedis !== false) {
return this.redisProvider;
}
// 降级到内存缓存
return this.memoryProvider;
}缓存模块集成了完善的监控功能,支持:
const cacheMonitor = require("../cache/utils/cacheMonitor");
const stats = cacheMonitor.getStats();
console.log(stats);
// {
// totalOperations: 1000,
// hits: 850,
// misses: 150,
// hitRate: 85.0,
// avgResponseTime: 2.3,
// memoryHitRate: 80.0,
// redisHitRate: 90.0,
// byModule: {
// user: { totalOperations: 500, hitRate: 88.0 }
// }
// }配置文件位置:config/base/cache.js
module.exports = {
// 缓存类型:memory 或 redis(可通过环境变量覆盖)
type: process.env.CACHE_TYPE || "memory",
// 缓存键前缀
keyPrefix: "pc",
// 内存缓存配置
memory: {
maxItems: 1000, // 最大缓存项数
useLRU: true, // 是否使用LRU淘汰策略
checkInterval: 60000, // 清理检查间隔(毫秒)
},
// 缓存策略(见下一节)
strategies: { ... }
};defaults: {
ttl: 3600, // 默认过期时间(秒)
useRedis: true, // 是否使用Redis
warmable: false, // 是否支持预热
version: "v1", // 缓存版本
enabled: true, // 是否启用缓存
}user: {
item: { ttl: 3600, useRedis: true, enabled: true }, // 用户详情:1小时
list: { ttl: 1800, useRedis: true, enabled: true }, // 用户列表:30分钟
bindroles: { ttl: 1800, useRedis: true }, // 绑定角色:30分钟
},
permission: {
item: { ttl: 3600, useRedis: true, enabled: true },
list: { ttl: 7200, useRedis: true, enabled: true }, // 权限列表:2小时
codes: { ttl: 1800, useRedis: true, enabled: true }, // 权限码:30分钟
},可通过环境变量覆盖配置:
# 设置缓存类型
CACHE_TYPE=redis
# 其他配置可通过环境配置文件覆盖
# config/env/production.js/**
* 获取缓存
* @param {string} key - 缓存键
* @param {string} strategyName - 策略名称(可选)
* @returns {Promise<any>} 缓存的值,不存在返回 null
*/
const value = await cacheService.get(key, strategyName);/**
* 设置缓存
* @param {string} key - 缓存键
* @param {any} value - 缓存值
* @param {string} strategyName - 策略名称(可选)
* @param {number} customTtl - 自定义TTL(可选,覆盖策略TTL)
* @returns {Promise<boolean>} 是否设置成功
*/
await cacheService.set(key, value, strategyName, customTtl);/**
* 删除缓存
* @param {string} key - 缓存键
* @param {string} strategyName - 策略名称(可选)
* @returns {Promise<boolean>} 是否删除成功
*/
await cacheService.del(key, strategyName);/**
* 根据模式清除缓存
* @param {string} pattern - 键模式(支持glob,如 "pc:v1:user:list:*")
* @param {Object} options - 选项
* @param {boolean} options.resetStats - 是否重置统计信息
* @returns {Promise<boolean>} 是否清除成功
*/
await cacheService.clearByPattern("pc:v1:user:list:*", { resetStats: false });const { generateCacheKey } = require("../cache/utils/cacheKeyHelper");
const key = generateCacheKey("user", "item", "user_123");
// 结果: "pc:v1:user:item:user_123"const { getCacheKeyPattern } = require("../cache/utils/cacheKeyHelper");
const pattern = getCacheKeyPattern("user", "list");
// 结果: "pc:v1:user:list:*"const { clearCacheKeys } = require("../cache/utils/cacheKeyHelper");
await clearCacheKeys("user", "update", userId, id, roleIds);const { getModuleEpoch, bumpModuleEpoch } = require("../cache/utils/cacheKeyHelper");
// 获取时间戳
const epoch = await getModuleEpoch("user");
// 更新时间戳(使所有相关缓存失效)
await bumpModuleEpoch("user");缓存管理 API 位于 /api/monitor/cache/*,需要认证。
GET /api/monitor/cache?search=user&page=1&pageSize=100DELETE /api/monitor/cache/:keyGET /api/monitor/cache/statsgenerateCacheKey 生成缓存键strategyName 使用配置的策略mget/mset原因:
clearCacheKeys解决:
clearCacheKeys原因:
解决:
原因:
解决:
cacheService.getProviderType() 返回的类型方法:
GET /api/monitor/cache/statsGET /api/monitor/cachegetProviderType() 确认使用的提供器问题:大量缓存同时过期,导致请求全部打到数据库。
解决方案:
问题:查询不存在的数据,每次都穿透缓存查询数据库。
解决方案:
NULL_MARKER)问题:热点数据过期,大量请求同时查询数据库。
解决方案:
缓存还不是特别完善,后面再重构吧