redis-缓存穿透--布隆过滤器

本文探讨了缓存穿透问题,特别是在高并发场景下,如何利用布隆过滤器有效减少无效查询,减轻数据库压力。通过具体示例和代码演示,展示了布隆过滤器在缓存前拦截查询,避免无效账号频繁查询数据库,提高系统效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

redis在我们实际的场景中用到的地方非常多,经常的用于缓存。

大面积的使用缓存的时候,我们要考虑到:缓存的穿透、雪崩,还有要注意key的设计
         * 1:模拟登陆的场景---用户输入账号登陆 第一次登陆的时候
         * 缓存里肯定没有,这个时候从数据库去查询,如果查询到了,则返回的同时,并且把姓名放到缓存里去 这样下次再去登陆                  的 时候,则从缓存里拿了
         * (放到缓存里的key就是这个账号  value是客户的姓名) 2:如果有这样的一种场景:用户传进来的 账号,在缓存里也没                有,然后再数据库里也没有
         * 在多线程并发的情况下,就会出现不停的查询数据库的场景,这个时候DB可能撑不住会直接挂了(最简单的就是连接池满                   了,直接蹦了)
         * 3:我们这个时候就需要去对这个key 进行处理,
         * 如果根据这个key查不到,并且数据库里也查不到这个时候就给这个key缓存下,放一个空的值到redis里 这样下次 在进来
         * 查询就可以直接从缓存里查询,发现返回一个空的,这个时候直接告诉客户的不存在即可。 
         * 4:但是 实际中还有这样的一种场景,就是这个key不是固定的随机的在变换 (===========有的人比较坏====比如随机                产生UUID的这样key,这个key根本就不存在),我们发现这个key不在缓存里,每次都不一样,这样还是会每次不停的去                 查 询数据库,给DB造成相当大的压力
         * 这就比较麻烦了。所以这里我们在查询缓存之前,在做一层拦截,确定一下这个key到底在不在数据库里,在数据库里,再去执行我们前面的逻辑,那么如何确定一个元素,在百万级别的数据库里到底是否存在呢?很显然我们不可能每次都要去数据库查询出在还是不在,这对DB同样是超级大的压力,我们需要一个方法或者工具来帮我们判断,这个元素到底在还是不在,这个时候我们引入一个过滤器-----布隆过滤器(底层是位图运算,大家感兴趣可以去搜索下,主要的应用是在海量的元素中寻找某一个元素是否存在)
         * 5:在查询redis缓存之前,实现用布隆过滤器拦截一下
         ===
======================分割线===============话不多说直接上我的演示代码=====================

附上一些伪代码,其实大部分代码都是可以用的,大家自己自行补上中文部分

import java.util.UUID;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;

import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Test {
    /**
     * 由于我这个代码是拆分到 main方法里来的,大家实际的时候记得不要这么玩,代码会有报错的
     * 
     * 部分中文的地方 请大家自行补上自己的代码
     * */
    static BloomFilter<String> bf;
    @Autowired
    @Qualifier("forObject")
    private RedisTemplate<String, Object> redisTemplate;
    public static void main(String[] args) {
        innt();
        String keyAccount = UUID.randomUUID().toString();
        // 如果 布隆过滤器 认为可能含有 这个key ,才去执行 之前的操作
        if (bf.mightContain(keyAccount)) {
            createThread(keyAccount);
        } else {
            log.debug("不存在该账号:请检查数据================================");
        }
    }
    /**
     * 模拟 100个线程 同时访问登陆的方法   CyclicBarrier 用到JDK里这个的工具类
     */
    public static void createThread(String key) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(100);
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        for (int i = 0; i < 100; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    getMyUserName(key);
                    try {
                        cyclicBarrier.await();
                    } catch (InterruptedException | BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

    }
    public static String getMyUserName(String keyAccount) {
        String name = "";
        // 如果存在则再去执行原先的逻辑 ,否则直接返回不存在
        // 当我们随机传一个key的时候 这个肯定肯定在 redis里不存,在数据库也不存在,这个时候redis就发生了缓存穿透
        // 查询缓存
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        name = (String) redisTemplate.opsForValue().get(keyAccount);
        // 双层检测锁
        if (StringUtils.isBlank(name)) {
            synchronized (this) {// 双层加锁 --
                name = (String) redisTemplate.opsForValue().get(keyAccount);
                if (StringUtils.isBlank(name)) {
                    // 缓存为空,查询数据库
                    返回值 result = 从数据库根据账号查询出姓名来
                    if (result != null && StringUtils.isNotBlank(result.getName())) {
                        // 把数据库查询出来的数据放入redis
                        redisTemplate.opsForValue().set(keyAccount, result.getName());
                    } else {
                        System.out.println("这个时候就发生缓存穿透了--------------------------------------》");
                    }
                }
            }
        } else {
            System.out.println("这个时候我们能从缓存里拿到的,不会直接去访问数据库");
            System.out.println(name);
        }
        return name;
    }
    /**
     * 初始化数据库的数据到 布隆过滤器里来
     **/
    public static void innt() {
          返回值 result = 从数据库查询 实现的100万条数据到,然后加载到布隆过滤器里
        // 从数据库里拿到数据 加载到布隆过滤器
        bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), reult.size());
        // 把数据放到布隆过滤器里
        for (Useruser : reult) {
            bf.put(user.getAccount());
        }
    }
}

=====================================user=================实体类===================

import java.io.Serializable;

import lombok.Data;
@Data
public class User implements Serializable{
    private static final long serialVersionUID = 1L;

    private String name; 
    private String account;

}

另外附上:redis序列化对象的以及基本的设置类

===============================RedisConfig类======================================

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
        @Bean("forObject")
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
            RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
//            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//            ObjectMapper om = new ObjectMapper();
//            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//            jackson2JsonRedisSerializer.setObjectMapper(om);
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            // key采用String的序列化方式
//            template.setKeySerializer(stringRedisSerializer);
            // hash的key也采用String的序列化方式
//            template.setHashKeySerializer(stringRedisSerializer);
            // value序列化方式采用jackson
//            template.setValueSerializer(jackson2JsonRedisSerializer);
            // hash的value序列化方式采用jackson
//            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            
            // 采用 key 是stirng ,value是对象的方式
            JdkSerializationRedisSerializer redisSerializer = new JdkSerializationRedisSerializer();
            RedisSerializer<String> stringSerializer = new StringRedisSerializer();
            template.setConnectionFactory(factory);
            template.setKeySerializer(stringSerializer);
            template.setValueSerializer(redisSerializer);
            return template;
        }
}
===============================RedisObjectSerializer 类======================================

import org.springframework.core.convert.converter.Converter;
import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
/**
 * 虽然以上的代码实现了 Redis 基础的数据操作,但是遗憾的是在 Java 开发领域内必须要考虑一个实际的问题,
 * 那么就是对象 的序列化保存问题,毕竟 Redis 数据库的读写速度是非常快的,但是如果不能够进行对象的存储,
 * 这样的存储意义就不大了,这样 就需要准备一个对象的序列化处理程序类,通过对象的形式进行数据的存储。
 * 如果要想进行 Redis 对象序列化操作则一定要首先准备一个序列化处理程序类,这个程序类有实现要求:
 * */
public class RedisObjectSerializer implements RedisSerializer<Object> {
    // 为了方便进行对象与字节数组的转换,所以应该首先准备出两个转换器
    private Converter<Object, byte[]> serializingConverter = new SerializingConverter();
    private Converter<byte[], Object> deserializingConverter = new DeserializingConverter();
    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; // 做一个空数组,不是null

    @Override
    public byte[] serialize(Object obj) throws SerializationException {
        if (obj == null) { // 这个时候没有要序列化的对象出现,所以返回的字节数组应该就是一个空数组
            return EMPTY_BYTE_ARRAY;
        }
        return this.serializingConverter.convert(obj); // 将对象变为字节数组
    }

    @Override
    public Object deserialize(byte[] data) throws SerializationException {
        if (data == null || data.length == 0) { // 此时没有对象的内容信息
            return null;
        }
        return this.deserializingConverter.convert(data);
    }

}

redis的连接配置,自己在项目里配置就好了,本身我这里是springCloud项目,所以只要在yaml里文件配置好了

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值