当前位置: 首页 > news >正文

【NestJS】在 nest.js 项目中,如何使用 Postgresql 来做缓存?

在 NestJS 项目中使用 PostgreSQL 作为缓存存储是一个可行的方案,尽管它通常不如 Redis 等专门的内存缓存系统高效。但是,如果你已经在使用 PostgreSQL 并且希望避免引入额外的服务(如 Redis),或者你的缓存需求量不大、对延迟不那么敏感,那么这确实是一个不错的选择。

本指南将详细介绍如何在 NestJS 中使用 TypeORM 和 PostgreSQL 来实现一个基本的缓存机制。


重要提示 (Disclaimer)

  • 性能考量: PostgreSQL 是一个关系型数据库,主要设计用于持久化存储和复杂查询。与 Redis 这种内存数据库相比,它在处理大量高并发的读写操作时通常会慢得多,因为它涉及磁盘 I/O、事务管理等开销。
  • 适用场景: 这种方案更适合:
    • 对缓存性能要求不那么极致的场景。
    • 缓存数据量相对较小或更新不频繁的场景。
    • 希望简化部署,避免引入新服务的场景。
    • 已经深度依赖 PostgreSQL 的项目。
  • 功能限制: Redis 提供了更多高级的缓存功能,如原子操作、发布/订阅、各种数据结构(列表、集合、哈希等)。PostgreSQL 只能模拟简单的键值对缓存。

步骤概览

  1. 数据库Schema设计: 创建一个 cache 表来存储键值对和过期时间。
  2. NestJS Entity定义:cache 表创建 TypeORM Entity。
  3. Cache Service实现: 创建一个服务来封装缓存的 getsetdelreset 逻辑。
  4. Cache Module整合: 将 Entity 和 Service 整合到 NestJS 模块中。
  5. AppModule集成: 在根模块中导入和配置缓存模块。
  6. 使用示例: 在应用中使用缓存服务。

详细步骤

前提条件
  • 一个 NestJS 项目。
  • 已安装并配置 TypeORM 和 PostgreSQL 驱动:
    npm install @nestjs/typeorm typeorm pg
    # 或者 yarn add @nestjs/typeorm typeorm pg
    
  • 确保你的 AppModule 中已经配置了 TypeORM 的数据库连接。
1. 数据库Schema设计

在你的 PostgreSQL 数据库中创建一个 application_cache 表(或你喜欢的任何名称):

CREATE TABLE IF NOT EXISTS application_cache (key VARCHAR(255) PRIMARY KEY, -- 缓存键,建议加上索引以提高查询速度value JSONB NOT NULL,         -- 缓存值,使用 JSONB 存储任意类型的数据expires_at TIMESTAMP WITH TIME ZONE, -- 缓存过期时间created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);-- 为 expires_at 列添加索引,以便更高效地清理过期数据
CREATE INDEX IF NOT EXISTS idx_application_cache_expires_at ON application_cache (expires_at);

解释:

  • key: 缓存的唯一标识符,设为主键,确保快速查找。
  • value: 使用 JSONB 类型可以存储任何 JSON 兼容的数据结构(对象、数组、字符串、数字等),这是最佳实践。
  • expires_at: 存储缓存的过期时间。如果为 NULL,表示永不过期。
  • created_at, updated_at: 审计字段。
2. NestJS Entity定义

创建 src/cache/cache.entity.ts 文件:

// src/cache/cache.entity.ts
import { Entity, PrimaryColumn, Column } from 'typeorm';@Entity('application_cache') // 对应数据库表名
export class CacheEntity {@PrimaryColumn({ type: 'varchar', length: 255 })key: string;@Column({ type: 'jsonb', nullable: false })value: any; // 使用 any 来存储 JSONB 类型的数据,TypeORM 会自动处理@Column({ type: 'timestamp with time zone', name: 'expires_at', nullable: true })expiresAt: Date | null;@Column({ type: 'timestamp with time zone', name: 'created_at', default: () => 'CURRENT_TIMESTAMP' })createdAt: Date;@Column({ type: 'timestamp with time zone', name: 'updated_at', default: () => 'CURRENT_TIMESTAMP', onUpdate: 'CURRENT_TIMESTAMP' })updatedAt: Date;
}
3. Cache Service实现

创建 src/cache/cache.service.ts 文件:

// src/cache/cache.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { LessThan, Repository, IsNull } from 'typeorm'; // 导入 LessThan 和 IsNull
import { CacheEntity } from './cache.entity';@Injectable()
export class CacheService {private readonly logger = new Logger(CacheService.name);constructor(@InjectRepository(CacheEntity)private cacheRepository: Repository<CacheEntity>,) {// 定期清理过期缓存,例如每小时运行一次setInterval(() => this.cleanupExpiredCache(), 60 * 60 * 1000);}/*** 获取缓存值* @param key 缓存键* @returns 缓存值或 null*/async get<T>(key: string): Promise<T | null> {const cacheEntry = await this.cacheRepository.findOne({ where: { key } });if (!cacheEntry) {return null;}// 检查缓存是否过期if (cacheEntry.expiresAt && cacheEntry.expiresAt < new Date()) {this.logger.debug(`Cache entry for key "${key}" expired. Deleting.`);await this.del(key); // 自动删除过期缓存return null;}return cacheEntry.value as T;}/*** 设置缓存值* @param key 缓存键* @param value 缓存值* @param ttlSeconds 缓存存活时间 (秒)。如果为 undefined 或 0,则永不过期。*/async set(key: string, value: any, ttlSeconds?: number): Promise<void> {const expiresAt = ttlSeconds? new Date(Date.now() + ttlSeconds * 1000): null;// 使用 upsert 确保原子性:如果键存在则更新,不存在则插入await this.cacheRepository.upsert({ key, value, expiresAt },['key'] // 冲突解决策略:如果 key 冲突,则更新);}/*** 删除缓存* @param key 缓存键*/async del(key: string): Promise<void> {await this.cacheRepository.delete({ key });}/*** 清空所有缓存 (谨慎使用!)*/async reset(): Promise<void> {this.logger.warn('Clearing ALL cache entries!');await this.cacheRepository.clear();}/*** 后台任务:清理所有已过期的缓存条目*/private async cleanupExpiredCache(): Promise<void> {this.logger.debug('Starting cleanup of expired cache entries...');try {// 删除 expiresAt 字段存在且小于当前时间的记录const deleteResult = await this.cacheRepository.delete({expiresAt: LessThan(new Date()),});this.logger.debug(`Cleaned up ${deleteResult.affected || 0} expired cache entries.`);} catch (error) {this.logger.error('Error during cache cleanup:', error.stack);}}
}

解释:

  • get<T>(key): 查询数据库获取缓存,并手动检查 expiresAt。如果过期,则删除并返回 null
  • set(key, value, ttlSeconds): 计算过期时间,然后使用 TypeORM 的 upsert 方法(如果 TypeORM 版本支持)或 save + findBy 逻辑来插入或更新缓存。upsert 是更好的选择,因为它在数据库层面处理了冲突,保证了原子性。
  • del(key): 根据键删除缓存。
  • reset(): 清空整个缓存表。在生产环境中需谨慎使用。
  • cleanupExpiredCache(): 一个私有方法,通过 setInterval 定期运行,负责从数据库中物理删除所有过期的缓存条目。这对于维护数据库大小和性能很重要。
4. Cache Module整合

创建 src/cache/cache.module.ts 文件:

// src/cache/cache.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CacheService } from './cache.service';
import { CacheEntity } from './cache.entity';@Module({imports: [TypeOrmModule.forFeature([CacheEntity])], // 注册 CacheEntityproviders: [CacheService],exports: [CacheService], // 导出 CacheService,以便其他模块可以使用
})
export class CacheModule {}
5. AppModule集成

在你的 src/app.module.ts 中导入并注册 CacheModule,并确保 TypeOrmModule.forRoot 中包含了 CacheEntity

// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CacheModule } from './cache/cache.module';
import { CacheEntity } from './cache/cache.entity'; // 引入 CacheEntity@Module({imports: [TypeOrmModule.forRoot({type: 'postgres',host: 'localhost',port: 5432,username: 'your_user',password: 'your_password',database: 'your_database',entities: [CacheEntity], // *** 确保这里包含了 CacheEntity ***synchronize: true, // 开发环境使用,生产环境请使用 Migrationslogging: false,}),CacheModule, // 导入你的 CacheModule],controllers: [AppController],providers: [AppService],
})
export class AppModule {}

注意:

  • entities: [CacheEntity] 是非常重要的,它告诉 TypeORM 管理这个实体。
  • synchronize: true 在开发环境下很方便,它会自动创建或更新数据库Schema。但在生产环境中,强烈建议使用 TypeORM 的 Migrations。
6. 使用示例

现在,你可以在你的任何服务或控制器中注入并使用 CacheService 了。

// src/app.service.ts
import { Injectable } from '@nestjs/common';
import { CacheService } from './cache/cache.service';@Injectable()
export class AppService {constructor(private readonly cacheService: CacheService) {}async getHello(): Promise<string> {const cacheKey = 'hello_greeting';let greeting = await this.cacheService.get<string>(cacheKey);if (greeting) {console.log('Fetching greeting from cache.');return `Hello from cache: ${greeting}`;}console.log('Fetching greeting from slow source...');// 模拟一个耗时操作await new Promise(resolve => setTimeout(resolve, 2000));greeting = 'World! (from slow source)';// 将结果缓存 60 秒await this.cacheService.set(cacheKey, greeting, 60);return `Hello freshly: ${greeting}`;}async getUserData(userId: number): Promise<any> {const cacheKey = `user_${userId}_data`;let userData = await this.cacheService.get<any>(cacheKey);if (userData) {console.log(`User ${userId} data from cache.`);return userData;}console.log(`Fetching user ${userId} data from DB...`);// 模拟从数据库获取用户数据await new Promise(resolve => setTimeout(resolve, 1500));userData = { id: userId, name: `User ${userId}`, email: `user${userId}@example.com` };// 缓存用户数据 5 分钟 (300秒)await this.cacheService.set(cacheKey, userData, 300);return userData;}
}

进一步的优化和考虑

  1. 索引: 确保 key 列有主键或唯一索引,expires_at 列有普通索引,以加速查找和过期清理。
  2. 错误处理:CacheService 中添加更健壮的 try/catch 块来处理数据库操作可能出现的错误。
  3. 连接池: TypeORM 会自动管理数据库连接池,但在高并发场景下,确保你的数据库和 TypeORM 配置有足够的连接数。
  4. 序列化/反序列化: JSONB 列在 TypeORM 中会自动处理 JSON 对象的序列化和反序列化。如果存储的是简单字符串或数字,它也会将其转换为 JSON 兼容格式。
  5. 性能监控: 监控 PostgreSQL 的 CPU、内存、I/O 使用情况,以及缓存表的读写延迟。如果缓存成为瓶颈,则需要重新考虑引入专门的缓存系统(如 Redis)。
  6. 高级缓存策略:
    • “Cache-Aside”(旁路缓存)模式: 这是我们目前实现的模式,应用程序代码负责检查缓存,如果未命中则从原始数据源获取,然后更新缓存。
    • “Write-Through”(直写)/ “Write-Back”(回写)模式: 通常需要更复杂的抽象层,可能不太适合直接在关系型数据库上实现。
  7. 分布式缓存: 如果你的 NestJS 应用是多实例部署的,那么所有实例都会使用同一个 PostgreSQL 缓存表,这天然支持了分布式缓存。而 Redis 也同样支持。

通过上述步骤,你就可以在 NestJS 项目中成功地使用 PostgreSQL 作为缓存存储了。再次强调,请根据你的实际需求和性能预算来选择最合适的缓存方案。

http://www.dtcms.com/a/536201.html

相关文章:

  • 解决由于没有远程桌面授权服务器可以提供许可证,远程会话被中断.的方法
  • 初始化服务器
  • 玉林建设信息网站帮别做网站
  • 【C++ 内存管理、模板初阶与 STL 简介】:打通高效编程的关键链路
  • web开发,在线%高校舆情分析%系统demo,基于python,flaskweb,echart,nlp,ida,tf-idf,多爬虫源,数据库mysql
  • 安装双系统
  • AI研究-113 DeepSeek-OCR 原理与架构全解|视觉压缩长文本 SAM-base 16×下采样 CLIP-L 3B-MoE
  • R语言绘制复杂加权数据(nhanes数据)生存分析决策曲线
  • 常州溧阳建设工程管理中心网站做网站平台的公司
  • 政务领域应用:国密 SSL 证书如何守护 “一网通办” 的数据安全?
  • LM实现教程:基于 nanochat项目 从零开始理解大语言模型
  • 【南京大学主办】第三届数学与机器学习国际学术会议(ICMML 2025)
  • 淮北市建设局网站福建省住房和城乡建设局网站
  • 无锡企业网站龙岩kk网最新招聘
  • 告别纸张,迎接更加高效的文档管理——Paperless-ngx介绍
  • 题解:P14309 【MX-S8-T2】配对
  • SQL之表的增删
  • 【计算机网络核心】TCP/IP模型与网页解析全流程详解
  • HTML 理论系统笔记2
  • 微软Copilot被用于窃取OAuth令牌,AI Agent成为攻击者帮凶
  • 免费网站建站w海口企业自助建站
  • 全球 PyTorch 大会与 Triton 大会释放强信号:算子语言繁荣和分化背后,编译器核心地位日益凸显
  • PyCharm 快速运行 django project
  • 自己动手制作网站外呼电销系统
  • 网站建设出错1004有专门下载地图做方案的网站吗
  • OpenCV C++ 中,访问图像像素三种常用方法
  • MATLAB基于小波云模型时间序列预测,以年径流预测为例
  • 项目名称:烟酒进销存管理系统
  • web开发,在线%蛋糕销售%管理系统,基于asp.net,webform,c#,sql server
  • UE5 蓝图-25:主 mainUI界面蓝图,右侧的颜色按钮的实现,换色逻辑与材质参数集,