MST
星途 面试题库

面试题:Node.js HTTP请求缓存策略与分布式系统的融合

假设你正在开发一个分布式Node.js应用,需要在多个Node.js实例间共享HTTP请求缓存数据。请设计一套完整的缓存策略与实现架构,包括但不限于选择合适的分布式缓存技术(如Redis),如何保证缓存数据的一致性,以及如何处理缓存数据的过期和更新,详细说明每个部分的实现细节和关键代码片段。
10.4万 热度难度
前端开发Node.js

知识考点

AI 面试

面试题答案

一键面试

1. 选择分布式缓存技术 - Redis

Redis是一个高性能的键值对存储系统,非常适合用于分布式缓存。它支持多种数据结构(如字符串、哈希表、列表等),并且提供了丰富的命令集来操作这些数据结构。同时,Redis具有高可用性、分布式特性,能够满足分布式Node.js应用的缓存需求。

2. 保证缓存数据的一致性

  • 读写锁机制:在更新缓存数据时,使用Redis的分布式锁来保证同一时间只有一个实例能够进行写操作,避免数据冲突。在Node.js中可以使用 ioredis 库来实现分布式锁。
const Redis = require('ioredis');
const redis = new Redis();

const lockKey = 'cache-update-lock';
const acquireLock = async () => {
    const lockValue = Math.random().toString(36).substring(7);
    const lockAcquired = await redis.set(lockKey, lockValue, 'NX', 'EX', 10);
    return lockAcquired ? lockValue : null;
};

const releaseLock = async (lockValue) => {
    if (lockValue) {
        await redis.del(lockKey);
    }
};

在更新缓存数据前,先调用 acquireLock 获取锁,操作完成后调用 releaseLock 释放锁。

  • 数据版本控制:为每个缓存数据设置版本号,每次数据更新时版本号递增。当读取缓存数据时,同时获取版本号并与预期版本号进行比较。如果不一致,则重新从数据源获取数据并更新缓存。
const cacheKey = 'http-request-cache';
const versionKey = 'http-request-cache-version';

const getCachedData = async () => {
    const multi = redis.multi();
    multi.get(cacheKey);
    multi.get(versionKey);
    const [data, version] = await multi.exec();
    return { data, version };
};

const updateCachedData = async (newData) => {
    const lockValue = await acquireLock();
    try {
        const currentVersion = await redis.get(versionKey) || 0;
        const newVersion = parseInt(currentVersion, 10) + 1;
        const multi = redis.multi();
        multi.set(cacheKey, newData);
        multi.set(versionKey, newVersion);
        await multi.exec();
    } finally {
        await releaseLock(lockValue);
    }
};

3. 处理缓存数据的过期和更新

  • 过期策略:使用Redis的过期时间(TTL)功能来设置缓存数据的过期时间。在将数据存入缓存时,指定一个合适的过期时间。
const setCachedDataWithExpiry = async (data, expirationTime) => {
    await redis.setex(cacheKey, expirationTime, data);
};
  • 缓存更新
    • 主动更新:当数据源的数据发生变化时,主动更新缓存数据。结合前面提到的锁机制和版本控制进行更新。
    • 被动更新:当缓存数据过期或者版本不一致时,从数据源重新获取数据并更新缓存。
const fetchDataFromSource = async () => {
    // 实际从数据源获取数据的逻辑,例如数据库查询、HTTP请求等
    return 'new data from source';
};

const ensureCacheValid = async () => {
    const { data, version } = await getCachedData();
    if (!data || version < expectedVersion) {
        const newData = await fetchDataFromSource();
        await updateCachedData(newData);
    }
    return data;
};

4. 整体架构实现

在Node.js应用中,每个实例都通过 ioredis 连接到同一个Redis实例。对于HTTP请求缓存数据的获取和更新,通过封装好的函数(如 getCachedDataupdateCachedDataensureCacheValid 等)来操作。

const express = require('express');
const app = express();

app.get('/data', async (req, res) => {
    const data = await ensureCacheValid();
    res.send(data);
});

const port = 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

这样,通过上述缓存策略和实现架构,可以有效地在多个Node.js实例间共享HTTP请求缓存数据,并保证缓存数据的一致性,同时合理处理缓存数据的过期和更新。