Dans le monde d'aujourd'hui, la rapidité et l'efficacité de la réponse aux demandes sont d'une importance primordiale pour les systèmes à grande échelle et à fort trafic. Les plateformes en ligne telles que les sites de commerce électronique, les réseaux sociaux et les services bancaires sont confrontées à un volume massif de données et de demandes des utilisateurs. Cette forte demande impose non seulement une charge importante sur les serveurs et les bases de données, mais peut également avoir un impact significatif sur l'expérience utilisateur. Dans ce contexte, la mise en œuvre d'un système de mise en cache peut être une solution efficace pour améliorer les performances et réduire la charge des ressources.
Dans cet article, nous explorons la mise en œuvre d'un système de mise en cache avancé qui combine les arborescences AVL et Redis. Ce système comprend des mécanismes de sécurité, une gestion TTL (Time to Live) et une intégration avec Redis pour améliorer les performances et la flexibilité. L'objectif est de tirer parti des avantages des deux technologies tout en atténuant leurs faiblesses.
Remarque importante : cet article a été développé avec l'aide de l'intelligence artificielle.
Efficacité de la mémoire améliorée :
Sécurité renforcée :
Gestion TTL avancée :
Structures de données diverses :
Flexibilité et personnalisation accrues :
Complexité architecturale accrue :
Augmentation des frais généraux :
Maintenance et synchronisation des données :
Coûts de développement et de maintenance plus élevés :
Complexité de sécurité :
Ci-dessous, nous présentons la mise en œuvre professionnelle de ce système de mise en cache. Cette implémentation comprend une arborescence AVL pour gérer les données avec des capacités TTL et Redis pour un stockage rapide des données.
Tout d'abord, nous implémentons l'arborescence AVL avec des capacités de gestion TTL.
// 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; } }
Dans cette section, nous implémentons le service de cache qui utilise à la fois l'arborescence AVL et Redis pour la gestion du cache. De plus, nous intégrons un mécanisme de validation de jeton.
// 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'); } } }
Le contrôleur gère les requêtes API vers le service de 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' }; } }
Définit le module de cache qui connecte le service et le contrôleur et injecte 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 {}
Pour utiliser Redis dans le projet NestJS, nous utilisons le package @nestjs-modules/ioredis. Tout d’abord, installez le package :
npm install @nestjs-modules/ioredis ioredis
Ensuite, configurez Redis dans le CacheModule comme indiqué ci-dessus. Si vous avez besoin de configurations plus avancées, vous pouvez utiliser des fichiers de configuration distincts.
Pour gérer et valider les jetons, diverses stratégies peuvent être utilisées. Dans cette implémentation simple, les jetons sont conservés dans un ensemble fixe. Pour les projets plus importants, il est recommandé d'utiliser JWT (JSON Web Tokens) ou d'autres méthodes de sécurité avancées.
Dans cette implémentation, les classes DTO (Data Transfer Object) sont utilisées pour la validation des entrées et la gestion des erreurs. De plus, le service de cache utilise la gestion générale des erreurs pour éviter les problèmes inattendus.
Enfin, nous ajoutons le module de cache au module d'application principal.
// 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; } }
Le fichier d'application principal qui démarre 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'); } } }
Après avoir implémenté tous les composants, vous pouvez exécuter l'application pour garantir sa fonctionnalité.
// 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' }; } }
Définir le cache :
// 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 {}
Obtenir le cache :
npm install @nestjs-modules/ioredis ioredis
Supprimer le cache :
// src/app.module.ts import { Module } from '@nestjs/common'; import { CacheModule } from './cache/cache.module'; @Module({ imports: [CacheModule], controllers: [], providers: [], }) export class AppModule {}
Systèmes bancaires et financiers :
Plateformes de commerce électronique à fort trafic :
Applications de messagerie et de réseaux sociaux :
Applications météo et de change :
Systèmes de gestion de contenu et plateformes multimédias :
Applications analytiques et tableaux de bord en temps réel :
Dans cet article, nous avons implémenté un système de mise en cache avancé utilisant des arborescences AVL et Redis dans le framework NestJS. Ce système, offrant une gestion TTL avancée, une sécurité basée sur des jetons et une intégration Redis, fournit une solution robuste et flexible pour les applications à forte demande. La combinaison de ces deux technologies exploite les atouts des deux, corrigeant les faiblesses de Redis et améliorant les performances globales de mise en cache.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!