首頁 >web前端 >js教程 >透過 NestJS 中的高級快取提高速度和效能:如何使用 AVL 樹和 Redis

透過 NestJS 中的高級快取提高速度和效能:如何使用 AVL 樹和 Redis

DDD
DDD原創
2024-12-26 12:15:19599瀏覽

Boosting Speed and Performance with Advanced Caching in NestJS: How to Use AVL Trees and Redis

在當今世界,回應請求的速度和效率對於大規模和高流量的系統至關重要。電子商務網站、社交網路和銀行服務等線上平檯面臨大量資料和使用者請求。這種高要求不僅會為伺服器和資料庫帶來巨大的負載,還會顯著影響使用者體驗。在這種情況下,實施快取系統可以成為提高效能和減少資源負載的有效解決方案。

在本文中,我們探討了結合 AVL 樹和 Redis 的高階快取系統的實作。該系統包括安全機制、TTL(生存時間)管理以及與 Redis 的集成,以增強性能和靈活性。目標是利用這兩種技術的優點,同時減輕它們的弱點。

重要提示:本文是在人工智慧的幫助下開發的。


基於AVL樹的快取系統與Redis結合的優點和缺點

優點:

  1. 提高記憶效率:

    • 智慧型TTL管理:透過使用AVL樹來管理資料過期,可以最佳化記憶體消耗,並防止保留陳舊資料。這在資料變化快、需要精確過期的場景下特別有用。
  2. 增強的安全性:

    • 令牌驗證:增加基於令牌的驗證機制增強了Redis的安全性。這個額外的安全層可以防止對快取的未經授權的訪問,從而增強整體系統的安全性。
  3. 進階 TTL 管理:

    • 自訂過期策略: AVL 樹允許實施 Redis 可能不支援的更複雜和客製化的過期策略。
  4. 多樣化的資料結構:

    • 平衡樹組合:作為一種平衡資料結構,與 Redis 的預設資料結構相比,AVL 樹可以為某些需要快速搜尋和排序的用例提供更好的效能。
  5. 增加靈活性和客製化:

    • 更好的客製化:結合兩個系統可以實現更廣泛的定制,從而能夠開發更精確和針對特定應用的解決方案。

缺點:

  1. 增加的架構複雜度:

    • 管理兩個快取系統:同時使用 Redis 和基於 AVL 樹的快取系統會增加架構複雜性,並且需要兩個系統之間的協調管理。
  2. 增加的時間開銷:

    • 額外延遲:新增額外的快取層可能會導致延遲。必須確保性能優勢超過這些潛在的延遲。
  3. 資料維護與同步:

    • 資料一致性:維護Redis和AVL樹之間的一致性和同步對於防止資料差異至關重要,需要複雜的同步機制。
  4. 更高的開發與維護成本:

    • 費用增加:開發和維護兩個快取系統需要更多資源和多樣化的專業知識,可能會增加整體專案成本。
  5. 安全複雜性:

    • 協調安全策略:確保在兩個系統中正確且一致地實施安全策略可能具有挑戰性。

利用AVL樹和Redis實現快取系統

下面,我們介紹一下這個快取系統的專業實作。此實作包括用於管理具有 TTL 功能的資料的 AVL 樹和用於快速資料儲存的 Redis。

1.帶有TTL的AVL樹

首先,我們實作具有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;
  }
}

2. 與Redis整合的快取服務(CacheService)

在本節中,我們實現了利用 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');
    }
  }
}

3. API控制器(CacheController)

控制器管理對快取服務的 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' };
  }
}

4.快取模組(CacheModule)

定義連接服務和控制器並注入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 {}

5.Redis配置

要在 NestJS 專案中使用 Redis,我們使用 @nestjs-modules/ioredis 套件。首先,安裝軟體套件:

npm install @nestjs-modules/ioredis ioredis

然後,在CacheModule中配置Redis,如上所示。如果您需要更進階的配置,可以使用單獨的設定檔。

6. 代幣驗證機制

為了管理和驗證代幣,可以採取各種策略。在這個簡單的實作中,令牌被維護在一個固定的集合中。對於較大的項目,建議使用 JWT(JSON Web Tokens)或其他進階安全方法。

7. 錯誤處理與輸入驗證

在此實作中,DTO(資料傳輸物件)類別用於輸入驗證和錯誤管理。此外,快取服務利用一般錯誤處理來防止意外問題。

8. 主應用模組(AppModule)

最後,我們將快取模組加入到主應用程式模組中。

// 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;
  }
}

9. 主應用程式檔案(main.ts)

引導 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');
    }
  }
}

10. 測試和運行應用程式

實現所有元件後,您可以執行應用程式以確保其功能。

// 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' };
  }
}

11. 樣品請求

設定快取:

// 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 {}

結合 Redis 和 AVL 基於樹的快取系統的合適用例

  1. 銀行與金融體系:

    • 管理敏感會話和交易:高安全性和精確的 TTL 管理對於敏感的財務資料至關重要。將令牌安全性和智慧 TTL 管理相結合在此領域非常有益。
  2. 高流量電商平台:

    • 儲存產品資料和管理購物車:優化記憶體和提高資料存取速度對於增強亞馬遜等大型線上商店的使用者體驗至關重要。
  3. 訊息和社交網路應用程式:

    • 儲存即時使用者狀態:需要快速存取和精確的資料管理來顯示使用者的線上/離線狀態和訊息。
  4. 天氣和貨幣兌換應用程式:

    • API 快取以減少請求負載:透過精確的過期管理儲存複雜運算的結果和即時數據,為使用者提供最新且快速的資訊。
  5. 內容管理系統與媒體平台:

    • 快取高流量頁面和內容:優化對高瀏覽量內容的存取並減少伺服器負載,以提供更流暢的使用者體驗。
  6. 分析應用程式和即時儀表板:

    • 儲存即時分析結果:使用多個快取提供快速且最新的分析數據,以提高效能和結果準確性。

結論

在本文中,我們在 NestJS 框架內使用 AVL 樹和 Redis 實現了高階快取系統。該系統提供先進的 TTL 管理、基於令牌的安全性和 Redis 集成,為高需求應用程式提供了強大且靈活的解決方案。這兩種技術的結合利用了兩者的優勢,解決了 Redis 的弱點並增強了整體快取效能。

以上是透過 NestJS 中的高級快取提高速度和效能:如何使用 AVL 樹和 Redis的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn