美文网首页
缓存击穿的风险和应对

缓存击穿的风险和应对

作者: 右耳菌 | 来源:发表于2022-09-04 23:29 被阅读0次

    1. 缓存击穿场景重现


    2. 思路

    思路: 查询之前先判断目标数据是否存在,不存在的直接忽略。将流量拦截于缓存和数据库之前。

    3. 分析

    • **问题的本质: **
      判断集合中是否存在某个元素。

    • 类似的场景:
      数据库设计中,海量数据查询,快速判断数据是否存在?
      数十亿网站域名,快速判定网址不合规。
      垃圾邮件快速判定;
      爬虫应用中的URL地址去重场景;

    • 不合理的做法:
      把数据全部加载到内存

    4. 设计

    目的: 减少内存占用
    方式: 不保存所有ID信息,只在内存中做一个标记

    布隆过滤器(Bloom Filter)

    布隆过滤器(Bloom Filter)是1970年由布隆提出的。
    它实际上是一个很长的二进制数组和一系列hash函数。
    布隆过滤器可以用于检索一个元素是否在一个集合中。
    它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

    5. 自研布隆过滤器

    利用Redis特性和命令: bitmaps (SETBIT设置指定位置的值、GETBIT获取值)可以理解为这是Redis自带的二进制数组特性。

    • 例子
    package cn.lazyfennec.cache.redis.service;
    
    import com.study.cache.redis.annotations.NeteaseCache;
    import com.study.cache.redis.pojo.User;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cache.annotation.CacheEvict;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.PostConstruct;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.TimeUnit;
    
    @Service // 默认 单实例
    public class UserService {
    
        @Autowired
        JdbcTemplate jdbcTemplate; // spring提供jdbc一个工具(mybastis类似)
    
        @Autowired
        RedisTemplate redisTemplate;
    
        // 生产环境:第一次初始化,后续,根据数据变化,自动维护
        @PostConstruct // 演示需要 --- 启动的时候去初始化布隆过滤器。
        public void init(){
            // 1. 加载所有数据 TODO 演示
            for (int i = 0; i < 1; i++) {
                String userId = "10001";
                int hashValue = Math.abs(userId.hashCode());
                long index = (long) (hashValue % Math.pow(2, 32)); // 元素  和 数组的映射
                // 设置Redis里面二进制数据中的值,对应位置 为 1
                redisTemplate.opsForValue().setBit("user_bloom_filter", index, true);
            }
        }
    
        /**
         * 根据ID查询用户信息 (redis缓存,用户信息以json字符串格式存在(序列化))
         */
        // @Cacheable(value="user", key = "#userId")// 返回值 存到redis: value -- key: user::10001
        public User findUserById(String userId) throws Exception {
            // 提前查询
            int hashValue = Math.abs(userId.hashCode());
            long index = (long) (hashValue % Math.pow(2, 32)); // 元素  和 数组的映射
            Boolean result = redisTemplate.opsForValue().getBit("user_bloom_filter", index);
            if(!result) {
                System.out.println("数据不存在" + userId);
                return null;
            }
    
            // 压根就不存在
            // 1. 先读取缓存
            Object cacheValue = redisTemplate.opsForValue().get(userId);
            if (cacheValue != null) {
                System.out.println("###从缓存读取数据");
                return (User) cacheValue;
            }
            // 2. 没有缓存读取数据 --- 连接,并发 支撑非常小 ---
            String sql = "select * from tb_user_base where uid=?";
            User user = jdbcTemplate.queryForObject(sql, new String[]{userId}, new BeanPropertyRowMapper<>(User.class));
            System.out.println("***从数据库读取数据");
            // 3. 设置缓存
            redisTemplate.opsForValue().set(userId, user);
            return user;
        }
    
        @CacheEvict(value = "user", key = "#user.uid") // 方法执行结束,清除缓存
        public void updateUser(User user) {
            String sql = "update tb_user_base set uname = ? where uid=?";
            jdbcTemplate.update(sql, new String[]{user.getUname(), user.getUid()});
        }
    
        /**
         * 根据ID查询用户名称
         */
        // 我自己实现一个类似的注解
        @NeteaseCache(value = "uname", key = "#userId") // 缓存
        public String findUserNameById(String userId) {
            // 查询数据库
            String sql = "select uname from tb_user_base where uid=?";
            String uname = jdbcTemplate.queryForObject(sql, new String[]{userId}, String.class);
    
            return uname;
        }
    }
    

    6. 布隆过滤器图示

    • 优点:
      • 内存空间占用少;
    • 缺点:
      • 布隆过滤器需要不断维护,带来新的工作
      • 布隆过滤器并不能精准过滤。
        (布隆过滤器判定不存在,100%不存在,判断为存在,则可能不存在的。)
        理论上Hash计算值是有碰撞的(不同的内容hash计算出同样的值),导致不存在的元素可能会被判断为存在

    布隆过滤器并非拦截所有请求,意在将缓存击穿控制在一定的量!


    如果觉得有收获就点个赞吧,更多知识,请点击关注查看我的主页信息哦~

    相关文章

      网友评论

          本文标题:缓存击穿的风险和应对

          本文链接:https://www.haomeiwen.com/subject/qmfvnrtx.html