在 Node.js 和 TypeScript 中使用 LRU 缓存

271 阅读3分钟

在构建 Web 应用程序时,我们常常需要执行一些高昂的操作。这些操作之所以代价高昂,要么是计算量很大,要么是需要很长时间才能完成操作,要么是因为需要调用昂贵的外部 API。

在许多情况下,一个简单的解决方案是使用缓存。缓存是一种技术,它允许我们将某个操作的结果存储起来,这样如果再次请求相同的数据,我们就不必再次执行该操作。

缓存有多种不同的方法,但在本文中,我将向您展示如何在 Node.js 中使用 lru-cache 包来实现一个 LRU缓存。

设置 LRU 缓存

首先,我们需要安装 lru-cache 包。

npm install lru-cache

然后,我们将设置一个 LRU 缓存来存储用户数据。这个缓存的最大容量为 5,这意味着它可以同时存储最多 5 个用户对象。以下是初始化方法:

import { LRUCache } from 'lru-cache';

const userCache = new LRUCache<number, User>({ max: 5 });

从 API 获取数据

接下来,我们需要模拟从外部 API 获取数据。我们将创建一个名为 fetchUserFromAPI 的函数,该函数接受一个用户 ID 并返回一个用户对象。这个函数将包含一个延迟,以模拟从网络获取数据所需的时间。

async function fetchUserFromAPI(userId: number): Promise<User | null> {
  console.log(`Fetching user data for ID: ${userId} from API...`);
  await new Promise(resolve => setTimeout(resolve, 500));

  const users: User[] = [
    { id: 1, name: 'Alice', email: 'alice@example.com' },
    { id: 2, name: 'Bob', email: 'bob@example.com' },
    { id: 3, name: 'Charlie', email: 'charlie@example.com' },
  ];
  
  const user = users.find((user) => user.id === userId);
  return user || null;
}

使用 LRU 缓存

现在,让我们创建一个名为 getUser 的函数。这个函数将首先检查用户数据是否已经在缓存中。如果在,我们将返回缓存的数据。如果不在,我们将从 API 获取数据并将其添加到缓存中。

async function getUser(userId: number): Promise<User | null> {
  const cachedUser = userCache.get(userId);

  if (cachedUser) {
    console.log(`User data for ID: ${userId} found in cache.`);
    return cachedUser;
  }
  
  const user = await fetchUserFromAPI(userId);
  if (user) {
    userCache.set(userId, user);
  }
  return user;
}

测试 LRU 缓存

为了看到我们的 LRU 缓存的实际效果,我们将创建一个 main 函数,该函数多次请求用户数据。这将展示缓存的工作方式以及当缓存满时如何移除最近最少使用的项。

async function main() {
  // 第一次请求,将从 API 获取
  console.log('First Request')
  let user1 = await getUser(1);
  console.log('User 1:', user1);
  console.log('-------------------')

  // 第二次请求相同用户,将从缓存中提供
  console.log('Second Request')
  user1 = await getUser(1);
  console.log('User 1:', user1);
  console.log('-------------------')

  // 请求不同用户,将从 API 获取
  console.log('Third Request')
  const user2 = await getUser(2);
  console.log('User 2:', user2);
  console.log('-------------------')

  // 请求新用户,将从 API 获取
  console.log('Fourth Request')
  const user3 = await getUser(3);
  console.log('User 3:', user3);
  console.log('-------------------')

  // 再次请求第一个用户,将从缓存中提供
  console.log('Fifth Request')
  const user1Again = await getUser(1);
  console.log('User 1 Again:', user1Again);
  console.log('-------------------')

  // 请求尚未获取的用户,将从 API 获取
  console.log('Sixth Request')
  const user4 = await getUser(4);
  console.log('User 4:', user4);
  console.log('-------------------')

  // 再次请求第二个用户,将从缓存中提供
  console.log('Seventh Request')
  const user2Again = await getUser(2);
  console.log('User 2 Again:', user2Again);
  console.log('-------------------')

  // 请求新用户,将从 API 获取,第一个用户将从缓存中移除
  console.log('Eighth Request')
  const user5 = await getUser(5);
  console.log('User 5:', user5);
  console.log('-------------------')

  // 再次请求第一个用户,将从 API 获取,因为它已被移除
  console.log('Ninth Request')
  const user1AgainAgain = await getUser(1);
  console.log('User 1 Again Again:', user1AgainAgain);
  console.log('-------------------')
}

main();

LRU 缓存的工作原理

当我们首次请求用户数据时,它来自 API。但当我们再次请求相同用户时,数据是从缓存中拉取的,这使得请求速度更快。这减轻了 API 的负载,并提高了我们应用程序的性能。

LRU 缓存的最大容量为 5。当我们请求第六个用户时,最近最少使用的项(在这种情况下,是第一个用户)将从缓存中移除,为新数据腾出空间。如果我们随后再次请求第一个用户,它将从 API 获取,因为它已不再在缓存中。

使用 LRU 缓存的好处

正如您所看到的,当我们多次请求相同用户数据时,它将从缓存中提供,这使得请求速度更快。这减轻了 API 的负载,提高了应用程序的性能,并且在许多情况下,它可以节省我们大量的资源。