面试题答案
一键面试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请求缓存数据的获取和更新,通过封装好的函数(如 getCachedData
、updateCachedData
、ensureCacheValid
等)来操作。
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请求缓存数据,并保证缓存数据的一致性,同时合理处理缓存数据的过期和更新。