缓存击穿的风险和应对

2022-09-04  本文已影响0人  右耳菌

1. 缓存击穿场景重现


2. 思路

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

3. 分析

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. 布隆过滤器图示

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


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

上一篇下一篇

猜你喜欢

热点阅读