MST

星途 面试题库

面试题:Node.js HTTP请求缓存策略在高并发场景下的优化

在高并发场景下,Node.js应用使用HTTP请求缓存策略时可能会遇到缓存一致性、缓存击穿等问题。请描述这些问题产生的原因,并给出至少两种针对这些问题的优化方案,且说明每种方案在Node.js中的具体实现思路。
19.8万 热度难度
前端开发Node.js

知识考点

AI 面试

面试题答案

一键面试

缓存一致性问题

  1. 产生原因:在高并发环境下,多个请求可能同时对缓存数据进行读写操作。当数据发生变化时,如果没有合适的同步机制,不同请求读取到的缓存数据可能不一致。例如,一个请求更新了数据库中的数据,但还未来得及更新缓存,其他请求仍然从缓存中读取到旧数据。
  2. 优化方案
    • 读写锁机制
      • 实现思路:在Node.js中可以使用第三方库如rwlock来实现读写锁。当有写操作时,获取写锁,此时其他读写操作都被阻塞,确保写操作完成后,缓存数据与数据库数据一致。读操作时获取读锁,允许多个读操作同时进行,但有写操作时,读操作会被阻塞。示例代码如下:
const RWLock = require('rwlock');
const rwlock = new RWLock();

// 写操作
function writeData(newData) {
    return new Promise((resolve, reject) => {
        rwlock.writeLock((release) => {
            // 更新数据库和缓存
            // 假设这里有更新数据库和缓存的逻辑
            release();
            resolve();
        });
    });
}

// 读操作
function readData() {
    return new Promise((resolve, reject) => {
        rwlock.readLock((release) => {
            // 从缓存读取数据
            // 假设这里有从缓存读取数据的逻辑
            release();
            resolve();
        });
    });
}
- **使用发布 - 订阅模式**:
    - **实现思路**:当数据更新时,发布一个事件,所有订阅了该事件的模块收到通知后更新缓存。在Node.js中可以使用内置的`EventEmitter`模块来实现。示例代码如下:
const EventEmitter = require('events');
const emitter = new EventEmitter();

// 订阅事件
emitter.on('dataUpdate', () => {
    // 更新缓存的逻辑
});

// 数据更新时发布事件
function updateData(newData) {
    // 更新数据库
    // 发布事件通知缓存更新
    emitter.emit('dataUpdate');
}

缓存击穿问题

  1. 产生原因:缓存击穿指的是在高并发情况下,一个热点缓存数据过期的瞬间,大量请求同时绕过缓存直接访问数据库,给数据库带来巨大压力。例如,一个商品详情页的缓存设置了较短的过期时间,过期时恰好大量用户同时访问该商品详情,这些请求都会直接去数据库查询。
  2. 优化方案
    • 互斥锁(Mutex)
      • 实现思路:在Node.js中可以使用redisSETNX(SET if Not eXists)命令来实现互斥锁。当缓存过期时,只有一个请求能获取到锁,该请求去数据库加载数据并更新缓存,其他请求等待锁释放后从缓存获取数据。示例代码如下:
const redis = require('redis');
const client = redis.createClient();

async function getData(key) {
    let data = await new Promise((resolve, reject) => {
        client.get(key, (err, reply) => {
            if (err) {
                reject(err);
            } else {
                resolve(reply);
            }
        });
    });
    if (!data) {
        const lockKey = `lock:${key}`;
        const acquireLock = await new Promise((resolve, reject) => {
            client.setnx(lockKey, 1, (err, reply) => {
                if (err) {
                    reject(err);
                } else {
                    resolve(reply);
                }
            });
        });
        if (acquireLock) {
            try {
                // 从数据库获取数据
                data = await getFromDatabase(key);
                // 更新缓存
                await new Promise((resolve, reject) => {
                    client.set(key, data, (err, reply) => {
                        if (err) {
                            reject(err);
                        } else {
                            resolve(reply);
                        }
                    });
                });
            } finally {
                // 释放锁
                await new Promise((resolve, reject) => {
                    client.del(lockKey, (err, reply) => {
                        if (err) {
                            reject(err);
                        } else {
                            resolve(reply);
                        }
                    });
                });
            }
        } else {
            // 等待一段时间后重试获取数据
            await new Promise((resolve) => setTimeout(resolve, 100));
            return getData(key);
        }
    }
    return data;
}

function getFromDatabase(key) {
    // 从数据库获取数据的逻辑
    return Promise.resolve('data from database');
}
- **热点数据永不过期**:
    - **实现思路**:对热点数据设置一个很长的过期时间,或者不设置过期时间。同时在系统中另外安排一个定时任务,定期更新这些热点数据的缓存,确保数据的实时性。在Node.js中可以使用`setInterval`来实现定时任务。示例代码如下:
const redis = require('redis');
const client = redis.createClient();

// 加载热点数据到缓存
async function loadHotData() {
    const data = await getFromDatabase();
    await new Promise((resolve, reject) => {
        client.set('hotDataKey', data, (err, reply) => {
            if (err) {
                reject(err);
            } else {
                resolve(reply);
            }
        });
    });
}

// 定时更新热点数据
setInterval(async () => {
    await loadHotData();
}, 60 * 1000); // 每60秒更新一次

function getFromDatabase() {
    // 从数据库获取数据的逻辑
    return Promise.resolve('data from database');
}