面试--redis

什么是redis?

Redis 是一个基于内存的高性能key-value数据库。

Memcache与Redis的区别都有哪些?

1)存储方式

Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。

Redis可以把数据保存在硬盘上,用来保证数据的持久性。

2)数据类型

Memcache的数据类型比较简单。

Redis有复杂的数据类型。

3)使用底层模型不同

它们之间底层的实现方式 以及与客户端之间通信的应用协议不一样。

4)value大小

redis最大可以达到1GB,而memcache只有1MB

Redis有哪些数据结构?

字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。

更进一步,redis还有数据结构HyperLogLog、Geo、Pub/Sub、BloomFilter,RedisSearch,Redis-ML等

为什么redis需要把所有数据放到内存中?

Redis为了达到最快的读写速度,将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以redis具有快速和数据持久化的特征。

如果不将数据放在内存中,磁盘I/O速度为严重影响redis的性能。

如果设置了最大使用的内存,则数据量达到内存限值后,将不能继续插入新值。

Redis 为什么这么快?

Redis 内部做了非常多的性能优化,比较重要的有下面 3 点:Redis 基于内存,内存的访问速度比磁盘快很多;Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用(Redis 线程模式后面会详细介绍到);Redis 内置了多种优化过后的数据类型/结构实现,性能非常高。Redis 通信协议实现简单且解析高效。

怎么用Redis创建分布式锁

先用setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。

注意:如果在setnx之后,并且在执行expire之前,进程意外崩溃或者要重启维护了,这个锁就永远得不到释放了。

为了解决这个问题,set指令有非常复杂的参数,可以同时把setnx和expire合成一条指令

假如Redis里面有1亿个key,其中有10万个key是以固定的前缀开头的,如何将这些全部找出来?

使用keys命令可以扫出指定模式的key列表。

但是,如果这个redis正在给线上的业务提供服务,因为redis的单线程的,所以keys命令会导致线程阻塞一段时间,线上服务会停顿,直到命令执行完毕,服务才能恢复。

为了解决这个问题,可以使用scan命令,scan命令可以无阻塞的提取出指定模式的key列表,会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys命令长。

如果有大量的key需要设置同一时间过期,需要如何处理?

如果大量的key过期时间设置的过于集中,那么当到了过期的那个时间点,redis可能会出现短暂的卡顿。

这种情况下,可以在时间上加一个随机值,使得过期时间分散一些。

Redis如何做持久化的?

bgsave做镜像全量持久化,aof做增量持久化。

因为bgsave会耗费较长时间,达不到实时,在停机的时候会导致大量丢失数据,所以需要aof来配合使用。

如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1秒1次,这个时候最多会丢失1s的数据。

Redis会定期做aof重写,压缩aof文件日志大小。

在redis实例重启时,优先使用aof来恢复内存的状态,如果没有aof日志,就会使用rdb文件来恢复。

触发条件

RDB持久化的触发分为手动触发和自动触发两种。

  1. 手动触发
    save命令和bgsave命令都可以生成RDB文件。
    save命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在Redis服务器阻塞期间,服务器不能处理任何命令请求。

而bgsave命令会创建一个子进程,由子进程来负责创建RDB文件,父进程(即Redis主进程)则继续处理请求。

bgsave命令执行过程中,只有fork子进程时会阻塞服务器,而对于save命令,整个过程都会阻塞服务器,因此save已基本被废弃,线上环境要杜绝save的使用;后文中也将只介绍bgsave命令。此外,在自动触发RDB持久化时,Redis也会选择bgsave而不是save来进行持久化;下面介绍自动触发RDB持久化的条件。

自动触发
save m n
自动触发最常见的情况是在配置文件中通过save m n,指定当m秒内发生n次变化时,会触发bgsave。
例如,查看redis的默认配置文件(Linux下为redis根目录下的redis.conf),可以看到如下配置信息:
在这里插入图片描述
其中save 900 1的含义是:当时间到900秒时,如果redis数据发生了至少1次变化,则执行bgsave;save 300 10和save 60 10000同理。当三个save条件满足任意一个时,都会引起bgsave的调用。

save m n的实现原理

Redis的save m n,是通过serverCron函数、dirty计数器、和lastsave时间戳来实现的。
serverCron是Redis服务器的周期性操作函数,默认每隔100ms执行一次;该函数对服务器的状态进行维护,其中一项工作就是检查 save m n 配置的条件是否满足,如果满足就执行bgsave。

dirty计数器是Redis服务器维持的一个状态,记录了上一次执行bgsave/save命令后,服务器状态进行了多少次修改(包括增删改);而当save/bgsave执行完成后,会将dirty重新置为0。

例如,如果Redis执行了set mykey helloworld,则dirty值会+1;如果执行了sadd myset v1 v2 v3,则dirty值会+3;注意dirty记录的是服务器进行了多少次修改,而不是客户端执行了多少修改数据的命令。

astsave时间戳也是Redis服务器维持的一个状态,记录的是上一次成功执行save/bgsave的时间。
save m n的原理如下:每隔100ms,执行serverCron函数;在serverCron函数中,遍历save m n配置的保存条件,只要有一个条件满足,就进行bgsave。对于每一个save m n条件,只有下面两条同时满足时才算满足:
(1)当前时间-lastsave > m
(2)dirty >= n
save m n 执行日志

除了save m n 以外,还有一些其他情况会触发bgsave:
在主从复制场景下,如果从节点执行全量复制操作,则主节点会执行bgsave命令,并将rdb文件发送给从节点

执行流程

前面介绍了触发bgsave的条件,下面将说明bgsave命令的执行流程
在这里插入图片描述
图片中的5个步骤所进行的操作如下:

  1. Redis父进程首先判断:当前是否在执行save,或bgsave/bgrewriteaof(后面会详细介绍该命令)的子进程,如果在执行则bgsave命令直接返回。bgsave/bgrewriteaof 的子进程不能同时执行,主要是基于性能方面的考虑:两个并发的子进程同时执行大量的磁盘写操作,可能引起严重的性能问题。
  2. 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的,Redis不能执行来自客户端的任何命令
  3. 父进程fork后,bgsave命令返回”Background saving started”信息并不再阻塞父进程,并可以响应其他命令
  4. 子进程创建RDB文件,根据父进程内存快照生成临时快照文件,完成后对原有文件进行原子替换
  5. 子进程发送信号给父进程表示完成,父进程更新统计信息

RDB文件

RDB文件是经过压缩的二进制文件,下面介绍关于RDB文件的一些细节。
存储路径
RDB文件的存储路径既可以在启动前配置,也可以通过命令动态设定。
配置:dir配置指定目录,dbfilename指定文件名。默认是Redis根目录下的dump.rdb文件。
动态设定:Redis启动后也可以动态修改RDB存储路径,在磁盘损害或空间不足时非常有用;执行命令为config set dir {newdir}和config set dbfilename {newFileName}
在这里插入图片描述

  1. REDIS:常量,保存着”REDIS”5个字符。
  2. db_version:RDB文件的版本号,注意不是Redis的版本号。
  3. SELECTDB 0 pairs:表示一个完整的数据库(0号数据库),同理SELECTDB 3 pairs表示完整的3号数据库;只有当数据库中有键值对时,RDB文件中才会有该数据库的信息(上图所示的Redis中只有0号和3号数据库有键值对);如果Redis中所有的数据库都没有键值对,则这一部分直接省略。其中:SELECTDB是一个常量,代表后面跟着的是数据库号码;0和3是数据库号码;pairs则存储了具体的键值对信息,包括key、value值,及其数据类型、内部编码、过期时间、压缩信息等等。
  4. EOF:常量,标志RDB文件正文内容结束。
  5. check_sum:前面所有内容的校验和;Redis在载入RBD文件时,会计算前面的校验和并与check_sum值比较,判断文件是否损坏。
    Redis默认采用LZF算法对RDB文件进行压缩。虽然压缩耗时,但是可以大大减小RDB文件的体积,因此压缩默认开启;可以通过命令关闭:
    config set rdbcompression no
    需要注意的是,RDB文件的压缩并不是针对整个文件进行的,而是对数据库中的字符串进行的,且只有在字符串达到一定长度(20字节)时才会进行。

启动时加载

RDB文件的载入工作是在服务器启动时自动执行的,并没有专门的命令。但是由于AOF的优先级更高,因此当AOF开启时,Redis会优先载入AOF文件来恢复数据;只有当AOF关闭时,才会在Redis服务器启动时检测RDB文件,并自动载入。服务器载入RDB文件期间处于阻塞状态,直到载入完成为止。
Redis载入RDB文件时,会对RDB文件进行校验,如果文件损坏,则日志中会打印错误,Redis启动失败。

RDB常用配置总结

下面是RDB常用的配置项,以及默认值;前面介绍过的这里不再详细介绍。
save m n:bgsave自动触发的条件;如果没有save m n配置,相当于自动的RDB持久化关闭,不过此时仍可以通过其他方式触发
stop-writes-on-bgsave-error yes:当bgsave出现错误时,Redis是否停止执行写命令;设置为yes,则当硬盘出现问题时,可以及时发现,避免数据的大量丢失;设置为no,则Redis无视bgsave的错误继续执行写命令,当对Redis服务器的系统(尤其是硬盘)使用了监控时,该选项考虑设置为no
rdbcompression yes:是否开启RDB文件压缩
rdbchecksum yes:是否开启RDB文件的校验,在写入文件和读取文件时都起作用;关闭checksum在写入文件和启动文件时大约能带来10%的性能提升,但是数据损坏时无法发现
dbfilename dump.rdb:RDB文件名
dir ./:RDB文件和AOF文件所在目录
接下来一篇继续介绍更重要的AOF

讲一下对Redis的同步机制的理解

Redis可以使用主从同步,从从同步。

第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。

加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。

主从同步最终流程

在这里插入图片描述
当slave断开重连后,会发送psync 命令给master。

master首先会对服务器运行id进行判断,如果与自己相同就进行判断偏移量。

master会判断自己的偏移量与slave的偏移量是否一致。

如果不一致,master会去缓冲区中判断slave的偏移量之后的数据是否存在。

如果存在就会返回+continue回复,表示slave可以执行部分同步了。

master发送断线后的写命令给slave。

slave执行写命令。

对Redis的回收策略的理解

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction(驱逐):禁止驱逐数据

对多级缓存的理解?

多级缓存就是有多个缓存,将ehcache配合redis缓存,比如ehcache作为1级缓存,使用redis作为2级缓存。

如何解决DB和缓存一致性问题?

当修改了数据库后,没有及时修改缓存就会造成不一致的情况。

而修改缓存失败的情况,最主要的原因就是缓存服务器挂了。

如果因为网络问题引起的没有及时更新,可以通过重试机制来解决。

然而如果缓存服务器死了,那么客户端的请求自然也就无法到达,从而请求会直接打到数据库。

因此,我们在修改数据库后,无法修改缓存,那么其中一个方案可以将这条数据放到数据库中,同时启动一个异步任务,定时去检测缓存服务器是否连接成功,一旦连接成功则从数据库中按顺序取出未同步的数据,依次进行同步缓存。

如何解决缓存穿透

生产中,通常都是先检查缓存中是否存在,如果存在直接返回缓存中内容,如果不存在就直接查询数据库,然后再缓存查询结果并返回。

这个时候如果查询的某一个数据在缓存中一直不存在,就会造成每一次请求都查询DB,这样缓存就失去了作用,在流量大时,就造成了缓存穿透。

有一个解决方案,可以将这个不存在的key预先设定一个值。是将缓存的KEY预先设置一个值,如,“key”:“&&”。

如果查询请求过来,得到的返回值是发现是预先设定的"&&", 那我们的应用就可以决定是否继续等待继续访问,还是放弃掉这次操作。

如果继续等待访问,过一个时间轮询点后,再次请求这个key,如果取到的值不再是"&&",则认为这时候key有值了,从而把大量的类似请求阻挡在缓存之中,也就避免了缓存穿透。

redis常见性能问题和解决方案

(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件

(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次

(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内

(4) 尽量避免在压力很大的主库上增加从库

(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…

这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

Redis集群

在这里插入图片描述在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值