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

    科技2022-07-11  90

    Redis 用作缓存

    文章目录

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

    一、概念

    Redis 既可以做缓存,又可以做数据库。那么 Redis 做缓存与数据库间的区别?

    首先需要明白,在系统中使用缓存并不是为了秀技术,而是为了解决架构存在的问题

    使用 Redis 作为缓存的目的:

    首先Redis 数据是存在于内存中的,内存速度快于磁盘千百倍,所以使用缓存一能够加快请求的响应速度再就是让大量的查询在到达缓存的时候,就能够得到数据,不用再去数据库中查询,减轻大量请求访问数据库的压力

    Redis 做缓存的时候,由于内存空间有限,每个 Redis 实例中只分配部分内存空间,所以 Redis 缓存中存放的并不是全量的数据,而是经常被访问的热数据,热数据是随着用户的请求趋势而发生变化的。换言之,Redis 的数据并不像数据库那样重要,它是随着时间变化,冷数据应该被剔除,让出空间给热数据

    那么 Redis 用作缓存的时候是如何保证淘汰冷数据,腾出空间给热数据呢?一般有如下两种方案:

    1. 设置 key 有效期 2. 内存回收策略:LRU、LFU

    二、设置 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的限制, 则根据设定好的策略进行回收。一个新的命令被执行,等等。所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。

    如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。

    四、有关缓存的常见面试题

    首先要明确:引入缓存的目的,就是为了减少大量并发到达数据库,减小数据库压力!

    1、缓存穿透

    用一句话描述缓存穿透就是:大量客户端请求不存在的数据!!!

    假设是一个买书的网站(比如当当图书),有很多人偏偏查询有没有日用百货。首先你的 Redis 缓存中肯定是没有日用百货的数据的,既然 Redis 缓存中没有,如果任由这些查询打到数据库,数据库就会进行无效的空查询。当有大量的无效查询并发到达数据库(比如黑客控制大量肉鸡恶意查询,当然这种情况很少发生),就会造成数据库拥堵,首先肯定会造成系统响应速度变慢,其次甚至会造成数据库宕机

    缓存穿透解决方案

    所以我们应该在这样的无效查询到达数据库之前进行过滤,减少数据库的空查询。生产中最常用的解决方案就是在 Redis 中集成布隆过滤器进行请求过滤

    布隆过滤器是这样一个思路:首先你网站里有什么商品,把所有商品的名字都拿出来放在一个集合里,用户搜索的时候先拿商品名字和集合中元素比对,如果没找到就说明数据库中一定没有,就没必要走数据库了;如果找到了,就可以直接返回简单信息,或者再去数据库查询具体的详细信息

    那么我们会条件反射的发现一个问题——我们的数据量一般很大,存储在磁盘数据库中都需要分库分表,建立集群分而治之,这么大的数据量怎么可能加载到小小的内存呢?布隆过滤器就是解决这个问题——如何用小的空间解决大量的数据匹配过程

    我们在前面五种数据结构中学习了二进制位图(bitmap),如果每一个商品都能够用几个二进制位表示的话,整体数据的体积会变的很小。示意图如下:

    总结:

    使用位图–bitmap,只需要很少的空间,即可代表所有的数据库商品字段,成本很低大概率解决问题,并非百分百解决问题,缓存穿透概率降为不到 1%。核心思想就是拿空间换时间,减少时间复杂度

    面试官问:如果使用布隆过滤器还是发生了低概率的缓存穿透,有没有什么优化方式?

    client端发送的请求穿透了redis,进行数据库查询但是没有查询到结果,可以增加 redis 中的key,value 标记为 null,下次进行同样的查询直接返回 null,查询就相当于被过滤,不会打到数据库数据库增加了元素,需要进行映射,在位图 bitmap 中标记为 1如果增加商品,需要完成元素对 bloom 的添加(此时就出现了双写的问题——数据既要添加数据库,又需要添加布隆过滤器,怎么保证这两个操作的原子性?)

    2、缓存击穿

    用一句换描述缓存击穿就是:少量的 key 过期时,恰好有大量的并发同时请求此数据!!!

    不管是设置 key 的有效期,还是通过 LRU 内存自动回收,总会有长期没被访问的冷数据会从内存中剔除,恰好在过期这一时刻,有大量并发同时请求此数据,此时 Redis 缓存中没数据,大量并发就会压到数据库

    注意一定是大量并发同时到达,因为如果只有少量请求,并不会给数据库带来严重的压力

    解决方案:第一个请求线程加锁,后续的请求得不到锁就不能请求数据库。第一个请求把数据 load 到Redis,后续请求直接从 Redis 中获取数据,无需查询数据库

    利用 redis 是单进程单实例,所有请求排队请求 redis

    1:让第一个请求返回 null 的时候,重新回到队伍末尾,当所有请求都返回错误的时候,第一个请求又回到队伍头部

    2:此线程使用 setnx() 加锁,并去数据库找数据;其余线程尝试加锁,但是无法获取锁,保持等待

    3:第一个线程从数据库获取数据返回,并将数据加载到 redis

    4:其余请求就能够从 redis 获取数据,不用再查询 DB

    不过加锁会产生如下问题:

    死锁

    第一个获得锁的线程到达数据库的时候,出现意外情况,此线程挂掉了,后续的请求就无法获得锁,出现了死锁,后续的请求也都无法获得锁,就会造成服务不可用

    解决方案:出现死锁会造成服务不可用,此时可以在加锁的时候设置锁的过期时间,这样就不会出现死锁

    锁超时

    第一个没挂,但是数据库出现拥堵,锁超时了会释放锁,后续的获得了锁,继续拥堵,造成服务阻塞不可用

    解决方案:使用多线程,一个线程取DB,另一个线程监控是否取回来,如果没有就更新锁时间

    技术讨论这个程度就可以了,此时我们可以发现自己手动实现分布式锁是很麻烦的,需要考虑到各种情况会出现的很多的问题。从此处可以引出常用的分布式锁的实现——Zookeeper,就可以理解 Zookeeper 的优美之处

    3、缓存雪崩

    用一句话描述缓存雪崩就是:大量的 key 同时失效,间接造成大量的访问到达数据库

    缓存雪崩和缓存击穿很像:缓存击穿是有一个key过期,恰好大量的请求同时到达,这种情况好像很难很难很难发生;而如果设计不合理的话,缓存穿透是很容易发生的。比如零点的时候一批几百个 key 会被清除,此时每个 key 有二三十个请求,同样会给数据库带来一定的压力,这种情况不像缓存击穿那么极端,是很有出现的

    解决方案:均匀分布过期时间

    对于时点性无关的数据,如果出现缓存雪崩,就说明过期时间有问题,可以设置让过期时间均匀分布

    但是有些时点性相关的数据,比如零点就是要把一批数据清空,压上一批新的数据。如果你整个系统都知道,零点的时候会发生数据清除更新,有概率会发生缓存雪崩,可以在前面的业务逻辑代码加判断,进行零点延时(让 util 随机睡个几秒,处理完所有请求),不要把流量放行到数据库


    关联文章:

    Redis入门–万字长文详解epoll

    Redis——详解五种数据结构

    Redis——Redis的进阶使用(管道/发布订阅/事务/布隆过滤器)

    Processed: 0.017, SQL: 8