Midway 缓存组件实战:基于 cache-manager v5 的多级缓存、自动刷新与装饰器缓存
后端微服务云原生【免费下载链接】midway A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 项目地址https://gitcode.com/gh_mirrors/mi/midway点击查看免费下载缓存是提升应用性能最简单有效的技术之一把热点数据放到更靠近调用方的存储中减少重复计算和下游访问延迟。Midway 官方基于 cache-manager v5 重新封装了缓存组件midwayjs/cache-manager支持内存、Redis 等多种 Store支持多级缓存聚合与后台自动刷新并提供了Caching装饰器一键缓存方法结果。读完本文你将掌握在 Midway 标准项目 / Serverless / 一体化应用中配置缓存、使用装饰器与 API 两种方式操作缓存、搭建多级缓存、实现自动刷新以及接入三方 Store 的完整方案。组件定位与能力矩阵根据 caching.md 中的说明该组件面向以下场景描述是否支持可用于标准项目✅可用于 Serverless✅可用于一体化✅包含独立主框架❌包含独立日志❌也就是说它不是一个独立可运行的框架而是作为 Midway 的组件被引入到现有应用中与主框架、日志等其他能力协同工作。组件底层 fork 自 cache-manager并在其基础上做了适配支持 Node.js v18 及以下版本、新增了methodWrap方法见 base/cacheManager.ts 的文件头注释并集成了 Midway 的依赖注入、配置体系与链路追踪。安装组件在项目根目录执行$ npm i midwayjs/cache-manager3 --save或者在package.json中手动增加依赖后重新安装{ dependencies: { midwayjs/cache-manager: ^3.0.0 // ... } }安装完成后组件提供的主要导出包括CachingFactory缓存服务工厂负责按配置创建并管理各个缓存实例MidwayCache/MidwayMultiCache单级缓存与多级缓存的类型定义Caching方法缓存装饰器createRedisStore快速创建基于 Redis 的 Store。启用组件在src/configuration.ts中导入组件并加入importsimport { Configuration } from midwayjs/core; import * as cacheManager from midwayjs/cache-manager; import { join } from path; Configuration({ imports: [ // ...其他组件 cacheManager, ], importConfigs: [ join(__dirname, config), ], }) export class MainConfiguration { }组件在启动时会读取cacheManager配置并在onReady阶段初始化CachingFactory及全部缓存实例同时注册Caching装饰器的拦截逻辑见 configuration.ts。CachingFactory本身是一个单例的ServiceFactory通过initClients批量创建配置中声明的每一个缓存实例见 factory.ts。配置缓存Store 与基础参数在使用前需要为缓存指定“存到哪里”。每个缓存对应一个 StoreMidway 内置了内存缓存也可以接入 Redis 或三方 Store。最简配置——创建一个名为default的内存缓存// src/config/config.default.ts export default { cacheManager: { clients: { default: { store: memory, }, }, }, };实际项目中通常还会补充两个关键参数// src/config/config.default.ts export default { cacheManager: { clients: { default: { store: memory, options: { max: 100, ttl: 10, }, }, }, }, };参数说明ttl缓存过期时间单位是毫秒注意不是秒max缓存 key 的最大个数超过后触发淘汰不同的 Store 淘汰 key 的算法不同内存缓存基于lru-cache使用LRU 算法。从源码看内存 Store 的配置类型为MemoryConfig除max、ttl外还支持sizeCalculation按值大小计算容量、shouldCloneBeforeSet写入前克隆等 lru-cache 原生选项见 base/types.ts。当store为字符串memory时工厂会调用memoryStore(args)创建 LRU 缓存实例见 base/cacheManager.ts。使用缓存get / set / del / reset通过装饰器注入实例借助InjectClient(CachingFactory, default)可以把名为default的缓存实例注入到任意Provide()类中import { InjectClient, Provide } from midwayjs/core; import { CachingFactory, MidwayCache } from midwayjs/cache-manager; Provide() export class UserService { InjectClient(CachingFactory, default) cache: MidwayCache; async invoke(name: string, value: string) { // 设置缓存 await this.cache.set(name, value); // 获取缓存 const data await this.cache.get(name); // ... } }动态设置过期时间set的第三个参数可以按单次调用动态指定ttl毫秒await this.cache.set(key, value, 1000);若某条缓存永不过期将ttl设为0即可await this.cache.set(key, value, 0);删除与清空删除单个 keyawait this.cache.del(key);清理整个缓存使用resetawait this.cacheManager.reset();⚠️危险操作reset会清空整个缓存 Store。如果使用 Redis 作为 Store将清空整个 Redis 数据务必谨慎。这一点在源码中体现得尤为明显Redis Store 的reset方法直接抛出MidwayCommonError提示flushdb()过于危险如确有需要请通过redisServiceFactory.get(client)获取 Redis 实例手动执行见 store.ts。这意味着在 Redis 场景下调用reset会直接报错从机制上杜绝误操作。通过工厂 API 获取实例除了装饰器注入也可以在运行时通过工厂 API 动态获取缓存实例import { Inject, Provide } from midwayjs/core; import { CachingFactory, MidwayCache } from midwayjs/cache-manager; Provide() export class UserService { Inject() cachingFactory: CachingFactory; async invoke() { const caching await this.cachingFactory.get(default); // ... } }CachingFactory还提供了类型更明确的getCaching(cacheKey)与getMultiCaching(cacheKey)方法见 factory.ts。配置多个缓存实例与 Midway 其他组件一致cacheManager.clients支持声明任意多个独立缓存实例// src/config/config.default.ts export default { cacheManager: { clients: { default: { store: memory, }, otherCaching: { store: memory, }, }, }, };随后可以分别注入import { InjectClient, Provide } from midwayjs/core; import { CachingFactory, MidwayCache } from midwayjs/cache-manager; Provide() export class UserService { InjectClient(CachingFactory, default) cache: MidwayCache; InjectClient(CachingFactory, otherCaching) customCaching: MidwayCache; }工厂在创建时会校验实例名多级缓存中引用了不存在的实例名会直接抛出cache instance xxx not found in yyy的明确错误见 factory.ts该行为在 factory.test.ts 中有对应测试。配置不同 StoreRedis 与三方 Store复用组件内 Redis 实例如果项目已经配置了midwayjs/redis组件可以通过组件内置的createRedisStore方法快速创建 Redis Store并复用已配置的 Redis 实例import { createRedisStore } from midwayjs/cache-manager; // src/config/config.default.ts export default { cacheManager: { clients: { default: { store: createRedisStore(default), options: { ttl: 10, }, }, }, }, redis: { clients: { default: { port: 6379, host: 127.0.0.1, }, }, }, };createRedisStore(default)中的参数是已配置的 Redis 实例名运行时它会通过RedisServiceFactory获取对应实例并包装成兼容 cache-manager 接口的 Store见 store.ts。从源码实现看这个 Redis Store 具备以下特性值统一通过JSON.stringify序列化后写入读取时自动JSON.parse还原解析失败则原样返回因此可以缓存对象、数组等复杂结构见 store.ts设置了ttl时使用 Redis 的PX命令按毫秒过期ttl为0或未设置时则永久写入见 store.tsttl查询使用 Redis 的pttl毫秒精度与自动刷新机制配合reset被禁用见上文危险操作说明。配置三方 Storecache-manager 社区提供了多种 Store 引擎Midway 组件完全兼容。以cache-manager-ioredis-yet为例// src/config/config.default.ts import { redisStore } from cache-manager-ioredis-yet; export default { cacheManager: { clients: { default: { store: redisStore, options: { port: 6379, host: localhost, ttl: 10, }, }, }, }, };这里store直接传入了三方 Store 的工厂函数一个(options) Store | PromiseStore形式的工厂工厂在创建时会把options透传给该函数见 factory.ts。因此三方 Store 的配置选项如这里的port、host与 Midway 的ttl等通用配置可以写在同一options中。官方 Store 引擎列表可参考 cache-manager 仓库的 store-engines 说明。多级缓存Multi-level Cachingcache-manager 支持把多个缓存 Store 聚合为一个多级缓存实现“L1 内存 L2 Redis”之类的经典组合。配置如下// src/config/config.default.ts import { createRedisStore } from midwayjs/cache-manager; export default { cacheManager: { clients: { memoryCaching: { store: memory, }, redisCaching: { store: createRedisStore(default), options: { ttl: 10, }, }, multiCaching: { store: [memoryCaching, redisCaching], options: { ttl: 100, }, }, }, }, redis: { clients: { default: { port: 6379, host: 127.0.0.1, }, }, }, };multiCaching的store是一个数组按优先级从上到下排列查找时先查memoryCaching内存中不存在 key 时继续查redisCaching。从源码看多级缓存的实现非常直观见 base/cacheManager.tsget按顺序遍历各级缓存取到第一个非undefined的值即返回set/del/reset并行调用所有级联缓存对应方法保证各级数据一致wrap/methodWrap命中高层缓存后还会把值回填backfill到更靠近调用方的低层缓存中让热点数据逐渐“上浮”到 L1。创建时工厂会对store数组中的每一项做解析字符串表示引用已存在的实例名对象表示内联的子 Store 配置函数则作为 Store 工厂执行见 factory.ts。测试用例 factory.test.ts 验证了“默认实例 内联实例”混合声明多级缓存的场景。使用多级缓存mset / mget / mdel多级缓存除了set、get、del还增加了批量方法mset、mget、mdelimport { InjectClient, Provide } from midwayjs/core; import { CachingFactory, MidwayMultiCache } from midwayjs/cache-manager; const userId 456; const key2 user_ userId; const ttl 5; Provide() export class UserService { InjectClient(CachingFactory, multiCaching) multiCache: MidwayMultiCache; async invoke() { // 设置到所有级别的缓存 await this.multiCache.set(foo2, bar2, ttl); // 从最高优先级的缓存 Store 中获取 key console.log(await this.multiCache.get(foo2)); // bar2 // 调用每一个 Store 的 del 方法进行删除 await this.multiCache.del(foo2); // 在所有缓存中批量设置多个 key await this.multiCache.mset( [ [foo, bar], [foo2, bar2], ], ttl ); // mget() 从最高优先级的缓存中获取值 // 如果第一个缓存 Store 中不包含所有的 key // 继续在下一个缓存 Store 中查找缺失的 key。 // 递归执行直到 // - 所有 key 都查找到值 // - 所有缓存 Store 都被查找过 console.log(await this.multiCache.mget(key, key2)); // [bar, bar2] // 调用每一个 Store 的 mdel 方法进行删除 await this.multiCache.mdel(foo, foo2); } }注意mget的语义与get一致从高优先级向低优先级查找缺失的 key直到全部找到或所有 Store 都被查过。其实现会并行读取各级 Store 的mget结果按位置合并补全见 base/cacheManager.ts。自动刷新refreshThreshold无论普通缓存还是多级缓存都支持后台自动刷新。只需配置refreshThreshold毫秒// src/config/config.default.ts export default { cacheManager: { clients: { default: { store: memory, options: { refreshThreshold: 3 * 1000, }, }, }, }, };工作原理每次从缓存获取值后会检查该 key 的剩余ttl如果剩余ttl小于refreshThreshold系统会异步重新执行取值逻辑刷新缓存同时本次调用仍然返回旧值直到ttl真正过期。也就是说在缓存即将过期前就开始“续命”把昂贵的计算/查询转移到后台用户几乎感知不到缓存击穿。源码实现位于createCache的wrap与methodWrap中命中缓存且设置了refreshThreshold时通过store.ttl(key)获取剩余过期时间-1表示永不过期小于阈值则用coalesceAsync合并并发请求后异步刷新见 base/cacheManager.ts。coalesceAsync的作用是让相同 key 的并发调用共享同一个执行结果避免重复计算。factory.test.ts 中验证了刷新行为阈值内再次读取仍拿到旧值刷新完成后读到新值。使用自动刷新时有几点需要注意多级缓存下根据优先级顺序找到第一个包含该 key 的 Store 执行刷新如果阈值设置得过低且取值函数执行较慢key 可能在刷新完成前就过期遇到并发更新的情况后台刷新机制目前只支持单个 key如果 key 没有设置ttl则不会触发刷新机制对于 Redis未显式设置ttl时默认为-1永不过期因此也不会触发刷新。自动缓存Caching 装饰器除了手动get/set组件还提供了Caching装饰器可以把方法结果自动缓存起来非常适合缓存 HTTP 响应或服务调用结果。缓存方法结果import { Provide } from midwayjs/core; import { Caching } from midwayjs/cache-manager; Provide() export class UserService { Caching(default) async getUser(name: string) { return name; } }第一次调用getUser时正常执行逻辑并返回结果装饰器会把结果写入default缓存第二次调用时若缓存未失效则直接从缓存返回不再执行方法体。指定缓存 ttl第二个参数可以单独设置ttl毫秒Provide() export class UserService { Caching(default, 100) async getUser(name: string) { return name; } }手动指定缓存 key对自动生成的 key 不满意时可以显式指定Provide() export class UserService { Caching(default, customKey, 100) async getUser(name: string) { return name; } }关于默认 key 的生成规则未手动指定 key 时装饰器会调用getClassMethodDefaultCacheKey按类名-类UUID-方法名的格式生成见 configuration.ts。因此不同类、不同实例、不同方法的缓存 key 天然隔离不容易互相污染。带自定义逻辑的缓存如果希望按特定逻辑决定是否缓存、缓存到哪个 key例如依据特定参数或 Header可以在第二个参数传入一个工具函数import { Provide } from midwayjs/core; import { Caching } from midwayjs/cache-manager; function cacheBy({ methodArgs, ctx, target }) { if (methodArgs[0] harry || methodArgs[0] mike) { return cache1; } } Provide() export class UserService { Caching(default, cacheBy, 100) async getUser(name: string) { return hello name; } }执行结果await userService.getUser(harry); // hello harry已缓存 await userService.getUser(mike); // hello harry命中同一 key 的缓存 await userService.getUser(lucy); // hello lucy跳过缓存重新执行工具函数的入参options包含三个字段methodArgs当前调用方法的实际参数ctx如果是请求作用域则是当前调用的上下文对象如果是单例则该对象为空对象target当前调用的实例即方法所属对象。函数返回值为字符串或布尔值返回字符串表示以该字符串为 key 缓存方法结果返回undefined或null表示跳过缓存直接执行原方法。借助这些参数可以实现非常灵活的自定义缓存策略。底层实现Caching装饰器注册了一个around拦截器见 configuration.ts。拦截器先解析出缓存 key函数式则先调用函数若 key 是字符串就调用缓存实例的methodWrap(cacheKey, proceed, args, ttl)执行“先查缓存、未命中则执行原方法并回填”的包装逻辑若 key 非字符串如返回undefined则直接放行原方法。装饰器的签名重载cacheKeyOrTTL既可以是数字也可以是函数在 decorator/cacheKey.ts 中有清晰定义。常见问题多进程下内存缓存 set 和 get 无法得到相同值这是正常现象每个进程的内存是独立的数据仅保存在当前进程内多进程/多实例之间无法共享。如需跨进程共享缓存请使用 Redis 这类分布式缓存系统例如上文通过createRedisStore或三方 Redis Store 接入。附组件内部结构与追踪能力速览packages/cache-manager/src的目录结构如下可作为深入阅读的索引base/fork 自 cache-manager 的底层实现——caching创建单级缓存、createCache包装 Store 并实现wrap/methodWrap/自动刷新、multiCaching多级缓存聚合、prmoiseCoalesce并发请求合并、store内存 LRU Store、types类型定义decorator/cacheKey.tsCaching装饰器定义configuration.ts组件配置类注册装饰器拦截器与默认 key 生成逻辑factory.tsCachingFactory服务工厂负责按配置创建单级/多级缓存并为get/set/del/wrap/methodWrap/mget/mset/mdel/reset方法绑定链路追踪上下文span 名为cache.${method}属性包含midway.cache.client、midway.cache.method等见 factory.tsstore.tscreateRedisStore与 Redis Store 实现interface.ts对外类型MidwayCache、MidwayMultiCache、CacheManagerOptions等。对应的测试位于 packages/cache-manager/test涵盖工厂初始化、多级缓存创建、ttl函数式配置、refreshThreshold自动刷新、Redis Store 读写与过期等关键行为是理解组件语义的绝佳参考。赞分享后端微服务云原生【免费下载链接】midway A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 项目地址https://gitcode.com/gh_mirrors/mi/midway点击查看免费下载相关推荐Midway 缓存组件实践指南基于 cache-manager 的内存、Redis、多级缓存与自动刷新Midway 缓存组件实践指南基于 cache manager 的内存、Redis、多级缓存与自动刷新 Midway 官方在 cache manager ht后端微服务云原生Midway 缓存组件 midwayjs/cache-manager 完全指南多 Store、多级缓存与自动刷新Midway 缓存组件 midwayjs/cache manager 完全指南多 Store、多级缓存与自动刷新 缓存是提升应用性能最简单也最有效的技术之一后端微服务云原生MidwayJS midwayjs/cache-manager 缓存组件完全指南内存、Redis、多级缓存与 Caching 装饰器MidwayJS midwayjs/cache manager 缓存组件完全指南内存、Redis、多级缓存与 Caching 装饰器 缓存是提升应用性能最后端微服务云原生上一篇Airbyte source-k6-cloud 声明式连接器全解析从 manifest 配置到验收测试下一篇如何在普通电脑上跑通PCSX2 PS2模拟器5分钟零基础配置与进阶调优创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考