在一些高频率查询的系统中,使用缓存可以显著减少数据库的负载,提高系统的响应速度。但是如果不加限制的直接通过缓存查询,就会出现缓存穿透的问题,即查询一个不存在的 key,由于缓存没有命中,就会去查询数据库,这样造成了无谓的数据库承载。如何避免 Redis 缓存穿透呢?本文将介绍如何利用 bloom filter 解决 Redis 缓存穿透的问题。
什么是 bloom filter?
Bloom filter 是一种基于布隆算法实现的快速数据判定算法。基于一个二进制向量和一组哈希函数,通过将数据进行哈希映射并在向量相应位置打上标记,从而实现对数据是否存在的快速判定。Bloom filter 具有空间效率和查询效率高、可伸缩性好等特点,因此在大数据处理、网络数据包过滤等领域有广泛应用。
为什么要使用 bloom filter 避免缓存穿透?
在使用 Redis 进行缓存时,如果查询 key 不存在,Redis 不会存储这个不存在的 key,也不会添加任何信息。但是在查询这个不存在的 key 的时候,Redis 会去查找数据库,这样就会造成缓存穿透。
为了解决这个问题,可以通过在 Redis 中添加一个 key 的布隆过滤器来减轻数据库负载。当请求到来时,首先查询这个 key 是否在布隆过滤器中,如果不存在即可直接返回了,这样就能避免了缓存穿透。而如果 key 存在于布隆过滤器中,则可以去 Redis 中查询这个 key 是否存在,如果不存在很显然就是缓存穿透了。
如何实现使用 bloom filter 避免 Redis 缓存穿透?
安装 Redis 和 Redis bloom filter 插件。
初始化 Redis bloom filter,这里使用 redis-bloom 的 npm 包进行实现。
// javascriptcn.com 代码示例 const RedisBloom = require('redis-bloom'); const client = RedisBloom.createClient({ host: 'localhost', port: '6379', // 使用默认配置 }); const BF_KEY = 'bf:user'; await client.bfCreate(BF_KEY, 0.0001, 10000000); // 初始化一个容量为 10,000,000 ,错误率为 0.01% 的布隆过滤器 console.log('Redis bloom filter 已初始化');
- 查询缓存
// javascriptcn.com 代码示例 // 缓存 key 可能是 userName、userPhone、userId 等,需要用不同的字段和值组成不同的 key const user = await client.get(key); if (!user) { // 缓存不存在,则查询布隆过滤器 if (await client.bfExists(BF_KEY, key)) { // 如果 key 存在于布隆过滤器中说明不是缓存穿透,否则说明这个 key 一定不存在 const user = await client.queryDatabase(query); // 查询数据库 if (user) { // 如果数据库中查到了结果,则将查询结果存储到 Redis 缓存中 await client.set(key, JSON.stringify(user)); } else { // 如果数据库中没有查到结果,则将查询结果的 key 放入布隆过滤器中 await client.bfAdd(BF_KEY, key); } } }
示例代码
完整示例代码如下:
// javascriptcn.com 代码示例 const RedisBloom = require('redis-bloom'); const redis = require('redis'); const util = require('util'); const client = redis.createClient({ host: 'localhost', port: '6379', }); const bfClient = RedisBloom.createClient({ host: 'localhost', port: '6379', }); const BF_KEY = 'bf:user'; const queryDatabase = async () => { // 连接数据库进行查询 }; const getUserByName = async (userName) => { const key = `userName:${userName}`; const user = await util.promisify(client.get).call(client, key); if (!user) { // 缓存不存在,则查询布隆过滤器 if (await util.promisify(bfClient.bfExists).call(bfClient, BF_KEY, key)) { // 如果 key 存在于布隆过滤器中说明不是缓存穿透,否则说明这个 key 一定不存在 const user = await queryDatabase(); if (user) { // 如果数据库中查到了结果,则将查询结果存储到 Redis 缓存中 await util.promisify(client.set).call(client, key, JSON.stringify(user)); } else { // 如果数据库中没有查到结果,则将查询结果的 key 放入布隆过滤器中 await util.promisify(bfClient.bfAdd).call(bfClient, BF_KEY, key); } } } return user ? JSON.parse(user) : null; }; (async () => { await util.promisify(bfClient.bfCreate).call(bfClient, BF_KEY, 0.0001, 10000000); // 初始化一个容量为 10,000,000 ,错误率为 0.01% 的布隆过滤器 const user1 = await getUserByName('user1'); const user2 = await getUserByName('user2'); const user3 = await getUserByName('user3'); console.log(user1, user2, user3); // 可以看到只有 user1 存在于 Redis,其他就直接返回 null/undefined 了 })();
总结
通过使用 bloom filter,我们可以很方便地避免 Redis 缓存穿透问题,并且使用 bloom filter 无需考虑数据存储格式和具体实现,只需要将插件集成到 Redis 中就可以很好地解决问题。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/652e85a27d4982a6ebf8c5d2