요청에 응답하는 속도와 효율성은 대규모 및 트래픽이 많은 시스템에서 가장 중요합니다. 전자상거래 웹사이트, 소셜 네트워크, 은행 서비스와 같은 온라인 플랫폼은 엄청난 양의 데이터와 사용자 요청에 직면해 있습니다. 이러한 높은 수요는 서버와 데이터베이스에 상당한 부하를 줄 뿐만 아니라 사용자 경험에도 큰 영향을 미칠 수 있습니다. 이러한 맥락에서 캐싱 시스템을 구현하는 것은 성능을 향상하고 리소스 부하를 줄이는 효과적인 솔루션이 될 수 있습니다.
이 기사에서는 AVL 트리와 Redis를 결합한 고급 캐싱 시스템의 구현을 살펴봅니다. 이 시스템에는 보안 메커니즘, TTL(Time to Live) 관리, Redis와의 통합이 포함되어 성능과 유연성을 향상시킵니다. 목표는 두 기술의 장점을 활용하면서 약점을 완화하는 것입니다.
중요사항: 이 글은 인공지능의 도움을 받아 작성되었습니다.
향상된 메모리 효율성:
보안 강화:
고급 TTL 관리:
다양한 데이터 구조:
향상된 유연성 및 사용자 정의:
건축 복잡성 증가:
오버헤드 시간 증가:
데이터 유지 관리 및 동기화:
높은 개발 및 유지 관리 비용:
보안 복잡성:
아래에서는 이 캐싱 시스템의 전문적인 구현을 소개합니다. 이 구현에는 TTL 기능으로 데이터를 관리하기 위한 AVL 트리와 빠른 데이터 저장을 위한 Redis가 포함됩니다.
먼저 TTL 관리 기능을 갖춘 AVL 트리를 구현합니다.
// src/utils/avltree.ts export class AVLNode { key: string; value: any; ttl: number; // Expiration time in milliseconds height: number; left: AVLNode | null; right: AVLNode | null; constructor(key: string, value: any, ttl: number) { this.key = key; this.value = value; this.ttl = Date.now() + ttl; this.height = 1; this.left = null; this.right = null; } isExpired(): boolean { return Date.now() > this.ttl; } } export class AVLTree { private root: AVLNode | null; constructor() { this.root = null; } private getHeight(node: AVLNode | null): number { return node ? node.height : 0; } private updateHeight(node: AVLNode): void { node.height = 1 + Math.max(this.getHeight(node.left), this.getHeight(node.right)); } private rotateRight(y: AVLNode): AVLNode { const x = y.left!; y.left = x.right; x.right = y; this.updateHeight(y); this.updateHeight(x); return x; } private rotateLeft(x: AVLNode): AVLNode { const y = x.right!; x.right = y.left; y.left = x; this.updateHeight(x); this.updateHeight(y); return y; } private getBalance(node: AVLNode): number { return node ? this.getHeight(node.left) - this.getHeight(node.right) : 0; } insert(key: string, value: any, ttl: number): void { this.root = this.insertNode(this.root, key, value, ttl); } private insertNode(node: AVLNode | null, key: string, value: any, ttl: number): AVLNode { if (!node) return new AVLNode(key, value, ttl); if (key < node.key) { node.left = this.insertNode(node.left, key, value, ttl); } else if (key > node.key) { node.right = this.insertNode(node.right, key, value, ttl); } else { node.value = value; node.ttl = Date.now() + ttl; return node; } this.updateHeight(node); const balance = this.getBalance(node); // Balancing the tree if (balance > 1 && key < node.left!.key) return this.rotateRight(node); if (balance < -1 && key > node.right!.key) return this.rotateLeft(node); if (balance > 1 && key > node.left!.key) { node.left = this.rotateLeft(node.left!); return this.rotateRight(node); } if (balance < -1 && key < node.right!.key) { node.right = this.rotateRight(node.right!); return this.rotateLeft(node); } return node; } search(key: string): any { let node = this.root; while (node) { if (node.isExpired()) { this.delete(key); return null; } if (key === node.key) return node.value; node = key < node.key ? node.left : node.right; } return null; } delete(key: string): void { this.root = this.deleteNode(this.root, key); } private deleteNode(node: AVLNode | null, key: string): AVLNode | null { if (!node) return null; if (key < node.key) { node.left = this.deleteNode(node.left, key); } else if (key > node.key) { node.right = this.deleteNode(node.right, key); } else { if (!node.left || !node.right) return node.left || node.right; let minLargerNode = node.right; while (minLargerNode.left) minLargerNode = minLargerNode.left; node.key = minLargerNode.key; node.value = minLargerNode.value; node.ttl = minLargerNode.ttl; node.right = this.deleteNode(node.right, minLargerNode.key); } this.updateHeight(node); const balance = this.getBalance(node); if (balance > 1 && this.getBalance(node.left!) >= 0) return this.rotateRight(node); if (balance < -1 && this.getBalance(node.right!) <= 0) return this.rotateLeft(node); if (balance > 1 && this.getBalance(node.left!) < 0) { node.left = this.rotateLeft(node.left!); return this.rotateRight(node); } if (balance < -1 && this.getBalance(node.right!) > 0) { node.right = this.rotateRight(node.right!); return this.rotateLeft(node); } return node; } }
이 섹션에서는 캐시 관리를 위해 AVL 트리와 Redis를 모두 활용하는 캐시 서비스를 구현합니다. 또한 토큰 검증 메커니즘을 통합합니다.
// src/cache/cache.service.ts import { Injectable, UnauthorizedException, InternalServerErrorException } from '@nestjs/common'; import { AVLTree } from '../utils/avltree'; import { InjectRedis, Redis } from '@nestjs-modules/ioredis'; @Injectable() export class CacheService { private avlTree: AVLTree; private authorizedTokens: Set<string> = new Set(['your_authorized_token']); // Authorized tokens constructor(@InjectRedis() private readonly redis: Redis) { this.avlTree = new AVLTree(); } validateToken(token: string): void { if (!this.authorizedTokens.has(token)) { throw new UnauthorizedException('Invalid access token'); } } async set(key: string, value: any, ttl: number, token: string): Promise<void> { this.validateToken(token); try { // Store in Redis await this.redis.set(key, JSON.stringify(value), 'PX', ttl); // Store in AVL Tree this.avlTree.insert(key, value, ttl); } catch (error) { throw new InternalServerErrorException('Failed to set cache'); } } async get(key: string, token: string): Promise<any> { this.validateToken(token); try { // First, attempt to retrieve from Redis const redisValue = await this.redis.get(key); if (redisValue) { return JSON.parse(redisValue); } // If not found in Redis, retrieve from AVL Tree const avlValue = this.avlTree.search(key); if (avlValue) { // Re-store in Redis for faster access next time // Assuming the remaining TTL is maintained in AVL Tree // For simplicity, we set a new TTL const newTtl = 60000; // 60 seconds as an example await this.redis.set(key, JSON.stringify(avlValue), 'PX', newTtl); return avlValue; } return null; } catch (error) { throw new InternalServerErrorException('Failed to get cache'); } } async delete(key: string, token: string): Promise<void> { this.validateToken(token); try { // Remove from Redis await this.redis.del(key); // Remove from AVL Tree this.avlTree.delete(key); } catch (error) { throw new InternalServerErrorException('Failed to delete cache'); } } }
컨트롤러는 캐시 서비스에 대한 API 요청을 관리합니다.
// src/cache/cache.controller.ts import { Controller, Get, Post, Delete, Body, Param, Query, HttpCode, HttpStatus } from '@nestjs/common'; import { CacheService } from './cache.service'; class SetCacheDto { key: string; value: any; ttl: number; // milliseconds token: string; } @Controller('cache') export class CacheController { constructor(private readonly cacheService: CacheService) {} @Post('set') @HttpCode(HttpStatus.CREATED) async setCache(@Body() body: SetCacheDto) { await this.cacheService.set(body.key, body.value, body.ttl, body.token); return { message: 'Data cached successfully' }; } @Get('get/:key') async getCache(@Param('key') key: string, @Query('token') token: string) { const value = await this.cacheService.get(key, token); return value ? { value } : { message: 'Key not found or expired' }; } @Delete('delete/:key') @HttpCode(HttpStatus.NO_CONTENT) async deleteCache(@Param('key') key: string, @Query('token') token: string) { await this.cacheService.delete(key, token); return { message: 'Key deleted successfully' }; } }
서비스와 컨트롤러를 연결하고 Redis를 주입하는 캐시 모듈을 정의합니다.
// src/cache/cache.module.ts import { Module } from '@nestjs/common'; import { CacheService } from './cache.service'; import { CacheController } from './cache.controller'; import { RedisModule } from '@nestjs-modules/ioredis'; @Module({ imports: [ RedisModule.forRoot({ config: { host: 'localhost', port: 6379, // Other Redis configurations }, }), ], providers: [CacheService], controllers: [CacheController], }) export class CacheModule {}
NestJS 프로젝트에서 Redis를 사용하기 위해 @nestjs-modules/ioredis 패키지를 활용합니다. 먼저 패키지를 설치하세요.
npm install @nestjs-modules/ioredis ioredis
그런 다음 위와 같이 CacheModule에서 Redis를 구성합니다. 더 고급 구성이 필요한 경우 별도의 구성 파일을 사용할 수 있습니다.
토큰을 관리하고 검증하기 위해 다양한 전략을 사용할 수 있습니다. 이 간단한 구현에서는 토큰이 고정된 세트로 유지됩니다. 대규모 프로젝트의 경우 JWT(JSON 웹 토큰) 또는 기타 고급 보안 방법을 사용하는 것이 좋습니다.
이 구현에서는 입력 유효성 검사 및 오류 관리에 DTO(Data Transfer Object) 클래스가 사용됩니다. 또한 캐시 서비스는 일반적인 오류 처리를 활용하여 예상치 못한 문제를 방지합니다.
마지막으로 기본 애플리케이션 모듈에 캐시 모듈을 추가합니다.
// src/utils/avltree.ts export class AVLNode { key: string; value: any; ttl: number; // Expiration time in milliseconds height: number; left: AVLNode | null; right: AVLNode | null; constructor(key: string, value: any, ttl: number) { this.key = key; this.value = value; this.ttl = Date.now() + ttl; this.height = 1; this.left = null; this.right = null; } isExpired(): boolean { return Date.now() > this.ttl; } } export class AVLTree { private root: AVLNode | null; constructor() { this.root = null; } private getHeight(node: AVLNode | null): number { return node ? node.height : 0; } private updateHeight(node: AVLNode): void { node.height = 1 + Math.max(this.getHeight(node.left), this.getHeight(node.right)); } private rotateRight(y: AVLNode): AVLNode { const x = y.left!; y.left = x.right; x.right = y; this.updateHeight(y); this.updateHeight(x); return x; } private rotateLeft(x: AVLNode): AVLNode { const y = x.right!; x.right = y.left; y.left = x; this.updateHeight(x); this.updateHeight(y); return y; } private getBalance(node: AVLNode): number { return node ? this.getHeight(node.left) - this.getHeight(node.right) : 0; } insert(key: string, value: any, ttl: number): void { this.root = this.insertNode(this.root, key, value, ttl); } private insertNode(node: AVLNode | null, key: string, value: any, ttl: number): AVLNode { if (!node) return new AVLNode(key, value, ttl); if (key < node.key) { node.left = this.insertNode(node.left, key, value, ttl); } else if (key > node.key) { node.right = this.insertNode(node.right, key, value, ttl); } else { node.value = value; node.ttl = Date.now() + ttl; return node; } this.updateHeight(node); const balance = this.getBalance(node); // Balancing the tree if (balance > 1 && key < node.left!.key) return this.rotateRight(node); if (balance < -1 && key > node.right!.key) return this.rotateLeft(node); if (balance > 1 && key > node.left!.key) { node.left = this.rotateLeft(node.left!); return this.rotateRight(node); } if (balance < -1 && key < node.right!.key) { node.right = this.rotateRight(node.right!); return this.rotateLeft(node); } return node; } search(key: string): any { let node = this.root; while (node) { if (node.isExpired()) { this.delete(key); return null; } if (key === node.key) return node.value; node = key < node.key ? node.left : node.right; } return null; } delete(key: string): void { this.root = this.deleteNode(this.root, key); } private deleteNode(node: AVLNode | null, key: string): AVLNode | null { if (!node) return null; if (key < node.key) { node.left = this.deleteNode(node.left, key); } else if (key > node.key) { node.right = this.deleteNode(node.right, key); } else { if (!node.left || !node.right) return node.left || node.right; let minLargerNode = node.right; while (minLargerNode.left) minLargerNode = minLargerNode.left; node.key = minLargerNode.key; node.value = minLargerNode.value; node.ttl = minLargerNode.ttl; node.right = this.deleteNode(node.right, minLargerNode.key); } this.updateHeight(node); const balance = this.getBalance(node); if (balance > 1 && this.getBalance(node.left!) >= 0) return this.rotateRight(node); if (balance < -1 && this.getBalance(node.right!) <= 0) return this.rotateLeft(node); if (balance > 1 && this.getBalance(node.left!) < 0) { node.left = this.rotateLeft(node.left!); return this.rotateRight(node); } if (balance < -1 && this.getBalance(node.right!) > 0) { node.right = this.rotateRight(node.right!); return this.rotateLeft(node); } return node; } }
NestJS를 부트스트랩하는 기본 애플리케이션 파일입니다.
// src/cache/cache.service.ts import { Injectable, UnauthorizedException, InternalServerErrorException } from '@nestjs/common'; import { AVLTree } from '../utils/avltree'; import { InjectRedis, Redis } from '@nestjs-modules/ioredis'; @Injectable() export class CacheService { private avlTree: AVLTree; private authorizedTokens: Set<string> = new Set(['your_authorized_token']); // Authorized tokens constructor(@InjectRedis() private readonly redis: Redis) { this.avlTree = new AVLTree(); } validateToken(token: string): void { if (!this.authorizedTokens.has(token)) { throw new UnauthorizedException('Invalid access token'); } } async set(key: string, value: any, ttl: number, token: string): Promise<void> { this.validateToken(token); try { // Store in Redis await this.redis.set(key, JSON.stringify(value), 'PX', ttl); // Store in AVL Tree this.avlTree.insert(key, value, ttl); } catch (error) { throw new InternalServerErrorException('Failed to set cache'); } } async get(key: string, token: string): Promise<any> { this.validateToken(token); try { // First, attempt to retrieve from Redis const redisValue = await this.redis.get(key); if (redisValue) { return JSON.parse(redisValue); } // If not found in Redis, retrieve from AVL Tree const avlValue = this.avlTree.search(key); if (avlValue) { // Re-store in Redis for faster access next time // Assuming the remaining TTL is maintained in AVL Tree // For simplicity, we set a new TTL const newTtl = 60000; // 60 seconds as an example await this.redis.set(key, JSON.stringify(avlValue), 'PX', newTtl); return avlValue; } return null; } catch (error) { throw new InternalServerErrorException('Failed to get cache'); } } async delete(key: string, token: string): Promise<void> { this.validateToken(token); try { // Remove from Redis await this.redis.del(key); // Remove from AVL Tree this.avlTree.delete(key); } catch (error) { throw new InternalServerErrorException('Failed to delete cache'); } } }
모든 구성 요소를 구현한 후 애플리케이션을 실행하여 기능을 확인할 수 있습니다.
// src/cache/cache.controller.ts import { Controller, Get, Post, Delete, Body, Param, Query, HttpCode, HttpStatus } from '@nestjs/common'; import { CacheService } from './cache.service'; class SetCacheDto { key: string; value: any; ttl: number; // milliseconds token: string; } @Controller('cache') export class CacheController { constructor(private readonly cacheService: CacheService) {} @Post('set') @HttpCode(HttpStatus.CREATED) async setCache(@Body() body: SetCacheDto) { await this.cacheService.set(body.key, body.value, body.ttl, body.token); return { message: 'Data cached successfully' }; } @Get('get/:key') async getCache(@Param('key') key: string, @Query('token') token: string) { const value = await this.cacheService.get(key, token); return value ? { value } : { message: 'Key not found or expired' }; } @Delete('delete/:key') @HttpCode(HttpStatus.NO_CONTENT) async deleteCache(@Param('key') key: string, @Query('token') token: string) { await this.cacheService.delete(key, token); return { message: 'Key deleted successfully' }; } }
캐시 설정:
// src/cache/cache.module.ts import { Module } from '@nestjs/common'; import { CacheService } from './cache.service'; import { CacheController } from './cache.controller'; import { RedisModule } from '@nestjs-modules/ioredis'; @Module({ imports: [ RedisModule.forRoot({ config: { host: 'localhost', port: 6379, // Other Redis configurations }, }), ], providers: [CacheService], controllers: [CacheController], }) export class CacheModule {}
캐시 가져오기:
npm install @nestjs-modules/ioredis ioredis
캐시 삭제:
// src/app.module.ts import { Module } from '@nestjs/common'; import { CacheModule } from './cache/cache.module'; @Module({ imports: [CacheModule], controllers: [], providers: [], }) export class AppModule {}
은행 및 금융 시스템:
트래픽이 많은 전자상거래 플랫폼:
메시징 및 소셜 네트워킹 애플리케이션:
날씨 및 환율 애플리케이션:
콘텐츠 관리 시스템 및 미디어 플랫폼:
분석 애플리케이션 및 실시간 대시보드:
이 기사에서는 NestJS 프레임워크 내에서 AVL 트리와 Redis를 사용하여 고급 캐싱 시스템을 구현했습니다. 고급 TTL 관리, 토큰 기반 보안 및 Redis 통합을 제공하는 이 시스템은 수요가 많은 애플리케이션을 위한 강력하고 유연한 솔루션을 제공합니다. 이 두 기술의 결합은 두 기술의 장점을 모두 활용하여 Redis의 약점을 해결하고 전반적인 캐싱 성능을 향상시킵니다.
위 내용은 NestJS의 고급 캐싱으로 속도 및 성능 향상: AVL 트리 및 Redis를 사용하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!