从零入门 NestJS:构建你的第一个 REST API(十一)缓存机制与优化

225 阅读4分钟

缓存机制与优化

在高并发场景下,合理使用缓存可以极大提升系统性能、降低数据库压力、优化用户体验。NestJS 提供了灵活的缓存机制,支持内存缓存和 Redis 等分布式缓存方案。

缓存的作用与常见方案

  • 作用:减少数据库/第三方接口访问频率,加速热点数据响应,提升系统吞吐量。
  • 常见方案
    • 本地内存缓存(适合单机、数据量小场景)
    • Redis 分布式缓存(适合多节点、数据共享、持久化需求)

NestJS 集成缓存模块

NestJS 提供了 @nestjs/cache-manager 官方模块,支持多种缓存后端(如 memory、redis)。

1. 安装依赖

npm install --save cache-manager @nestjs/cache-manager cache-manager-redis-store redis

2. 配置缓存模块

// app.module.ts
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import * as redisStore from 'cache-manager-redis-store';

@Module({
  imports: [
    CacheModule.register({
      isGlobal: true, // 全局可用
      store: redisStore,
      host: 'localhost',
      port: 6379,
      ttl: 60, // 默认缓存 60 秒
    }),
  ],
})
export class AppModule {}

缓存装饰器与手动缓存

1. 使用 @Cacheable 装饰器自动缓存接口

// user.controller.ts
import { Controller, Get, CacheKey, CacheTTL, CacheInterceptor, UseInterceptors } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
@UseInterceptors(CacheInterceptor) // 启用缓存拦截器
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get('hot')
  @CacheKey('hot_users') // 自定义缓存 key
  @CacheTTL(120)         // 缓存 120 秒
  async getHotUsers() {
    return this.userService.findHotUsers();
  }
}

2. 手动读写缓存

// user.service.ts
import { Injectable, Inject } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';

@Injectable()
export class UserService {
  constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}

  async getUserProfile(userId: string) {
    // 先查缓存
    const cacheKey = `user_profile_${userId}`;
    let profile = await this.cacheManager.get(cacheKey);
    if (!profile) {
      // 缓存未命中,查数据库
      profile = await this.findUserById(userId);
      await this.cacheManager.set(cacheKey, profile, 300); // 缓存5分钟
    }
    return profile;
  }
}

典型实战场景

  • 接口数据缓存:如首页推荐、排行榜、热点新闻等高频只读接口。
  • 热点数据优化:如用户信息、商品详情等,减少数据库压力。
  • 分布式会话/Token 缓存:如单点登录、验证码等。
  • 防止缓存击穿/雪崩:可用互斥锁、预热、过期分散等手段优化。

最佳实践与常见坑点

  • 缓存 key 要唯一且有业务含义,避免覆盖和冲突。
  • 缓存时间(TTL)要合理,过短失去意义,过长易数据不一致。
  • 缓存失效要有降级方案,如回源数据库。
  • 分布式部署建议用 Redis,保证多节点数据一致。
  • 敏感数据不建议缓存,防止泄露。
  • 缓存穿透/击穿/雪崩要防护,可用互斥锁、预热、限流等手段。
  • 定期清理无用缓存,防止内存/Redis 膨胀。

缓存淘汰与清理策略

缓存空间有限,必须有合理的淘汰策略来保证热点数据常驻、冷数据及时清理。常见策略有:

  • LRU(最近最少使用):优先淘汰最近最少被访问的数据,适合大多数通用场景。
  • LFU(最不经常使用):优先淘汰访问频率最低的数据,适合热点数据极少变动的场景。
  • TTL(过期时间):每条缓存设置生存时间,到期自动清除,适合数据时效性强的场景。
  • 手动清理:业务代码中主动删除或刷新缓存,适合数据变更频繁、需强一致性的场景。

1. Redis 缓存淘汰策略配置

Redis 支持多种淘汰策略,可在 redis.conf 或启动参数中配置:

  • volatile-lru:对设置了过期时间的 key 使用 LRU
  • allkeys-lru:对所有 key 使用 LRU
  • volatile-ttl:对设置了过期时间的 key 使用 TTL
  • allkeys-random:对所有 key 随机淘汰

示例:

maxmemory 256mb
maxmemory-policy allkeys-lru

2. cache-manager 内存缓存策略

cache-manager 的内存存储默认采用 LRU 策略,自动清理最久未使用的数据。

import { CacheModule } from '@nestjs/cache-manager';

CacheModule.register({
  max: 100, // 最大缓存条数,超出后自动淘汰最久未用数据
  ttl: 60, // 每条缓存默认 60 秒
})

3. 手动清除缓存

// 清除指定 key
await this.cacheManager.del('user_profile_123');

// 清除所有缓存(慎用,可能影响性能)
await this.cacheManager.reset();

4. 结合 TTL + LRU 提升效果

  • 推荐为热点数据设置合理 TTL,结合 LRU 策略,既保证数据新鲜度,又防止缓存膨胀。
  • 对于极易变动的数据,建议主动删除或更新缓存,避免脏读。

选择合适的淘汰策略需结合业务场景、数据访问模式和系统资源,建议多查阅 Redis 淘汰策略文档cache-manager 文档

缓存是提升系统性能的利器,建议多查阅 NestJS 缓存文档cache-manager 文档 掌握更多高级用法。