分布式系列教程(07) -分布式Redis缓存 (缓存雪崩&穿透&热点key)

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 分布式系列教程(07) -分布式Redis缓存 (缓存雪崩&穿透&热点key)

引言

关于Redis:

一般对数据库进行数据变更的时候(增加、删除、修改)的时候才会对Redis进行缓存更新,不建议再查询的时候把查询出来的数据进行redis缓存更新。比如某公司只会在晚上进行缓存同步基础数据(如:省份及下级市)

1.缓存雪崩

缓存雪崩通俗简单的理解就是:由于原有缓存失效(或者数据未加载到缓存中),新缓存未到期间(缓存正常从Redis中获取)所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机,造成系统的崩溃。

正常流程如下图:

当缓存失效时:

1.2解决办法

缓存失效时的雪崩效应对底层系统的冲击非常可怕!那有什么办法来解决这个问题呢?

基本解决思路如下:

  • 方案一:大多数系统设计者考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,避免缓存失效时对数据库造成太大的压力,虽然能够在一定的程度上缓解了数据库的压力但是与此同时又降低了系统的吞吐量。
  • 方案二:分析用户的行为,尽量让缓存失效的时间均匀分布。
  • 方案三:如果是因为某台缓存服务器宕机,可以考虑做主备。比如:redis主备,但是双缓存涉及到更新事务的问题,update可能读到脏数据,需要解决。

方案一:加锁或队列(治标不治本)

  • 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
@Service
public class UserAvalanService {
  @Autowired
  private UserMapper userMapper;
  @Autowired
  private RedisService redisService;
  private Lock lock = new ReentrantLock();
  private String SIGN_KEY = "${NULL}";
  public Users getByUsers(Long id) {
    // 1.先查询redis
    String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName()
        + "-id:" + id;
    String userJson = redisService.getString(key);
    if (!StringUtils.isEmpty(userJson)) {
      Users users = JSONObject.parseObject(userJson, Users.class);
      return users;
    }
    Users user = null;
    try {
      lock.lock();
      // 查询db
      user = userMapper.getUser(id);
      redisService.setSet(key, JSONObject.toJSONString(user));
      lock.unlock();
    } catch (Exception e) {
    } finally {
      lock.unlock(); // 释放锁
    }
    return user;
  }

注意:加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法。

方案二:设置Key过时时间

  • 不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

方案三:二级缓存

  • A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期(此点为补充)。

2.缓存穿透

缓存穿透 是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。

解决的办法就是:

  • 如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。
  • 把空结果,也给缓存起来,这样下次同样的请求就可以直接返回空了,即可以避免当查询的值为空时引起的缓存穿透。同时也可以单独设置个缓存区域存储空值,对要查询的key进行预先校验,然后再放行给后面的正常缓存处理逻辑。
@Service
public class UserAvalanService {
  @Autowired
  private UserMapper userMapper;
  @Autowired
  private RedisService redisService;
  private Lock lock = new ReentrantLock();
  private String SIGN_KEY = "${NULL}";
  public String getByUsers(Long id) {
    // 1.先查询redis
    String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName()
        + "-id:" + id;
    String userName = redisService.getString(key);
    if (!StringUtils.isEmpty(userName)) {
      return userName;
    }
    System.out.println("######开始发送数据库DB请求########");
    Users user = userMapper.getUser(id);
    String value = null;
    if (user == null) {
      // 标识为null
      value = SIGN_KEY;
    } else {
      value = user.getName();
    }
    redisService.setString(key, value);
    return value;
  }
}

3.热点key

热点key指的是某个key访问非常频繁,当key失效的时候有大量线程来构建缓存,导致负载增加,系统崩溃。

解决办法:

  1. 使用锁,单机用synchronized,lock等,分布式用分布式锁。
  2. 缓存过期时间不设置,而是设置在key对应的value里。如果检测到存的时间超过过期时间则异步更新缓存。
  3. 在value设置一个比过期时间t0小的过期时间值t1,当t1过期的时候,延长t0并做更新缓存操作。

附:

总结

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
3天前
|
缓存 NoSQL Java
【Redis】5、Redis 的分布式锁、Lua 脚本保证 Redis 命令的原子性
【Redis】5、Redis 的分布式锁、Lua 脚本保证 Redis 命令的原子性
16 0
|
2天前
|
NoSQL Java Redis
Redis分布式锁和Java锁的区别
Redis分布式锁和Java锁的主要区别在于它们的适用范围和实现机制。
13 2
|
3天前
|
运维 NoSQL Java
【Redis】6、Redisson 分布式锁的简单使用(可重入、重试机制...)
【Redis】6、Redisson 分布式锁的简单使用(可重入、重试机制...)
25 1
|
3天前
|
NoSQL 算法 Java
【Redis】4、全局唯一 ID生成、单机(非分布式)情况下的秒杀和一人一单
【Redis】4、全局唯一 ID生成、单机(非分布式)情况下的秒杀和一人一单
29 0
|
3天前
|
存储 缓存 NoSQL
【Redis】3、Redis 作为缓存(Redis中的穿透、雪崩、击穿、工具类)
【Redis】3、Redis 作为缓存(Redis中的穿透、雪崩、击穿、工具类)
37 0
|
6天前
|
缓存 NoSQL Redis
如何在Python中使用Redis或Memcached进行缓存?
如何在Python中使用Redis或Memcached进行缓存?
10 2
|
11天前
|
NoSQL Java Redis
Spring boot 实现监听 Redis key 失效事件
【2月更文挑战第2天】 Spring boot 实现监听 Redis key 失效事件
30 0
|
11天前
|
NoSQL Java 关系型数据库
浅谈Redis实现分布式锁
浅谈Redis实现分布式锁
22 0
|
28天前
|
存储 缓存 NoSQL
Redis--缓存设计与性能优化
Redis--缓存设计与性能优化
|
28天前
|
缓存 监控 NoSQL
Redis缓存保卫战:拒绝缓存击穿的进攻【redis问题 三】
Redis缓存保卫战:拒绝缓存击穿的进攻【redis问题 三】
23 0

热门文章

最新文章