Redis面试必会

Redis提高

什么是缓存

  • 缓存原指CPU上的一种告诉存储器,它先于内存与CPU交换数据,访问速度很快

  • Cache Aside Pattern(旁路缓存),也是最常用的缓存读写模式

  • 读的时候,先读缓存,如果没有缓存,就读数据库,然后取出数据后放入缓存,同时返回响应
    读写缓存

  • 更新缓存:更新的时候,先更新数据库,在删除缓存
    更新缓存

      为什么是删除缓存,而不是更新呢?
      因为缓存的值是一个结构:hash、list,更新数据需要遍历,效率低
    

    Redis底层数据结构

    Redis中存在“数据库”的概念,该结构由redis.h中的redisDb定义
    当Redis服务器初始化时,会预先分配16个数据库
    所有数据库保存到结构redisServer的一个成员redisServer.db数组中

  • RedisDB结构体源码:

typedef struct redisDb {
	int id;			//id是数据库序号,为0-15(默认Redis有16个数据库)
	long ave_ttl;	//存储数据库对象的平均ttl,用于统计
	dict *dict;		//存储数据库所有的key-value
	dict *expires;  //存储key的过期时间
	dict *blocking_keys; //blpop存储阻塞key和客服端对象
	dict *ready_keys;  //阻塞后push响应阻塞客服端,存储阻塞后push的key和客服端对象
	dict *watched_keys; //存储watch监控的key和客服端对象
} redisDb
  • Redis字符串对象(重点
struct sdshdr{
  //记录buf数组中已使用字节的数量
  int len;
  //记录 buf 数组中未使用字节的数量
  int free;
  //字符数组,用于保存字符串
  char buf[];
}

buf[]的长度 = len + free + 1

	SDS的优势:
		1.SDS在C字符串的基础上加入了free和len字段,获取字符串长度:SDS是O(1),C字符串是O(N)
		2.SDS记录了长度,在可能造成缓冲区溢出时会自动分配内存,杜绝了缓冲区溢出
		3.可以存取二进制数据,以字符串长度len作为结束标识,c则是以 \0 结束标识
		
		使用场景:SDS主要用在存储字符串和整形数据、存储key、AOF缓冲区和用户输入缓冲
  • 跳跃表、压缩列表、字典中有个概念叫 rehash(扩容)

    1. 初次申请默认容量为4个dictEntry,非初次申请为当前hash表容量的一倍。
    2. rehashidx=0表示要进行rehash操作。
    3. 新增加的数据在新的hash表h[1]
    4. 修改、删除、查询在老hash表h[0]、新hash表h[1]中(rehash中)
    5. 将老的hash表h[0]的数据重新计算索引值后全部迁移到新的hash表h[1]中,这个过程称为rehash。
      渐进式rehash
      当数据量巨大时rehash的过程是非常缓慢的,所以需要进行优化。
      服务器忙,则只对一个节点进行rehash
      服务器闲,可批量rehash
  • 快速列表

       快速列表(quicklist)是Redis底层重要的数据结构,是列表的底层实现。
       quicklist是一个双向链表,链表中的每个节点时一个ziplist结构。quicklist中的每个
       节点ziplist都能够存储多个数据元素。
    
      数据压缩:
      	quicklist每个节点的实际存储结构为ziplist,这种结构的优势在于节省存储空间。为了进
      	一步降低ziplist的存储空间,还可以对ziplist进行压缩,Redis采用的压缩算法是LZF。其
      	基本思想为:数据与前面重复的记录重复位置及其长度,不重复的记录原始数据
      	压缩过后的数据还可以分成多个片段,每个片段有两个部分:解释字段和数据字段
      
      quicklist结构体如下:
    
typedef struct quicklistLZF {
	unsigned int sz; // LZF压缩后占用的字节数
	char compressed[]; // 柔性数组,指向数据部分
} quicklistLZF;

Redis的缓存过期和淘汰策略

Redis的数据删除有定时删除、惰性删除和主动删除三种方式。
Redis目前采用惰性删除+主动删除的方式。

定时删除
在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除
操作。
需要创建定时器,而且消耗CPU,一般不推荐使用。

惰性删除
在key被访问时如果发现它已经失效,那么就删除它。

主动删除
在redis.conf文件中可以配置主动删除策略,默认是no-enviction(不删除)

LRU
LRU (Least recently used) 最近最少使用,算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
最常见的实现是使用一个链表保存缓存数据,详细算法实现如下:

  1. 新数据插入到链表头部;
  2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
  3. 当链表满的时候,将链表尾部的数据丢弃。
  4. 在Java中可以使用LinkHashMap(哈希链表)去实现LRU
  • Redis的LRU 数据淘汰机制

     LRU 数据淘汰机制是这样的:在数据集中随机挑选几个键值对,取出其中 lru 最大的键值对淘汰。		
     不可能遍历key 用当前时间-最近访问 越大 说明 访问间隔时间越长
     
     volatile-lru
     	从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
     allkeys-lru
     	从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
     
     LFU (Least frequently used) 最不经常使用,如果一个数据在最近一段时间内使用次数很少,
     那么在将来一段时间内被使用的可能性也很小。
    
     random 随机
     volatile-random
     	从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
     allkeys-random
     	从数据集(server.db[i].dict)中任意选择数据淘汰
     	
     ttl
     volatile-ttl
     	从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
     	redis 数据集数据结构中保存了键值对过期时间的表,即 redisDb.expires。
     TTL 数据淘汰机制:从过期时间的表中随机挑选几个键值对,取出其中 ttl 最小的键值对淘汰。
     
     noenviction
     	禁止驱逐数据,不删除 默认
    

缓存淘汰策略的选择

  • allkeys-lru : 在不确定时一般采用策略。 冷热数据交换
  • volatile-lru : 比allkeys-lru性能差 存 :过期时间
  • allkeys-random : 希望请求符合平均分布(每个元素以相同的概率被访问)
  • 自己控制:volatile-ttl 缓存穿透

Redis及其IO多路复用的模型与选择

Redis客户端与服务器交互采用序列化协议(RESP)。
select,poll,epoll、kqueue都是IO多路复用的机制。

  • select
    select 函数监视的文件描述符分3类,分别是:

      - writefds 	
      - readfds 	
      - exceptfds
    

    调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。
    当select函数返回后,可以 通过遍历fd列表,来找到就绪的描述符

    优点:select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。
    缺点:单个进程打开的文件描述是有一定限制的,它由FD_SETSIZE设置,默认值是1024,采用数组存储。
    另外在检查数组中是否有文件描述需要读写时,采用的是线性扫描的方法,即不管这些socket是不是活跃的,都轮询一遍,所以效率比较低。

  • poll
    poll使用一个 pollfd的指针实现,pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式。

    优点:采样链表的形式存储,它监听的描述符数量没有限制,可以超过select默认限制的1024
    缺点:另外在检查链表中是否有文件描述需要读写时,采用的是线性扫描的方法,即不管这些socket是不是活跃的,都轮询一遍,所以效率比较低。

  • epoll

     优点:
     epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,举个例子,在1GB内存的机
     器上大约是10万左右
     效率提升, epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,因此在实际
     的网络环境中, epoll的效率就会远远高于select和poll 。
     epoll使用了共享内存,不用做内存拷贝
    

    Redis持久化

  • RDB,是redis默认的存储方式,RDB是通过快照(snapshotting)完成的。

  • 出发快照的方式
    1.符合自定义配置的快照规则
    2.执行save或者bgsave命令
    3.执行flushall命令
    4.执行主从复制(第一次)

RDB执行流程
在这里插入图片描述

1.Redis父进程首先判断:当前是否在执行save,或者bgsave/bgrewriteaof(aof文件重写命令)的子进程,如果在执行则bgsave命令直接返回。

2.父进程执行fork(调用OS函数复制主进程)操作创建子进程,这个过程中父进程是阻塞的,Redis不能执行来自客服端的任何命令。

3.父进程fork后,bgsave命令返回“Background saving started”信息并不再阻塞父进程,并可以响应其他命令。

4.子进程创建RDB文件,根据父进程内存块照生成临时快照文件,完成后对原有文件进行原子替换。(RDB始终完整)。

5.子进程发送信号给父进程表示完成,父进程更新统计信息。

6.父进程fork子进程后,继续工作。

RDB优缺点

优点
RDB是二进制压缩文件,占用空间小,便于传输(传给slaver)
主进程fork子进程,可以最大化Redis性能,主进程不能太大,复制过程中主进程阻塞
缺点
不保证数据完整性

AOF
AOF是Redis的另一种持久化方式。Redis默认情况下不开启,开启AOF持久化后
Redis将所有对数据库进行过写入的命令及其参数(RESP)记录到AOF文件,以此达到记录数据库状态的目的
AOF会记录过程,RDB只管结果

  • AOF原理

     AOF中存储的是redis的命令,同步命令到AOF文件的整个过程可以分为三个阶段:
     
     命令传播:redis将执行完的命令、命令的参数、命令的参数个数等信息发送到AOF程序中
     缓存追加:AOF根据接收到的命令数据,将命令数据转换为网络通信协议的格式,然后将协议
     		 内容追加到AOF缓存中
     文件写入和保存:AOF缓存中的内容被写入到AOF文件末尾,如果设定的AOF保存条件满足的话,
                   fsync函数或者fdatasync函数会被调用,将写入的内容真正的保存到磁盘中
    
    
     命令传播:
     		当一个Redis客服端要执行命令时,它通过网络连接,将协议文本发送给Redis服务器。
     		服务器在接到客服端的请求后,他会根据协议的文本内容,选择适当的命令函数,并将
     		各个参数从字符串文本转换为Redis字符串对象(StringObject)。每当命令执行成功
     		后,命令参数都会被传播到AOF程序。
     	
     缓存追加:
     		当命令被传播到AOF程序后,程序会根据命令参数以及命令的参数,将命令从字符串对象
     		转换回原来的协议文本。协议文本生成之后,它会被追加到redis.h/redisServer结构
     		的aof_buf末尾
     		redisServer结构维持着Redis服务器的状态,aof_buf域则保持这所有等待写入到AOF
     		文件的协议文本(RESP)。
     文件的写入和保存:
     		每当服务器常规任务函数被执行、或事件处理器被执行时,aof.c/flushAppendOnlyFile
     		函数都会被调用,这个函数执行以下两个工作:
     		write:根据条件,将aof_buf中的缓存写入到AOF文件
     		save:根据条件,调用fsync或fdatasync函数,将AOF文件保存在磁盘中
    

AOF保存模式
Redis目前支持三种AOF保存模式:
AOF_FSYNC_NO: 不保存
AOF_FSYNC_EVERYSEC: 每一秒中保存一次(默认)
AOF_FSYNC_ALWAYS: 没执行一个命令保存一次。(不推荐)

  • 不保存

     这种模式下,每次调用flushAppendOnlyFile函数,write都会被执行,但是save会被略过。
     在这种模式下,save只会在以下任意一种情况中被执行:
     1.redis被关闭
     2.AOF功能被关闭
     3.系统的写缓存被刷新(可能是缓存已经被写满,或者定期保存操作被执行)
     这三种情况下的save操作都会引起redis主进程阻塞。
    
  • 每一秒种保存一次
    在这种模式下,save原则上每隔一秒钟就回执行一次,因为save操作是由后台子线程fork调用的,所以它不会引起服务器主进程阻塞

  • 每执行一个命令保存一次

     在这种模式下,每次执行完一个命令后,write和save都会被执行。
     另外,因为save是由Redis主进程执行的,所以在save执行期间,主进程会被阻塞,不能接收
     命令请求
    

    AOF保存模式对性能和安全性的影响
    在这里插入图片描述

AOF重写、触发方式、混合持久化
AOF记录数据的变化过程越来越大,需要重写“瘦身”
Redis可以在AOF体积变得过大时,自动在后台(fork子进程)对AOF进行重写
在这里插入图片描述

  • Redis不希望AOF重写造成服务器无法处理请求,所以Redis决定将AOF重写程序放到后台子进程里执行,这样的最大好处是:

     1.子进程进行AOF重写期间,主进程可以继续处理命令请求。
     2.子进程带有主进程的数据副本,使用子进程而不是线程,可以避免在锁的情况下,保证数据的安全性
    
  • 不过,使用子进程需要解决一个问题:

  • 因为子进程在进行AOF重写期间,主进程还需要继续处理命令,而新的命令可能对现有数据进行修改,这会让当前数据库的数据和重写后的AOF文件中的数据不一致。

  • 为了解决这个问题,Redis增加了一个AOF重写缓存,这个缓存在fork出子进程后开始启用,Redis主进程在接到新的写命令后,除了会将这个写命令的协议内容追加到现有的AOF文件之外,还会追加到这个缓存中。
    在这里插入图片描述

重写过程分析(整个重写操作是绝对安全的):

	Redis在创建新AOF文件的过程中,会继续将命令追加到现有AOF文件里面,即便重写过程中发生
	停机,现有的AOF文件也不会丢失。而一旦新AOF文件创建完毕,Redis就会从旧AOF文件切换到
	新AOF文件,并开始对新AOF文件进行追加操作。

	当子进程在执行AOF重写时,主进程需要执行以下三个工作:
	1.处理命令请求。
	2.将写命令追加到现有AOF文件中。
	3.将写命令追加到AOF缓存中。

这样一来,现有的AOF功能会继续执行,即使在AOF重写期间发生停机,也不会有任何数据丢失。
所有对数据库进行修改的命令都会被记录到AOF重写缓存中。

当子进程完成AOF重写后,它会向父进程发送一个完成信号,父进程在接到完成信号后,会调用一个信号
处理函数,并完成以下工作:	
	将AOF重写缓存中的内容全部写入到新AOF文件中。
	对新AOF文件进行改名,覆盖原有的AOF文件。
	
这个信号处理函数执行完毕之后,主进程就可以继续像往常一样接收命令请求了。在整个AOF后台
重写的过程中,只有最后的写入缓存和文件改名会造成主进程阻塞,在其他时候,AOF后台重写都
不会对主进程造成阻塞,这将AOF重写性能造成的影响降到了最低。

以上就是AOF后台重写,也即是BGERWRITEAOF命令(AOF重写)的工作原理。

在这里插入图片描述
混合持久化

	RDB和AOF各有优缺点,Redis 4.0 开始支持 rdb 和 aof 的混合持久化。如果把混合持久化打开,
	aof rewrite 的时候就直接把 rdb 的内容写到 aof 文件开头。
	RDB的头+AOF的身体---->appendonly.aof
	开启混合持久化
	在redis.conf中: aof-use-rdb-preamble yes 
  • 在加载时,首先会识别AOF文件是否以 REDIS字符串开头,如果是就按RDB格式加载,加载完RDB后继续按AOF格式加载剩余部分。

     因为AOF文件里面包含了重建数据库状态所需的所有写命令,所以服务器只要读入并重新执
     行一遍AOF
     文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态
     Redis读取AOF文件并还原数据库状态的详细步骤如下:
     
     	1、创建一个不带网络连接的伪客户端(fake client):因为Redis的命令只能在客户端上
     	下文中执行,而载入AOF文件时所使用的命令直接来源于AOF文件而不是网络连接,所以服务器
     	使用了一个没有网络连接的伪客户端来执行AOF文件保存的写命令,伪客户端执行命令的效果和
     	带网络连接的客户端执行命令的效果完全一样
     	2、从AOF文件中分析并读取出一条写命令
     	3、使用伪客户端执行被读出的写命令
     	4、一直执行步骤2和步骤3,直到AOF文件中的所有写命令都被处理完毕为止
    

当完成以上步骤之后,AOF文件所保存的数据库状态就会被完整地还原出来,整个过程如下图所示:

在这里插入图片描述

RDB与AOF对比

  1. RDB存某个时刻的数据快照,采用二进制压缩存储,AOF存操作命令,采用文本存储(混合)
  2. RDB性能高,AOF性能较低
  3. RDB在配置出发状态会丢失最后一次快照以后更改的所有数据,AOF设置为每秒保存一次,则最多丢2秒数据
  4. Redis以主服务器模式运行,RDB不会保存过期的键值对数据,Redis以从服务器运行,RDB会保存过期键值对,当主服务器向从服务器同步时,再清空过期键值对。
  5. AOF写入文件时,对过期的key会追加一条del命令,当执行AOF重写时,会忽略过期key和del命令

应用场景

  • 内存数据库 rdb+aof 数据不容易丢
  • 缓存服务器 rdb 性能高
  • 不建议 只使用 aof (性能差)
  • 在数据还原时
  • 有rdb+aof 则还原aof,因为RDB会造成文件的丢失,AOF相对数据要完整。
  • 只有rdb,则还原rdb
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一些常见的Redis面试题目及其答案: 1. Redis是什么?它的主要用途是什么? - Redis是一种开源的高性能键值存储系统,它可以用作数据库、缓存和消息代理。它被广泛用于构建实时应用、数据分析、高速队列等场景。 2. Redis与其他数据库的主要区别是什么? - Redis是基于内存的数据库,而传统的关系型数据库如MySQL、Oracle等是基于磁盘的。这使得Redis具有高速读写能力和低延迟。此外,Redis支持丰富的数据结构和强大的数据处理功能,如列表、哈希表、有序集合等。 3. Redis的数据结构有哪些? - Redis支持字符串、列表、哈希表、集合和有序集合等数据结构。 4. Redis的持久化机制有哪些? - Redis提供了两种持久化机制:RDB(Redis Database)和AOF(Append Only File)。 - RDB是通过将内存中的数据快照保存到磁盘上的二进制文件中来实现持久化。 - AOF则是通过将每个写操作追加到文件末尾来记录所有的写操作,以便在重启时重新执行这些写操作。 5. Redis如何处理并发访问? - Redis使用单线程的事件循环模型来处理并发访问。它通过非阻塞的I/O和事件通知机制来实现高并发和低延迟。 6. Redis的主从复制工作原理是什么? - Redis的主从复制是通过将主节点的数据异步复制到从节点来实现的。主节点将写操作记录在内存中,并将这些写操作发送给所有连接的从节点。从节点接收到写操作后,会将其应用到自己的数据集上。 这些问题只是Redis面试中的一小部分,希望能对你有所帮助。如果你需要更多问题或者深入了解,请告诉我。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值