1.0.8 • Published 6 months ago

@iocore/cache v1.0.8

Weekly downloads
-
License
-
Repository
-
Last release
6 months ago

@iocore/cache

npm version

IoCore 的多层缓存管理模块。

提供了一种基于模板和多种缓存存储后端(Dispenser)的缓存策略。

核心概念

  1. CacheDispenser<R> (抽象类): 定义了缓存存储的基本接口 (set, get, has, ttl, delete)。具体的实现类(如 @iocore/cache-dispenser-momery, @iocore/cache-dispenser-file, @iocore/cache-dispenser-ioredis)负责与特定的存储后端交互。
  2. CacheImplementer<T, R> (抽象类): 定义了具体的缓存逻辑。
    • 模板字符串 T: 用于动态生成缓存键。格式为 prefix:{paramName:type}:suffix...,例如 user:{userId:number}:profile:{field:string}
    • provide(options: ExtractParams<T>): Promise<[R, number?]>: 必须实现 的方法。当缓存未命中时,此方法被调用以生成原始数据 R。它可以选择性地返回一个缓存过期时间(秒)。
    • 缓存分发器 pools: 在构造函数中传入一个或多个 CacheDispenser CacheImplementer 会按顺序使用这些分发器。
  3. 缓存键生成: CacheImplementer 根据模板字符串 T 和传入的参数 options (类型由 ExtractParams<T> 推断) 自动生成缓存键。可以通过设置环境变量 CACHE_PREFIX 来添加全局缓存键前缀。
  4. 读写逻辑: readwrite 方法协调多个 CacheDispenser
    • read: 按顺序查询 Dispenser,第一个命中则返回数据,并将此数据写回到之前未命中的 Dispenser 中(缓存预热)。如果所有 Dispenser 都未命中,则调用 provide 获取数据,然后写入所有 Dispenser。
    • write: 调用 provide 获取数据,然后将数据写入所有 Dispenser。
    • remove: 删除所有 Dispenser 中的对应缓存键。

安装

npm install @iocore/cache @iocore/component --save
# 还需要至少安装一个 CacheDispenser 实现,例如:
npm install @iocore/cache-dispenser-momery --save
# or
yarn add @iocore/cache @iocore/component
yarn add @iocore/cache-dispenser-momery

依赖

  • @iocore/component: IoCore 核心组件系统。
  • 至少一个 CacheDispenser 实现包。

使用

import { Application, Component } from '@iocore/component';
import {
  CacheImplementer,
  CacheDispenser,
  ExtractParams,
} from '@iocore/cache';
import MomeryCacheDispenser from '@iocore/cache-dispenser-momery';
// import FileCacheDispenser from '@iocore/cache-dispenser-file'; // 假设使用文件作为二级缓存

// --- 定义缓存实现 ---

// 模板:缓存用户的配置信息,键类似 user:123:settings
type UserSettingsTemplate = 'user:{userId:number}:settings';
// 缓存的数据类型
interface UserSettingsData {
  theme: string;
  notifications: boolean;
  lastLogin: string;
}

@Application.Injectable() // 缓存实现本身也是一个 Component
class UserSettingsCache extends CacheImplementer<UserSettingsTemplate, UserSettingsData> {
  constructor(
    // 注入缓存分发器 (按顺序使用)
    @Application.Inject(MomeryCacheDispenser)
    memoryDispenser: CacheDispenser<UserSettingsData>,
    // @Application.Inject(FileCacheDispenser)
    // fileDispenser: CacheDispenser<UserSettingsData>,
  ) {
    super(
      'user:{userId:number}:settings', // 模板字符串
      // 传入 Dispenser 类
      MomeryCacheDispenser,
      // FileCacheDispenser,
    );
  }

  // 实现 provide 方法:当缓存未命中时,从源头获取数据
  protected async provide(
    options: ExtractParams<UserSettingsTemplate>
  ): Promise<[UserSettingsData, number?]> {
    console.log(`[UserSettingsCache] PROVIDE called for userId: ${options.userId}`);
    // 模拟从数据库或其他服务获取数据
    await new Promise(res => setTimeout(res, 80));
    const data: UserSettingsData = {
      theme: Math.random() > 0.5 ? 'dark' : 'light',
      notifications: Math.random() > 0.5,
      lastLogin: new Date().toISOString(),
    };
    // 返回数据和可选的过期时间 (例如 10 分钟)
    const expireInSeconds = 600;
    return [data, expireInSeconds];
  }
}

// --- 使用缓存 --- //

@Application.Injectable()
class UserProfileService extends Component {
  @Application.Inject(UserSettingsCache)
  private settingsCache: UserSettingsCache;

  async displaySettings(userId: number) {
    console.log(`\n--- Displaying settings for user ${userId} ---`);
    // 使用 read 方法获取缓存或从 provide 生成
    const settings = await this.settingsCache.read({ userId });
    console.log('Settings:', settings);
  }

  async refreshSettings(userId: number) {
    console.log(`\n--- Refreshing settings for user ${userId} ---`);
    // 使用 write 方法强制从 provide 获取并更新所有缓存
    const settings = await this.settingsCache.write({ userId });
    console.log('Refreshed Settings:', settings);
  }

  async clearSettingsCache(userId: number) {
    console.log(`\n--- Clearing cache for user ${userId} ---`);
    // 使用 remove 方法删除所有缓存中的条目
    await this.settingsCache.remove({ userId });
  }
}

// --- 启动应用 --- //

// 确保所有依赖 (包括 Dispenser 和 Implementer) 都被注入
@Application.Inject(
  MomeryCacheDispenser,
  // FileCacheDispenser,
  UserSettingsCache,
  UserProfileService
)
class BootApp extends Application {
  @Application.Inject(UserProfileService)
  service: UserProfileService;

  async main() {
    const userId = 42;
    await this.service.displaySettings(userId); // 第一次调用,执行 provide
    await this.service.displaySettings(userId); // 第二次调用,命中内存缓存
    await this.service.refreshSettings(userId); // 强制刷新,执行 provide 并写入缓存
    await this.service.displaySettings(userId); // 命中内存缓存 (已刷新)
    await this.service.clearSettingsCache(userId); // 清除缓存
    await this.service.displaySettings(userId); // 再次调用,执行 provide
  }

  initialize() {}
  terminate() {}
}

Application.start(BootApp);

CacheImplementer<T, R>

  • constructor(template: T, ...pools: INewAble<CacheDispenser<R>>[]): 构造函数。
    • template: 缓存键模板字符串。
    • pools: 按优先级顺序传入的 CacheDispenser 类。
  • protected abstract provide(options: ExtractParams<T>): Promise<[R, number?]>: 必须实现。定义数据源逻辑。
  • read(options: ExtractParams<T>): Promise<R>: 读取缓存。尝试按顺序从 pools 中获取,如果未命中则调用 provide,并将结果写回所有 pools
  • write(options: ExtractParams<T>): Promise<R>: 强制写入缓存。调用 provide 获取最新数据,并写入所有 pools
  • remove(options: ExtractParams<T>): Promise<void>: 从所有 pools 中删除缓存。

CacheDispenser<R> (抽象类)

定义缓存存储后端必须实现的接口。

  • set(key: string, value: R, time?: number): Promise<R>
  • has(key: string): Promise<boolean>
  • get(key: string): Promise<R>
  • ttl(key: string): Promise<number> (返回秒)
  • delete(key: string): Promise<unknown>

ExtractParams<Template>

一个 TypeScript 类型工具,用于从模板字符串中提取参数名和推断类型。

// type Params = { userId: number, type: string }
type Params = ExtractParams<'data:{userId:number}:{type:string}'>;

贡献

欢迎提交 Pull Request。对于重大更改,请先开一个 Issue 来讨论您想要更改的内容。

许可证

MIT

1.0.8

6 months ago

1.0.7

6 months ago

1.0.6

6 months ago

1.0.5

7 months ago

1.0.4

7 months ago

1.0.3

7 months ago

1.0.2

7 months ago

1.0.1

7 months ago

1.0.0

7 months ago