【RuoYi-Eggjs】:缓存,让你的应用飞起来

前言

在 Web 应用开发中,缓存是提升性能的关键手段。无论是减少数据库查询、加快 API 响应,还是降低服务器负载,缓存都扮演着重要角色。[ruoyi-eggjs-cache](https://github.com/undsky/ruoyi-eggjs-cache) 就是一个为 Egg.js 量身定制的缓存插件,基于强大的 cache-manager 生态,提供了开箱即用的缓存能力。

核心特性

🎯 三种存储方式,按需选择

插件支持三种缓存存储方式,每种都有其独特的使用场景:

1. 内存缓存(Memory)

2. 文件系统缓存(FS)

3. Redis 缓存(推荐)

🔧 统一的 API,简单易用

插件提供了四个核心方法,覆盖所有缓存操作场景:


    
    
    
  // 1. set - 设置缓存
await
 app.cache.default.set('key', 'value', 300);  // 缓存 5 分钟

// 2. get - 获取缓存

const
 value = await app.cache.default.get('key');

// 3. del - 删除缓存

await
 app.cache.default.del('key');

// 4. wrap - 缓存包装器(最常用)

const
 result = await app.cache.default.wrap('key', async () => {
  return
 await fetchData();  // 仅在缓存不存在时执行
});

其中 wrap 方法是最实用的——它会自动判断缓存是否存在,存在则直接返回,不存在则执行回调函数并缓存结果。

快速上手

安装


    
    
    
  npm i ruoyi-eggjs-cache --save

配置

1. 启用插件


    
    
    
  // config/plugin.js
exports
.cache = {
  enable
: true,
  package
: "ruoyi-eggjs-cache",
};

2. 基础配置(内存缓存)


    
    
    
  // config/config.default.js
const
 path = require('path');

config.cache = {
  default
: 'memory',  // 默认使用内存缓存
  ttl
: 600,           // 缓存 10 分钟
  fs
: {
    path
: path.join(appInfo.baseDir, 'cache'),
    subdirs
: false,
    zip
: false,
  },
  redis
: null,
};

3. Redis 配置(推荐生产环境)


    
    
    
  // config/config.prod.js
config.cache = {
  default
: 'redis',
  ttl
: 600,
  redis
: {
    host
: '127.0.0.1',
    port
: 6379,
    password
: '',
    db
: 0,
  },
};

实战场景

场景 1:数据库查询缓存

数据库查询是最常见的性能瓶颈,使用缓存可以显著提升响应速度:


    
    
    
  // app/service/user.js
class
 UserService extends Service {
  async
 getUserById(userId) {
    const
 { app } = this;
    
    // 使用 wrap 自动管理缓存

    return
 await app.cache.default.wrap(`user:${userId}`, async () => {
      return
 await app.mysql.select('SELECT * FROM users WHERE id = ?', [userId]);
    });
  }

  async
 updateUser(user) {
    const
 { app } = this;
    
    // 更新数据库

    await
 app.mysql.update('UPDATE users SET name = ? WHERE id = ?', [user.name, user.id]);
    
    // 删除缓存,确保下次获取最新数据

    await
 app.cache.default.del(`user:${user.id}`);
  }
}

关键点

场景 2:API 响应缓存

对于查询频繁但变化不大的列表接口,缓存可以大幅降低服务器负载:


    
    
    
  // app/controller/article.js
class
 ArticleController extends Controller {
  async
 list() {
    const
 { ctx, app } = this;
    const
 params = ctx.request.body;
    
    // 根据查询参数生成唯一的缓存 key

    const
 cacheKey = `article:list:${JSON.stringify(params)}`;
    
    const
 articles = await app.cache.default.wrap(cacheKey, async () => {
      return
 await ctx.service.article.getList(params);
    }, { ttl: 60 });  // 列表缓存 1 分钟
    
    ctx.body = { code: 200, data: articles };
  }
}

场景 3:配置数据缓存

系统配置数据变化频率极低,非常适合长时间缓存:


    
    
    
  // app/service/config.js
class
 ConfigService extends Service {
  async
 getConfigByKey(configKey) {
    const
 { app } = this;
    
    return
 await app.cache.default.wrap(`config:${configKey}`, async () => {
      return
 await app.mysql.select('SELECT * FROM sys_config WHERE config_key = ?', [configKey]);
    }, { ttl: 3600 });  // 缓存 1 小时
  }

  async
 updateConfig(config) {
    const
 { app } = this;
    
    await
 app.mysql.update('UPDATE sys_config SET config_value = ? WHERE config_key = ?'
      [config.configValue, config.configKey]);
    
    // 清除配置缓存

    await
 app.cache.default.del(`config:${config.configKey}`);
  }
}

场景 4:不同存储方式的组合使用

针对不同的数据特点,选择最合适的缓存方式:


    
    
    
  // app/service/data.js
class
 DataService extends Service {
  // 高频访问的小数据 → 内存缓存

  async
 getHotData() {
    return
 await this.app.cache.memory.wrap('hot:data', async () => {
      return
 await this.fetchHotData();
    });
  }

  // 体积大的数据 → 文件系统缓存

  async
 getBigData() {
    return
 await this.app.cache.fs.wrap('big:data', async () => {
      return
 await this.fetchBigData();
    }, { ttl: 1800 });
  }

  // 多实例共享数据 → Redis 缓存

  async
 getSharedData() {
    return
 await this.app.cache.redis.wrap('shared:data', async () => {
      return
 await this.fetchSharedData();
    }, { ttl: 600 });
  }
}

缓存策略最佳实践

1. 合理设计缓存 Key

好的 Key 命名能让缓存管理更清晰:


    
    
    
  // ✅ 推荐:使用有意义的前缀和参数
`user:${userId}`

`article:list:${category}:${page}`

`config:${configKey}`


// ❌ 不推荐:无意义的 Key

`u123`

`data`

2. 根据数据特点设置 TTL


    
    
    
  // 频繁变化的数据 - 短 TTL
await
 app.cache.default.wrap('stock:price', async () => {
  return
 await fetchStockPrice();
}, { ttl: 60 });  // 1 分钟

// 相对稳定的数据 - 中 TTL

await
 app.cache.default.wrap('user:info', async () => {
  return
 await fetchUserInfo();
}, { ttl: 600 });  // 10 分钟

// 几乎不变的数据 - 长 TTL

await
 app.cache.default.wrap('system:config', async () => {
  return
 await fetchSystemConfig();
}, { ttl: 3600 });  // 1 小时

3. 避免缓存雪崩

缓存雪崩是指大量缓存同时过期,导致瞬间的数据库压力激增。解决方法是给 TTL 加上随机值:


    
    
    
  // 在基础 TTL 上增加随机时间
const
 ttl = 300 + Math.floor(Math.random() * 60);  // 300-360 秒
await
 app.cache.default.set('key', 'value', ttl);

4. 及时清除过期缓存

在数据变更(增删改)时,记得清除相关缓存:


    
    
    
  // 更新后清除单条缓存
await
 app.cache.default.del(`user:${userId}`);

// 删除多条缓存

await
 app.cache.default.del([`user:${userId}`, `user:list`]);

// 清空所有缓存(慎用)

await
 app.cache.default.reset();

性能优化建议

  1. 1. 选择合适的缓存方式
    • • 开发/测试:Memory
    • • 生产单机:FS 或 Redis
    • • 生产集群:Redis
  2. 2. 合理设置 TTL
    • • 避免设置过长导致数据不一致
    • • 避免设置过短失去缓存意义
  3. 3. 缓存粒度控制
    • • 细粒度:单条数据缓存(如 user:123
    • • 粗粒度:列表缓存(如 user:list
    • • 根据业务场景选择
  4. 4. 监控缓存命中率
    • • 定期检查缓存使用情况
    • • 优化低命中率的缓存策略

总结

ruoyi-eggjs-cache 是一个轻量且强大的 Egg.js 缓存插件,它的优势在于:

无论是初创项目还是大型应用,合理使用缓存都能带来显著的性能提升。如果你正在使用 Egg.js,不妨试试这个插件,让你的应用飞起来!


引用链接

[1] ruoyi-eggjs-cache: https://github.com/undsky/ruoyi-eggjs-cache
[2] RuoYi-Eggjs: https://github.com/undsky/ruoyi-eggjs
[3] RuoYi-Eggjs 文档: https://www.undsky.com/blog/?category=RuoYi-Eggjs#