一、IMS缓存体系架构总览
在IMS信息管理系统中,缓存是提升数据读取性能、降低数据库压力的关键手段。一个设计合理的缓存体系,通常由本地缓存和分布式缓存两层组成,各自承担不同的职责。
1.1 多级缓存架构
IMS系统采用 L1 本地缓存 + L2 分布式缓存的两级架构。L1 位于应用进程内,访问延迟在纳秒级别,适合存放变更频率低、体积小的配置数据;L2 基于 Redis 集群,访问延迟在亚毫秒级别,适合存放需要跨实例共享的业务数据。
// 多级缓存管理器核心接口
interface IMSCacheManager {
get<T>(key: string): Promise<T | null>;
set<T>(key: string, value: T, ttl?: number): Promise<void>;
delete(key: string): Promise<void>;
invalidate(pattern: string): Promise<void>;
}
class MultiLevelCache implements IMSCacheManager {
private l1: LocalCache;
private l2: RedisCache;
async get<T>(key: string): Promise<T | null> {
// 先查L1,命中则直接返回
const local = this.l1.get<T>(key);
if (local !== null) return local;
// 再查L2,命中后回填L1
const remote = await this.l2.get<T>(key);
if (remote !== null) {
this.l1.set(key, remote, 300);
}
return remote;
}
}
1.2 缓存选型对比
本地缓存常用方案包括 Map、LRU Cache 等;分布式缓存则以 Redis 为主流。下表对比了各方案的核心差异:
- 本地 Map:零依赖,无淘汰策略,适合静态配置
- LRU Cache:自带淘汰策略,适合热点数据窗口
- Redis Standalone:部署简单,适合中小规模系统
- Redis Cluster:支持水平扩展,适合大规模生产环境
二、本地缓存实现与最佳实践
2.1 LRU 缓存实现
IMS系统中频繁访问的权限树、字典数据等,适合采用 LRU 策略的本地缓存。当缓存容量达到上限时,自动淘汰近段时间内使用次数较少的数据。
class LRULocalCache<T> {
private cache = new Map<string, { data: T; expire: number }>();
private maxSize: number;
constructor(maxSize: number = 500) {
this.maxSize = maxSize;
}
get(key: string): T | null {
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() > entry.expire) {
this.cache.delete(key);
return null;
}
// 刷新访问顺序,实现LRU淘汰
this.cache.delete(key);
this.cache.set(key, entry);
return entry.data;
}
set(key: string, data: T, ttlMs: number = 60000): void {
if (this.cache.has(key)) this.cache.delete(key);
else if (this.cache.size >= this.maxSize) {
// 淘汰Map中的首条记录(即近期使用少的)
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, { data, expire: Date.now() + ttlMs });
}
}
2.2 本地缓存使用场景
在IMS系统中,本地缓存适合以下场景:
- 系统配置项:如功能开关、参数配置,变更频率极低
- 数据字典:下拉选项、枚举映射等,数据量小且读多写少
- 权限树结构:用户菜单与按钮权限,在一次会话中基本不变
需要注意本地缓存不具备跨实例一致性,当配置发生变更时,需要通过消息广播通知各实例刷新。
三、分布式缓存与 Redis 实战
3.1 Redis 连接与封装
IMS系统基于 ioredis 封装了统一的缓存客户端,支持集群模式、连接池、断线重连等生产级特性。
import Redis from 'ioredis';
class IMSRdisCache {
private client: Redis;
private keyPrefix = 'ims:';
constructor(nodes: string[]) {
this.client = new Redis.Cluster(nodes, {
redisOptions: { password: process.env.REDIS_PASSWORD },
retryStrategy: (times) => Math.min(times * 200, 5000),
maxRetriesPerRequest: 3,
});
}
async get<T>(key: string): Promise<T | null> {
const raw = await this.client.get(this.keyPrefix + key);
return raw ? JSON.parse(raw) : null;
}
async set<T>(key: string, value: T, ttlSec?: number): Promise<void> {
const fullKey = this.keyPrefix + key;
const serialized = JSON.stringify(value);
if (ttlSec) {
await this.client.setex(fullKey, ttlSec, serialized);
} else {
await this.client.set(fullKey, serialized);
}
}
// 批量获取,减少网络往返
async mget<T>(keys: string[]): Promise<(T | null)[]> {
const fullKeys = keys.map(k => this.keyPrefix + k);
const results = await this.client.mget(...fullKeys);
return results.map(r => r ? JSON.parse(r) : null);
}
}
3.2 缓存Key设计规范
良好的Key命名是缓存可维护性的基础。IMS系统遵循 业务域:实体:标识 三段式命名规范:
ims:user:10001—— 用户信息缓存ims:perm:tree:10001—— 用户权限树ims:dict:department—— 部门字典数据ims:config:system—— 系统配置项
Key的TTL需要根据业务特点设置:用户会话数据可设 1800s,字典数据设 86400s,配置项设 3600s 并配合主动刷新机制。
四、缓存穿透、雪崩与击穿防护
4.1 缓存穿透:布隆过滤器与空值缓存
当大量请求查询数据库中不存在的数据时,缓存无法命中,请求全部穿透到数据库,这就是缓存穿透。IMS系统采用布隆过滤器前置拦截与空值缓存双重防护策略。
class AntiPenetrationCache {
private bloomFilter: BloomFilter;
private nullValueTTL = 60; // 空值缓存60秒
async getOrLoad<T>(
key: string,
loader: () => Promise<T | null>
): Promise<T | null> {
// 第一层:布隆过滤器快速判断
if (!this.bloomFilter.mightContain(key)) {
return null;
}
// 第二层:查缓存
const cached = await this.cache.get(key);
if (cached !== null) return cached;
// 第三层:查数据库
const data = await loader();
if (data === null) {
// 空值缓存,防止穿透
await this.cache.set(key, '__NULL__', this.nullValueTTL);
return null;
}
await this.cache.set(key, data);
return data;
}
}
4.2 缓存雪崩与击穿
缓存雪崩指大量缓存Key在同一时刻集中过期,导致请求涌入数据库。解决方案是给TTL添加随机偏移量:
// TTL基础值 + 随机偏移,避免集中过期
function randomizedTTL(base: number): number {
const jitter = Math.floor(Math.random() * base * 0.3);
return base + jitter;
}
缓存击穿指某个热点Key过期的瞬间,大量并发请求同时穿透到数据库。IMS系统采用分布式锁 + 单飞模式来解决:
class SingleFlightCache {
private inflight = new Map<string, Promise<any>>();
async getOrLoad<T>(key: string, loader: () => Promise<T>): Promise<T> {
if (this.inflight.has(key)) {
return this.inflight.get(key);
}
const promise = loader().finally(() => {
this.inflight.delete(key);
});
this.inflight.set(key, promise);
return promise;
}
}
五、缓存一致性策略
5.1 Cache-Aside 模式与双写
Cache-Aside 是IMS系统采用的缓存读写基础模式:读操作先查缓存,未命中再查数据库并回写缓存;写操作先更新数据库,再删除缓存。相比更新缓存,删除策略更简单且不易产生数据不一致。
class CacheAsideService<T> {
async findById(id: string): Promise<T | null> {
const cacheKey = `entity:${id}`;
const cached = await this.cache.get<T>(cacheKey);
if (cached) return cached;
const data = await this.db.findById(id);
if (data) await this.cache.set(cacheKey, data, 600);
return data;
}
async update(id: string, data: Partial<T>): Promise<void> {
// 先更新数据库
await this.db.update(id, data);
// 再删除缓存
await this.cache.delete(`entity:${id}`);
}
}
5.2 延迟双删与消息队列保障
在高并发场景下,Cache-Aside 的"先更新DB、再删除缓存"仍可能出现短暂不一致。IMS系统采用延迟双删策略,并通过消息队列做可靠性兜底:
async updateWithDoubleDelete(id: string, data: Partial<T>) {
// 1. 先删除缓存
await this.cache.delete(`entity:${id}`);
// 2. 更新数据库
await this.db.update(id, data);
// 3. 延迟再删一次(防止并发读回填旧值)
setTimeout(async () => {
await this.cache.delete(`entity:${id}`);
}, 500);
// 4. 消息队列兜底,确保最终一致
await this.mq.publish('cache:invalidate', { key: `entity:${id}` });
}
六、总结
IMS系统缓存策略的设计要点归纳如下:
- 采用L1本地 + L2分布式的两级缓存架构,兼顾访问速度与跨实例共享
- 本地缓存使用LRU策略,适合字典、配置等低变更数据;分布式缓存基于Redis Cluster,承载业务核心数据
- 通过布隆过滤器 + 空值缓存防护穿透,TTL随机偏移防护雪崩,单飞模式防护击穿
- 缓存一致性以Cache-Aside为基础,关键路径叠加延迟双删和消息队列保障最终一致
- Key命名遵循三段式规范,TTL按业务特征分级设置
缓存是系统性能优化的利器,但也引入了数据一致性这一固有的复杂度。在实际工程中,需要根据业务对一致性的容忍度,在性能与正确性之间做出合理取舍。没有通用的缓存银弹,只有在特定场景下经过验证的缓存策略组合。