后端面试(四)分布式缓存

一、分布式缓存Redis面试:

缓存☞将需要频繁访问的数据存放在内存中加快用户访问速度的一种技术。分为进程级缓存和分布式缓存,进程级缓存将数据缓存在服务内部,通过Map,List结构实现存储;分布式缓存将缓存单独存放在分布式系统中,以便于缓存的统一管理和存取;

1、介绍一下Redis:

redis(Remote Dictionary Server远程字典服务),是一款高性能的(key/value)分布式内存数据库,基于内存运行并支持持久化的NoSQL数据库。因为数据都在内存中,所以运行速度快。redis支持丰富的数据类型并且支持事务,事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发送来的命令打断。

2、Redis支持的数据类型:

Reids支持共八种数据结构,String,Hash,List,Set,ZSet(有序集合),Bitmap(位图),HyperLogLog(超级日志)和Geospatial(地理空间),前五种比较常用:

  • **string:**redis 中字符串 value 最大可为512M。可以用来做一些计数功能的缓存(也是实际工作中最常见的)。
  • **list:**简单的字符串列表,按照插入顺序排序,可以添加一个元素到列表的头部(左边)或者尾部(右边),其底层实现是一个链表。可以实现一个简单消息队列功能,做基于redis的分页功能等。
  • **set:**是一个字符串类型的无序集合。可以用来进行全局去重等。
  • sorted set:是一个字符串类型的有序集合,给每一个元素一个固定的分数score来保持顺序。可以用来做排行榜应用或者进行范围查找等。
  • **hash:**键值对集合,是一个字符串类型的 Key和 Value 的映射表,也就是说其存储的Value是一个键值对(Key- Value)。可以用来存放一些具有特定结构的信息。
3、Redis为什么执行速度这么快:

首先redis是单线程的,redis的单线程是指网络请求模块使用了一个线程,所以不需考虑并发安全性。但是对于需要依赖多个操作的复合操作来说,还是需要锁的,而且有可能是分布式锁。

那么单线程的redis为什么执行速度如此之快?

  • 基于内存实现,完全内存计算
  • 单线程操作,避免了线程上下文切换操作
  • 多路I/O复用的线程模型,实现了一个线程监控多个IO流,及时响应请求(I/O多路复用就是通过一种机制一个进程可以监视多个描述符
  • redis对外部的依赖比较少,属于轻量级内存数据库
4、Redis集群数据复制原理(主从复制):

Redis在集群模式下,可以实现在主数据库(Master)中的数据更新之后,自动将更新的数据同步到从数据库,一个主数据库拥有多个从数据库

在这里插入图片描述

(1)一个从数据库在启动后,会向主数据库发送SYNC命令。
(2)主数据库在接收到SYNC命令后会开始在后台保存快照(即RDB持久化的过程),并将保存快照期间接收到的命令缓存起来。在该持久化过程中会生成一个.rdb快照文件。
(3)在主数据库快照执行完成后,Redis会将快照文件和所有缓存的命令以.rdb快照文件的形式发送给从数据库。
(4)从数据库收到主数据库的.rdb快照文件后,载入该快照文件到本地。
(5)从数据库执行载入后的.rdb快照文件,将数据写入内存中。以上过程被称为复制初始化。
(6)在复制初始化结束后,主数据库在每次收到写命令时都会将命令同步给从数据库,从而保证主从数据库的数据一致。

5、Redis的持久化(重点):

redis的持久化方式有两种,即RDB和AOF的方式,分别介绍如下:

RDB(快照方式 snapshotting)(全量持久化):

将当前内存中的数据集快照写入磁盘,实现数据的持久化,恢复时可以将快照重新载入内存。

触发方式:
  • **自动触发:**在配置文件中,可以配置执行了多少次save就自动触发自动持久化。
  • **手动触发:**通过bgsave命令,在后台异步进行生成快照的操作,同时还可以响应客户端的请求。通过redis进程fork操作创建子进程,生成的快照由子进程负责,客户端请求只会在fork阶段被阻塞。
快照恢复:

将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务,redis会自动加载快照文件数据到内存。但是,redis 服务器在载入 RDB 文件期间,会一直处于阻塞状态,直到载入工作完成为止。

优缺点分析:
  • RDB持久化方式存在数据的丢失,因为其没有办法实现实时持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高,会影响系统性能。自动触发也存在丢失部分数据的情况。
  • 在恢复大数据集时候,RDB方式相对于AOF要快。
AOF(append-only-file)(增量持久化):

在 redis配置文件的 APPEND ONLY MODE 中,可以设置AOF持久化。通过记录redis服务器所执行的写命令来记录数据库状态。恢复时可以将AOF文件载入内存,并且可以通过redis-check-aof --fix 进行修复AOF文件。

AOF日志重写:
  • AOF文件会随着服务器运行的时间越来越大,可以通过AOF重写来控制AOF文件的大小。
  • AOF重写会首先读取数据库中现有的键值对状态,然后根据类型使用一条命令来替代前面对键值对操作的多条命令。
  • 使用命令 bgrewriteaof 来实现AOF重写
AOF重写缓存区:

redis 是单线程工作,当AOF文件较大时重写时间会比较长,在重写 AOF 期间,redis将长时间无法处理客户端请求。为了解决这个问题,可以将 AOF 重写程序放到子进程中执行,好处如下:

  • 子进程进行 AOF 重写期间,服务器进程(父进程)可以继续处理其它客户端请求。
  • 子进程带有父进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性。
子进程中AOF重写导致的问题:
  • 子进程在进行 AOF 重写期间,服务器进程依然可以处理其它客户端请求,这就会导致数据库状态已经发生了改变,使得当前数据库数据状态和重写后的 AOF 文件中的数据不一致。
  • 也就是出现了AOF文件和数据库中数据不一致的问题。
数据状态不一致解决办法:
  • redis 服务器设置了一个 AOF 重写缓冲区。这个缓冲区在创建子进程后开始使用,当redis服务器执行一个客户端的写请求命令,之后将这个写命令也发送到 AOF 重写缓冲区。
  • 当子进程完成 AOF 日志重写之后,给父进程发送信号,父进程接收此信号后,将 AOF 重写缓冲区的内容写到新的 AOF 文件中,保持数据的一致性。
优缺点分析:
  • AOF文件可以做到秒级持久化,使用追加写的方式来写入,可读性强并且可以使用命令进行文件修复。
  • 相比于RDB文件,同样数据下AOF文件体积要大。在redis负载较高时,秒级更新AOF文件会影响性能
6、持久化策略选择:
  • AOF更安全,可将数据及时同步到文件中,但需要较多的磁盘IO,AOF文件尺寸较大,文件内容恢复相对较慢也更加完整。
  • RDB持久化,安全性较差,它是正常时期数据备份及 master-slave数据同步的最佳手段,文件尺寸较小并且恢复速度较快。
7、缓存预热:

缓存预热指在用户请求数据前先将数据加载到缓存系统中,用户查询事先被预热的缓存数据,以提高系统查询效率。缓存预热一般有系统启动加载、定时加载等方式。

8、缓存更新:

缓存更新指在数据发生变化后及时将变化后的数据更新到缓存中。常见的缓存更新策略有以下4种。
◎ 定时更新:定时将底层数据库内的数据更新到缓存中,该方法比较简单,适合需要缓存的数据量不是很大的应用场景。
◎ 过期更新:定时将缓存中过期的数据更新为最新数据并更新缓存的过期时间。
◎ 写请求更新:在用户有写请求时先写数据库同时更新缓存,这适用于用户对缓存数据和数据库的数据有实时强一致性要求的情况。
◎ 读请求更新:在用户有读请求时,先判断该请求数据的缓存是否存在或过期,如果不存在或已过期,则进行底层数据库查询并将查询结果更新到缓存中,同时将查询结果返回给用户。

9、 缓存淘汰策略:

在缓存数据过多时需要使用某种淘汰算法决定淘汰哪些数据。常用的淘汰算法有以下几种。
◎ FIFO(First In First Out,先进先出):判断被存储的时间,离目前最远的数据优先被淘汰。
◎ LRU(Least Recently Used,最近最少使用):判断缓存最近被使用的时间,距离当前时间最远的数据优先被淘汰。
◎ LFU(Least Frequently Used,最不经常使用):在一段时间内,被使用次数最少的缓存优先被淘汰。

10、缓存雪崩:

缓存雪崩指在同一时刻由于大量缓存失效,导致大量原本应该访问缓存的请求都去查询数据库,而对数据库的CPU和内存造成巨大压力,严重的话会导致数据库宕机,从而形成一系列连锁反应,使整个系统崩溃。一般有以下3种处理方法。
◎ 请求加锁:对于并发量不是很多的应用,使用请求加锁排队的方案防止过多请求数据库。
◎ 失效更新:为每一个缓存数据都增加过期标记来记录缓存数据是否失效,如果缓存标记失效,则更新数据缓存。
◎ 设置不同的失效时间:为不同的数据设置不同的缓存失效时间,防止在同一时刻有大量的数据失效。

11、 缓存穿透:

缓存穿透指由于缓存系统故障或者用户频繁查询系统中不存在(在系统中不存在,在自然数据库和缓存中都不存在)的数据,而这时请求穿过缓存不断被发送到数据库,导致数据库过载,进而引发一连串并发问题。
比如用户发起一个userName为zhangsan的请求,而在系统中并没有名为zhangsan的用户,这样就导致每次查询时在缓存中都找不到该数据,然后去数据库中再查询一遍。由于zhangsan用户本身在系统中不存在,自然返回空,导致请求穿过缓存频繁查询数据库,在用户频繁发送该请求时将导致数据库系统负载增大,从而可能引发其他问题。常用的解决缓存穿透问题的方法有布隆过滤器和cache null策略。
**◎ 布隆过滤器:**指将所有可能存在的数据都映射到一个足够大的Bitmap中,在用户发起请求时首先经过布隆过滤器的拦截,一个一定不存在的数据会被这个布隆过滤器拦截,从而避免对底层存储系统带来查询上的压力。
**◎ cache null策略:**指如果一个查询返回的结果为null(可能是数据不存在,也可能是系统故障),我们仍然缓存这个null结果,但它的过期时间会很短,通常不超过5分钟;在用户再次请求该数据时直接返回null,而不会继续访问数据库,从而有效保障数据库的安全。其实cache null策略的核心原理是:在缓存中记录一个短暂的(数据过期时间内)数据在系统中是否存在的状态,如果不存在,则直接返回null,不再查询数据库,从而避免缓存穿透到数据库上。

12、缓存降级:

缓存降级指由于访问量剧增导致服务出现问题(如响应时间慢或不响应)时,优先保障核心业务的运行,减少或关闭非核心业务对资源的使用。常见的服务降级策略如下。
◎ 写降级:在写请求增大时,可以只进行Cache的更新,然后将数据异步更新到数据库中,保证最终一致性即可,即将写请求从数据库降级为Cache。
◎ 读降级:在数据库服务负载过高或数据库系统故障时,可以只对Cache进行读取并将结果返回给用户,在数据库服务正常后再去查询数据库,即将读请求从数据库降级为Cache。这种方式适用于对数据实时性要求不高的场景,保障了在系统发生故障的情况下用户依然能够访问到数据,只是访问到的数据相对有延迟。

13、RedisObject:

redis中基于双端链表、简单动态字符串(sds)、字典、跳跃表、整数集合、压缩列表、快速列表等等数据结构实现了一个对象系统(RedisObject),并且实现了5种不同的对象,每种对象都使用了至少一种前面的数据结构,优化对象在不同场合下的使用效率。

功能:

  • 为5种不同的对象类型提供同一的表示形式。
  • 为不同的对象适用于不同的场景,支持同一种对象类型采用多种的数据结构方式。
  • 支持引用计数,实现对象共享机制。
  • 记录对象的访问时间,便于删除对象。

结构:

typedef struct redisObject {
    // 类型
    unsigned type:4;
    // 编码
    unsigned encoding:4;
    // 对象最后一次被访问的时间
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
    // 引用计数
    int refcount;
    // 指向实际值的指针
    void *ptr;
} robj;

type

《Redis设计与实现第二版》61页

encoding:底层编码方式:

《Redis设计与实现第二版》62页

《Redis设计与实现第二版》63页

OBJ内存大小:16KB

14、SDS动态字符串:

Redis 是用 C 语言写的,但是对于Redis的字符串,却不是 C 语言中的字符串(即以空字符’\0’结尾的字符数组),它是自己构建了一种名为 **简单动态字符串(simple dynamic string,SDS)**的抽象类型,并将 SDS 作为 Redis的默认字符串表示。

struct sdshdr{
     //记录buf数组中已使用字节的数量
     //等于 SDS 保存字符串的长度
     int len;
     //记录 buf 数组中未使用字节的数量
     int free  ;
     //字节数组,用于保存字符串
     char buf[];
}

图示(图片来源于《Redis设计与实现》)

img

对比C字符串:img

15、Skiplist跳表:

skiplist查找效率很高,堪比优化过的二叉平衡树(红黑树),且比平衡树的实现简单,查找单个key,skiplist和平衡树的时间复杂度都为O(log n)。平衡树的插入和删除操作可能引发树的旋转调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。

Skiplist多层链表的想法的启发而设计出来的,实际上,按照上面生成链表的方式,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似于一个二分查找,使得查找的时间复杂度可以降低到O(log n)

16、skiplist与平衡树、哈希表的比较
  • skiplist 和 各种平衡树(如AVL、红黑树等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做单个key的查找,不适宜做范围查找。
  • 平衡树的插入 和 删除操作可能引发树的旋转调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。
  • 查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;而哈希表在保持较低的哈希值冲突概率的前提下,查找时间复杂度接近O(1),性能更高一些。
  • 从内存占用上来说,skiplist比平衡树更灵活一些。平衡树一般每个节点包含2个指针,而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于一个概率参数p。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。
17、缓存过期策略:
  • 定时删除
    • 含义:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除
    • 优点:保证内存被尽快释放
    • 缺点:
      • 若过期key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,CPU不能把所有的时间用来做要紧的事儿,还需要去花时间删除这些key
      • 定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能影响严重
      • 没人用
  • 惰性删除
    • 含义:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。
    • 优点:删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的key了)
    • 缺点:若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄露(无用的垃圾占用了大量的内存)
  • 定期删除
    • 含义:每隔一段时间执行一次删除(在redis.conf配置文件设置hz,1s刷新的频率)过期key操作
    • 优点:
      • 通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用–处理"定时删除"的缺点
      • 定期删除过期key–处理"惰性删除"的缺点
    • 缺点
      • 在内存友好方面,不如"定时删除"
      • 在CPU时间友好方面,不如"惰性删除"
    • 难点
      • 合理设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)(这个要根据服务器运行情况来定了)

看完上面三种策略后可以得出以下结论:
定时删除和定期删除为主动删除:Redis会定期主动淘汰一批已过去的key

惰性删除为被动删除:用到的时候才会去检验key是不是已过期,过期就删除

惰性删除为redis服务器内置策略

定期删除可以通过:

  • 第一、配置redis.conf 的hz选项,默认为10 (即1秒执行10次,100ms一次,值越大说明刷新频率越快,最Redis性能损耗也越大)
  • 第二、配置redis.conf的maxmemory最大值,当已用内存超过maxmemory限定时,就会触发主动清理策略

Redis采用的过期策略

惰性删除+定期删除

  • 惰性删除流程
    • 在进行get或setnx等操作时,先检查key是否过期,
    • 若过期,删除key,然后执行相应操作;
    • 若没过期,直接执行相应操作
  • 定期删除流程(简单而言,对指定个数个库的每一个库随机删除小于等于指定个数个过期key)
    • 遍历每个数据库(就是redis.conf中配置的"database"数量,默认为16)
      • 检查当前库中的指定个数个key(默认是每个库检查20个key,注意相当于该循环执行20次,循环体时下边的描述)
        • 如果当前库中没有一个key设置了过期时间,直接执行下一个库的遍历
        • 随机获取一个设置了过期时间的key,检查该key是否过期,如果过期,删除key
          是否过期,
    • 若过期,删除key,然后执行相应操作;
    • 若没过期,直接执行相应操作
  • 定期删除流程(简单而言,对指定个数个库的每一个库随机删除小于等于指定个数个过期key)
    • 遍历每个数据库(就是redis.conf中配置的"database"数量,默认为16)
      • 检查当前库中的指定个数个key(默认是每个库检查20个key,注意相当于该循环执行20次,循环体时下边的描述)
        • 如果当前库中没有一个key设置了过期时间,直接执行下一个库的遍历
        • 随机获取一个设置了过期时间的key,检查该key是否过期,如果过期,删除key
        • 判断定期删除操作是否已经达到指定时长,若已经达到,直接退出定期删除。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值