Redis|Redis——Redis用作缓存(内存回收/穿透/击穿/雪崩)

Redis 用作缓存
文章目录

  • Redis 用作缓存
    • 一、概念
    • 二、设置 key 有效期
    • 三、内存回收策略
    • 四、有关缓存的常见面试题
      • 1、缓存穿透
      • 2、缓存击穿
      • 3、缓存雪崩

一、概念 Redis 既可以做缓存,又可以做数据库。那么 Redis 做缓存与数据库间的区别?
【Redis|Redis——Redis用作缓存(内存回收/穿透/击穿/雪崩)】首先需要明白,在系统中使用缓存并不是为了秀技术,而是为了解决架构存在的问题
使用 Redis 作为缓存的目的:
  • 首先Redis 数据是存在于内存中的,内存速度快于磁盘千百倍,所以使用缓存一能够加快请求的响应速度
  • 再就是让大量的查询在到达缓存的时候,就能够得到数据,不用再去数据库中查询,减轻大量请求访问数据库的压力
Redis 做缓存的时候,由于内存空间有限,每个 Redis 实例中只分配部分内存空间,所以 Redis 缓存中存放的并不是全量的数据,而是经常被访问的热数据,热数据是随着用户的请求趋势而发生变化的。换言之,Redis 的数据并不像数据库那样重要,它是随着时间变化,冷数据应该被剔除,让出空间给热数据
Redis|Redis——Redis用作缓存(内存回收/穿透/击穿/雪崩)
文章图片

那么 Redis 用作缓存的时候是如何保证淘汰冷数据,腾出空间给热数据呢?一般有如下两种方案:
1. 设置 key 有效期
2. 内存回收策略:LRU、LFU
Redis|Redis——Redis用作缓存(内存回收/穿透/击穿/雪崩)
文章图片

二、设置 key 有效期
官网:http://redis.cn/commands/expire.html
使用:设置key的过期时间,超过时间后,将会自动删除该key
//设置 key1 的 value 的为 aaa,过期时间为 20 秒
set key1 aaa ex 20
我们知道在 servlet 中,再次操作会触发 cookie 重新计时,那么在 Redis 中设置 key 有效期之后,重新查询命中 key之后,key 是否会重新计时呢?
面试的时候,很多人都回答错了。查询其实不会触发 key 的有效期重新计时,不会延长时间
但是对已经有过期时间的key执行EXPIRE操作,将会更新它的过期时间。有很多应用有这种业务场景,例如记录会话的session。
除了设置有效期外,关于 key 还有以下两个不太常用的功能:
  • 倒计时失效,且 redis 不能延长倒计时时间
  • 定时失效
总结:过期时间是根据业务逻辑来设置的,究竟是一天就过期,还是三天,亦或是一周,这些应该由程序员判断,并在代码层面实现、保证
三、内存回收策略
官网:http://redis.cn/topics/lru-cache.html
当Redis被当做缓存来使用,当你新增数据时,让它自动地回收旧数据是件很方便的事情。这个行为在开发者社区非常有名,因为它是流行的memcached系统的默认行为。
LRU 是Redis唯一支持的回收方法。Redis的maxmemory指令用于将可用内存限制成一个固定大小,还包括了Redis使用的LRU算法,这个实际上只是近似的LRU。
maxmemory配置指令用于配置Redis存储数据时指定限制的内存大小,例如为了配置内存限制为100mb,以下的指令可以放在redis.conf文件中
maxmemory 100mb

当指定的内存限制大小达到时,需要选择不同的行为,也就是策略,回收策略可以设置多种,其中最常用的两个参数如下:
  • allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放
  • volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放
回收进程如何工作?
理解回收进程如何工作是非常重要的:
  • 一个客户端运行了新的命令,添加了新的数据。
  • Redis检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。
  • 一个新的命令被执行,等等。
  • 所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。
如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。
四、有关缓存的常见面试题 首先要明确:引入缓存的目的,就是为了减少大量并发到达数据库,减小数据库压力!
Redis|Redis——Redis用作缓存(内存回收/穿透/击穿/雪崩)
文章图片

1、缓存穿透
用一句话描述缓存穿透就是:大量客户端请求不存在的数据!!!
假设是一个买书的网站(比如当当图书),有很多人偏偏查询有没有日用百货。首先你的 Redis 缓存中肯定是没有日用百货的数据的,既然 Redis 缓存中没有,如果任由这些查询打到数据库,数据库就会进行无效的空查询。当有大量的无效查询并发到达数据库(比如黑客控制大量肉鸡恶意查询,当然这种情况很少发生),就会造成数据库拥堵,首先肯定会造成系统响应速度变慢,其次甚至会造成数据库宕机
缓存穿透解决方案
所以我们应该在这样的无效查询到达数据库之前进行过滤,减少数据库的空查询。生产中最常用的解决方案就是在 Redis 中集成布隆过滤器进行请求过滤
布隆过滤器是这样一个思路:首先你网站里有什么商品,把所有商品的名字都拿出来放在一个集合里,用户搜索的时候先拿商品名字和集合中元素比对,如果没找到就说明数据库中一定没有,就没必要走数据库了;如果找到了,就可以直接返回简单信息,或者再去数据库查询具体的详细信息
那么我们会条件反射的发现一个问题——我们的数据量一般很大,存储在磁盘数据库中都需要分库分表,建立集群分而治之,这么大的数据量怎么可能加载到小小的内存呢?布隆过滤器就是解决这个问题——如何用小的空间解决大量的数据匹配过程
我们在前面五种数据结构中学习了二进制位图(bitmap),如果每一个商品都能够用几个二进制位表示的话,整体数据的体积会变的很小。示意图如下:
Redis|Redis——Redis用作缓存(内存回收/穿透/击穿/雪崩)
文章图片

总结:
  • 使用位图–bitmap,只需要很少的空间,即可代表所有的数据库商品字段,成本很低
  • 大概率解决问题,并非百分百解决问题,缓存穿透概率降为不到 1%。核心思想就是拿空间换时间,减少时间复杂度
面试官问:如果使用布隆过滤器还是发生了低概率的缓存穿透,有没有什么优化方式?
  • client端发送的请求穿透了redis,进行数据库查询但是没有查询到结果,可以增加 redis 中的key,value 标记为 null,下次进行同样的查询直接返回 null,查询就相当于被过滤,不会打到数据库
  • 数据库增加了元素,需要进行映射,在位图 bitmap 中标记为 1
  • 如果增加商品,需要完成元素对 bloom 的添加(此时就出现了双写的问题——数据既要添加数据库,又需要添加布隆过滤器,怎么保证这两个操作的原子性?)
2、缓存击穿
用一句换描述缓存击穿就是:少量的 key 过期时,恰好有大量的并发同时请求此数据!!!
不管是设置 key 的有效期,还是通过 LRU 内存自动回收,总会有长期没被访问的冷数据会从内存中剔除,恰好在过期这一时刻,有大量并发同时请求此数据,此时 Redis 缓存中没数据,大量并发就会压到数据库
注意一定是大量并发同时到达,因为如果只有少量请求,并不会给数据库带来严重的压力
Redis|Redis——Redis用作缓存(内存回收/穿透/击穿/雪崩)
文章图片

解决方案:第一个请求线程加锁,后续的请求得不到锁就不能请求数据库。第一个请求把数据 load 到Redis,后续请求直接从 Redis 中获取数据,无需查询数据库
利用 redis 是单进程单实例,所有请求排队请求 redis
1:让第一个请求返回 null 的时候,重新回到队伍末尾,当所有请求都返回错误的时候,第一个请求又回到队伍头部
2:此线程使用 setnx() 加锁,并去数据库找数据;其余线程尝试加锁,但是无法获取锁,保持等待
3:第一个线程从数据库获取数据返回,并将数据加载到 redis
4:其余请求就能够从 redis 获取数据,不用再查询 DB
不过加锁会产生如下问题:
  1. 死锁
    第一个获得锁的线程到达数据库的时候,出现意外情况,此线程挂掉了,后续的请求就无法获得锁,出现了死锁,后续的请求也都无法获得锁,就会造成服务不可用
    解决方案:出现死锁会造成服务不可用,此时可以在加锁的时候设置锁的过期时间,这样就不会出现死锁
  2. 锁超时
    第一个没挂,但是数据库出现拥堵,锁超时了会释放锁,后续的获得了锁,继续拥堵,造成服务阻塞不可用
    解决方案:使用多线程,一个线程取DB,另一个线程监控是否取回来,如果没有就更新锁时间
技术讨论这个程度就可以了,此时我们可以发现自己手动实现分布式锁是很麻烦的,需要考虑到各种情况会出现的很多的问题。从此处可以引出常用的分布式锁的实现——Zookeeper,就可以理解 Zookeeper 的优美之处
3、缓存雪崩
用一句话描述缓存雪崩就是:大量的 key 同时失效,间接造成大量的访问到达数据库
缓存雪崩和缓存击穿很像:缓存击穿是有一个key过期,恰好大量的请求同时到达,这种情况好像很难很难很难发生;而如果设计不合理的话,缓存穿透是很容易发生的。比如零点的时候一批几百个 key 会被清除,此时每个 key 有二三十个请求,同样会给数据库带来一定的压力,这种情况不像缓存击穿那么极端,是很有出现的
Redis|Redis——Redis用作缓存(内存回收/穿透/击穿/雪崩)
文章图片

解决方案:均匀分布过期时间
对于时点性无关的数据,如果出现缓存雪崩,就说明过期时间有问题,可以设置让过期时间均匀分布
但是有些时点性相关的数据,比如零点就是要把一批数据清空,压上一批新的数据。如果你整个系统都知道,零点的时候会发生数据清除更新,有概率会发生缓存雪崩,可以在前面的业务逻辑代码加判断,进行零点延时(让 util 随机睡个几秒,处理完所有请求),不要把流量放行到数据库
关联文章:
Redis入门–万字长文详解epoll
Redis——详解五种数据结构
Redis——Redis的进阶使用(管道/发布订阅/事务/布隆过滤器)

    推荐阅读