redis缓存雪崩,缓存穿透

一、缓存雪崩

缓存雪崩是指在非预期情况下,大量的缓存同时过期导致大量的请求直击数据库,可能因流量过大使得数据库直接崩溃。

预防措施

  1. 设置不同的过期时间,避免同时过期。

  2. 事前通过在应用层增加熔断、限流等处理手段,避免数据库压力过大。

  3. 使用更持久的持久化方案,过期数据从持久层加载回缓存。

  4. 使用分布式锁或者队列,只允许一个线程查询数据库并写入缓存。

  5. 限制大批量操作,大批量操作往往导致数据库压力巨大。

CacheConfig配置类中,我们定义了一个KeyGenerator,用来生成缓存的键,并且使用RedisCacheManager设置了一个全局的缓存过期时间,这个时间可以根据实际需要进行调整。

现在我们的getUserInfo方法就可以利用这个自定义的缓存管理器,对缓存的键进行管理,并且设置过期时间了。

Java示例

下面是一个简单的实现过程,通过设置随机的过期时间,避免大量缓存同时过期:

如果你正在使用Spring和Spring Boot,你可以使用@CacheableSimpleKeyGenerator来简化操作。考虑以下的方法:

复制代码
@Cacheable(value = "userCache")
public String getUserInfo(String userId) {
    return databaseService.getUserInfo(userId);
}

默认情况下,这个缓存没有设置过期时间。为了避免缓存雪崩,你可以修改SimpleKeyGenerator为你自定义的方法,在这个自定义的方法里给每一个缓存项设置一个随机的过期时间。

复制代码
@Configuration
public class CacheConfig extends CachingConfigurerSupport {

    @Bean
    public KeyGenerator wiselyKeyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

    @Bean
    public CacheManager cacheManager(RedisTemplate<?, ?> redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        // 缓存失效时间,单位是秒,这里设置了没30分钟失效一次,然后在实际使用中,我们从该缓存中获取数据就可以了
        cacheManager.setDefaultExpiration(1800L);
        return cacheManager;
    }
}

二、缓存穿透

缓存穿透是指查询一个数据库中没有的数据,此时缓存不起作用,请求会穿透到数据库,当大量此类请求出现时,会给数据库带来极大的压力。

预防措施

  1. 使用布隆过滤器。将所有可能的数据hash到一个足够大的bitmap里,一个一定不存在的数据会被这个bitmap拦截,从而避免对底层存储系统的查询压力。

  2. 无论数据是否存在,都进行缓存。对于不存在的数据,我们可以存储一个特殊的值,用于标识这个值的不存在。使用这个策略要设置一个较短的过期时间。

以下是一个Java与Redis缓存穿透的实现例子:

复制代码
public String getUserInfo(String userId) {
    // 从缓存中获取用户信息
    String userInfo = redisTemplate.opsForValue().get(userId);
    if (userInfo == null) {
        // 如果缓存中没有,我们从数据库中获取
        userInfo = databaseService.getUserInfo(userId);
        // 对空结果设置一个默认值,有效期设定为5分钟(这个时间可以自己设置),
        // 避免频繁的对数据库进行相同的查询操作
        if (userInfo == null) {
            userInfo = "NULL_VALUE";
            redisTemplate.opsForValue().set(userId, userInfo, 5, TimeUnit.MINUTES);
        } else {
            // 如果查到了,那么放入缓存中
            redisTemplate.opsForValue().set(userId, userInfo);
        }
    } else if ("NULL_VALUE".equals(userInfo)) {
        // 如果缓存中查到了这个空值,那么返回空给用户
        userInfo = null;
    }
    return userInfo;
}

此Java示例描述的是第二种策略,当从数据库中查询所需的值为空时,将一个对应的空标志NULL_VALUE存入缓存,达到标记的目的。根据这个标记,可以避免用户不断查询数据库中不存在的值,从而减少了对数据库的压力。

相关推荐
XDH_CS19 分钟前
MySQL 8.0 安装与 MySQL Workbench 使用全流程(超详细教程)
开发语言·数据库·mysql
运维全栈笔记25 分钟前
K8S部署Redis高可用全攻略:1主2从3哨兵架构实战
redis·docker·云原生·容器·架构·kubernetes·bootstrap
treacle田41 分钟前
达梦数据库-统计信息收集-记录
数据库·达梦数据库统计信息收集
审判长烧鸡1 小时前
PostgreSQL之索引/函数/触发器
数据库·postgresql·触发器·函数·索引
Data_Journal1 小时前
如何使用cURL更改User Agent
大数据·服务器·前端·javascript·数据库
掌心向暖RPA自动化2 小时前
如何获取网页某个元素在屏幕可见部分的中心坐标影刀RPA懒加载坐标定位技巧
java·javascript·自动化·rpa·影刀rpa
日取其半万世不竭2 小时前
Minecraft Java版社区服务器搭建教程(Linux,适合新手)
java·linux·服务器
Python私教2 小时前
GenericAgent PySide6 桌面应用深度解析:悬浮按钮 + 聊天面板的原生 Qt 方案
开发语言·数据库·qt
byoass2 小时前
企业云盘与设计软件深度集成:AutoCAD/Revit/SolidWorks插件开发与API集成实战
服务器·网络·数据库·安全·oracle·云计算
TeamDev2 小时前
JxBrowser 9.0.0 版本发布啦!
java·前端·混合应用·jxbrowser·浏览器控件·跨平台渲染·原声输入