MST

星途 面试题库

面试题:Node.js缓存中间件的优化与配置

在高并发的Node.js应用中使用缓存中间件提升API效率时,会遇到缓存穿透、缓存雪崩等问题。请详细阐述如何通过调整缓存中间件的配置(如设置缓存过期策略、缓存预热等)来解决这些问题,并结合具体代码示例说明。
45.3万 热度难度
前端开发Node.js

知识考点

AI 面试

面试题答案

一键面试

缓存穿透问题及解决办法

  1. 问题描述:缓存穿透指查询一个一定不存在的数据,由于缓存未命中,就会去查询数据库,若每次都查询数据库,当高并发时会对数据库造成巨大压力。
  2. 解决办法 - 设置缓存过期策略
    • 空值缓存:当查询数据库发现数据不存在时,也将空值缓存起来,并设置一个较短的过期时间,防止一直查询数据库。例如,在Node.js中使用node-cache模块:
const NodeCache = require('node-cache');
const myCache = new NodeCache();

async function getData(key) {
    let data = myCache.get(key);
    if (data === undefined) {
        // 假设这是查询数据库的函数
        data = await queryDatabase(key); 
        if (data === null) {
            // 缓存空值,设置较短过期时间,比如1分钟
            myCache.set(key, null, 60); 
        } else {
            myCache.set(key, data);
        }
    }
    return data;
}

async function queryDatabase(key) {
    // 模拟数据库查询,这里返回null表示数据不存在
    return null; 
}
  1. 解决办法 - 缓存预热
    • 布隆过滤器:在缓存预热阶段,使用布隆过滤器来判断数据是否存在。布隆过滤器可以快速判断一个元素是否在一个集合中,误判率很低。当查询时,先通过布隆过滤器判断,如果不存在则直接返回,避免查询数据库。以bloom-filters模块为例:
const BloomFilter = require('bloom-filters');
const myCache = new NodeCache();

// 初始化布隆过滤器,预计元素数量和误判率
const bf = new BloomFilter(1000, 0.01); 

// 缓存预热,假设从数据库获取所有存在的key
const allKeys = await getAllKeysFromDatabase(); 
allKeys.forEach(key => {
    bf.add(key);
    // 同时缓存数据
    const data = await queryDatabase(key);
    myCache.set(key, data);
});

async function getData(key) {
    if (!bf.has(key)) {
        return null;
    }
    let data = myCache.get(key);
    if (data === undefined) {
        data = await queryDatabase(key);
        myCache.set(key, data);
    }
    return data;
}

async function queryDatabase(key) {
    // 模拟数据库查询
    return { value: 'data' }; 
}

async function getAllKeysFromDatabase() {
    // 模拟获取所有数据库中的key
    return ['key1', 'key2', 'key3']; 
}

缓存雪崩问题及解决办法

  1. 问题描述:缓存雪崩指在某一时刻,大量的缓存数据同时过期,导致大量请求直接访问数据库,造成数据库压力过大甚至崩溃。
  2. 解决办法 - 设置缓存过期策略
    • 随机过期时间:避免设置固定的过期时间,而是在一个合理的时间范围内设置随机的过期时间。例如:
const NodeCache = require('node-cache');
const myCache = new NodeCache();

async function setData(key, data) {
    // 设置10 - 20分钟之间的随机过期时间
    const randomExpiry = Math.floor(Math.random() * (20 - 10 + 1) + 10) * 60; 
    myCache.set(key, data, randomExpiry);
}

async function getData(key) {
    let data = myCache.get(key);
    if (data === undefined) {
        data = await queryDatabase(key);
        await setData(key, data);
    }
    return data;
}

async function queryDatabase(key) {
    // 模拟数据库查询
    return { value: 'data' }; 
}
  1. 解决办法 - 缓存预热
    • 分级缓存:在缓存预热时,将数据分级缓存。例如,一级缓存设置较短的过期时间,二级缓存设置较长的过期时间。当一级缓存过期后,先从二级缓存获取数据,同时重新设置一级缓存。
const NodeCache = require('node-cache');
const firstLevelCache = new NodeCache();
const secondLevelCache = new NodeCache();

async function setData(key, data) {
    // 一级缓存设置较短过期时间,如10分钟
    firstLevelCache.set(key, data, 10 * 60); 
    // 二级缓存设置较长过期时间,如1小时
    secondLevelCache.set(key, data, 60 * 60); 
}

async function getData(key) {
    let data = firstLevelCache.get(key);
    if (data === undefined) {
        data = secondLevelCache.get(key);
        if (data!== undefined) {
            // 重新设置一级缓存
            firstLevelCache.set(key, data, 10 * 60); 
        } else {
            data = await queryDatabase(key);
            await setData(key, data);
        }
    }
    return data;
}

async function queryDatabase(key) {
    // 模拟数据库查询
    return { value: 'data' }; 
}