面试常问
⭐什么是Redis?简述它的优缺点?
Redis是C语言开发的一个开源的(遵从BSD协议)高性能键值对(key-value)的内存数据库,可以用作数据库、缓存、消息中间件等。它是一种NoSQL(not-only sql,泛指非关系型数据库)的数据库。
优势:
😀性能优秀,数据在内存中,读写速度非常快,支持并发10W QPS;
😀单进程单线程,是线程安全的,采用IO多路复用机制;
😀丰富的数据类型,支持字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等;
😀支持数据持久化。可以将内存中数据保存在磁盘中,重启时加载;
😀主从复制,哨兵,高可用;
😀可以用作分布式锁;
😀可以作为消息中间件使用,支持发布订阅
缺点
🤥Redis的数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上;
🤥在主节点宕机前有部分数据不能及时同步到从节点上;
⭐Redis持久化策略
redis为了保证效率,数据缓存在了内存中,但是会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件中,以保证数据的持久化。
🍀RDB:
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
🍀AOF:
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。Aof保存的是appendonly.aof文件。
相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb。
💡Redis默认是快照RDB的持久化方式
💡甚至可以关闭持久化功能,让数据只在服务器运行时存。
⭐Redis事务
🌺事务三阶段:
开启:以MULTI开始一个事务
入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到执行的事务队列里
执行:由EXEC命令触发事务
🌺三特性:
单独的隔离操作:事务中所有的命令都会序列化、按顺序的执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
没有隔离级别的概念:队列的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被执行。
不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。
🌺相关指令:
MULTI:标记一个事务块的开始
EXEC:执行所有事务块的内容
EXECDISCARD:取消事务
WATCH:监视key,如果在执行事务之前key被其他命令改动,那么事务将被打断
⭐Redis有哪几种数据淘汰策略
淘汰策略保证了redis中的数据都是热点数据
淘汰策略 | 描述 |
---|---|
noeviction(不删除策略) | 达到最大内存限制时,如果需要更多内存,直接返回错误信息 |
allkeys-lru | 所有key,优先删除最近最少使用(less recently used ,LRU)的key |
volatile-lru | 设置了过期时间,优先删除最近最少使用的key |
allkeys-random | 所有key通用; 随机删除一部分key |
volatile-random | 随机删除设置了过期时间的一部分key |
volatile-ttl | 优先删除设置了过期时间、剩余时间短的key |
allkeys-lfu | 通过统计访问频率,将访问频率最少,即最不经常使用的KV淘汰(4.0版本) |
⭐Redies的五种数据类型
类型 | 简介 | 特性 | 场景 |
---|---|---|---|
string(字符串) | 二进制安全 | 可以包含任何数据,如图片或者序列化对象,一个最大储存512M | 任何 |
Hash(字典) | 键值对集合,即编程语言中的map类型 | 适合存储对象,并且可以像数据库中的update一个属性一样只修改某一项属性值 | 存储、读取修改用户属性 |
List(列表) | 链表(双向) | 增删快,提供了操作一元素的api | 最新消息排行;消息队列 |
set(集合) | hash表实现,元素不重复 | 添加、删除、查找的复杂度都是O(1),提供了求交集、并集、差集的操作 | 共同好友;利用唯一性,统计访问网站的所有IP |
sorted set(有序集合) | 将set中的元素增加一个权重参数score,元素按score有序排列 | 数据插入集合时,已经进行了天然排序 | 排行榜;带权重的消息队列 |
⭐Redis雪崩
📝由于原有缓存大面积失效(设置缓存时采用了相同的过期时间,或者数据未加载到缓存中),原本应该访问缓存的请求都去查询数据库了,DB瞬时压力过重崩溃
🛠️解决:
1.把每个Key的失效时间都加个随机值 setRedis(key, value, time+Math.random()*10000)
2.消息中间件
3.使用分布式锁、本地锁
“锁”的机制,在缓存更新或者过期的情况下,先尝试获取到锁,当更新或者从数据库获取完成后再释放锁,其他的请求只需要牺牲一定的等待时间,即可直接从缓存中继续获取数据。
⭐缓存穿透
📝缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求或有人利用不存在的key频繁攻击我们的应用。
👉举个栗子:我们数据库的id都是从1自增的,如果发起id=-1的数据或者id特别大不存在的数据,这样的不断攻击导致数据库压力很大,严重会击垮数据库。
🛠️解决:
1.缓存数据或程序内检查key值合法性
2.布隆过滤器
3.缓存空对象,查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
⭐缓存击穿
📝这个跟缓存雪崩有点像,但是又有一点不一样,缓存雪崩是因为大面积的缓存失效,打崩了DB,而缓存击穿是指极度热点的key在瞬间失效,大量请求击穿数据库,就在这个Key的点上击穿了缓存
🛠️解决:
该key值不允许过期
分布式锁
互斥锁
⭐假如Redis里面有1亿个 key,其中有10w个key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?
使用keys指令可以扫出指定模式的key列表。
对方接着追问∶如果这个 Redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问题?
这个时候你要回答Redis关键的—个特性;Redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做—次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
⭐Redis为何这么快?为什么是单线程的?
Redis是单进程单线程的模型,因为Redis完全是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。
- Redis完全基于内存,绝大部分请求是纯粹的内存操作,非常迅速,数据存在内存中
- 数据结构简单,对数据操作也简单。
- 采用单线程,避免了不必要的上下文切换和竞争条件,不存在多线程导致的CPU切换,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有死锁问题导致的性能消耗。
- 使用多路复用IO模型,非阻塞IO。
⭐RDB是怎么工作的
🥝默认Redis是会以快照"RDB"的形式将数据持久化到磁盘的一个二进制文件dump.rdb。
🥝工作原理简单说一下:当Redis需要做持久化时,Redis会fork一个子进程,子进程将数据写到磁盘上一个临时RDB文件中。当子进程完成写临时文件后,将原来的RDB替换掉,这样的好处是可以copy-on-write。
😀RDB的优点是:这种文件非常适合用于备份:比如,你可以在最近的24小时内,每小时备份一次,并且在每个月的每一天也备份一个RDB文件。这样的话,即使遇上问题,也可以随时将数据集还原到不同的版本。RDB非常适合灾难恢复。
🤥RDB的缺点是:如果你需要尽量避免在服务器故障时丢失数据,那么RDB不合适你。
⭐说下AOF
🥝以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。AOF保存的是appendonly.aof文件。
🥝使用AOF的优点是会让redis变得非常耐久。可以设置不同的fsync策略,aof的默认策略是每秒钟fsync一次,在这种配置下,就算发生故障停机,也最多丢失一秒钟的数据。缺点是对于相同的数据集来说,AOF的文件体积通常要大于RDB文件的体积。相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb。
⭐AOF和RDB该用哪一个呢
如果你非常关心你的数据,但仍然可以承受数分钟内的数据丢失,那么可以只使用RDB持久。AOF将Redis执行的每一条命令追加到磁盘中,处理巨大的写入会降低Redis的性能,不知道你是否可以接受。
数据库备份和灾难恢复:定时生成RDB快照非常便于进行数据库备份,并且RDB恢复数据集的速度也要比AOF恢复的速度快。当然了,redis支持同时开启RDB和AOF,系统重启后,redis会优先使用AOF来恢复数据,这样丢失的数据会最少。
⭐Redis主从复制
主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。
redis单节点存在单点故障问题,为了解决单点问题,一般都需要对redis配置从节点,然后使用哨兵来监听主节点的存活状态,如果主节点挂掉,从节点能继续提供缓存功能
⭐你能说说Redis主从复制的过程和原理吗
主从配置结合哨兵模式能解决单点故障问题,提高redis可用性。从节点仅提供读操作,主节点提供写操作。对于读多写少的状况,可给主节点配置多个从节点,从而提高响应效率。
关于复制过程,是这样的:
- 从节点执行slaveof[masterIP][masterPort],保存主节点信息
- 从节点中的定时任务发现主节点信息,建立和主节点的socket连接
- 从节点发送Ping信号,主节点返回Pong,两边能互相通信
- 连接建立后,主节点将所有数据发送给从节点(数据同步)
- 主节点把当前的数据同步给从节点后,便完成了复制的建立过程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性。
⭐那你能详细说下数据同步的过程吗
redis2.8之前使用sync[runId][offset]同步命令,redis2.8之后使用psync[runId][offset]命令。两者不同在于,sync命令仅支持全量复制过程,psync支持全量和部分复制。
介绍同步之前,先介绍几个概念:
runId:每个redis节点启动都会生成唯一的uuid,每次redis重启后,runId都会发生变化。
offset:主节点和从节点都各自维护自己的主从复制偏移量offset,当主节点有写入命令时,offset=offset+命令的字节长度。从节点在收到主节点发送的命令后,也会增加自己的offset,并把自己的offset发送给主节点。这样,主节点同时保存自己的offset和从节点的offset,通过对比offset来判断主从节点数据是否一致。repl_backlog_size:保存在主节点上的一个固定长度的先进先出队列,默认大小是1MB。
(1)主节点发送数据给从节点过程中,主节点还会进行一些写操作,这时候的数据存储在复制缓冲区中。从节点同步主节点数据完成后,主节点将缓冲区的数据继续发送给从节点,用于部分复制。
(2)主节点响应写命令时,不但会把命名发送给从节点,还会写入复制积压缓冲区,用于复制命令丢失的数据补救。
上面是psync的执行流程:从节点发送psync[runId][offset]命令,主节点有三种响应:
(1)FULLRESYNC:第一次连接,进行全量复制
(2)CONTINUE:进行部分复制
(3)ERR:不支持psync命令,进行全量复制
⭐具体说下全量复制和部分复制的过程吗?
👉上面是全量复制的流程。主要有以下几步:
🥑从节点发送psync ? -1命令(因为第一次发送,不知道主节点的runId,所以为?,因为是第一次复制,所以offset=-1)。
🥑主节点发现从节点是第一次复制,返回FULLRESYNC {runId} {offset},runId是主节点的runId,offset是主节点目前的offset。
🥑从节点接收主节点信息后,保存到info中。
🥑主节点在发送FULLRESYNC后,启动bgsave命令,生成RDB文件(数据持久化)。
🥑主节点发送RDB文件给从节点。到从节点加载数据完成这段期间主节点的写命令放入缓冲区。
🥑从节点清理自己的数据库数据。
🥑从节点加载RDB文件,将数据保存到自己的数据库中。
🥑如果从节点开启了AOF,从节点会异步重写AOF文件。
👉关于部分复制有以下几点说明:
🥝部分复制主要是Redis针对全量复制的过高开销做出的一种优化措施,使用psync[runId][offset]命令实现。当从节点正在复制主节点时,如果出现网络闪断或者命令丢失等异常情况时,从节点会向主节点要求补发丢失的命令数据,主节点的复制积压缓冲区将这部分数据直接发送给从节点,这样就可以保持主从节点复制的一致性。补发的这部分数据一般远远小于全量数据。
🥝主从连接中断期间主节点依然响应命令,但因复制连接中断命令无法发送给从节点,不过主节点内的复制积压缓冲区依然可以保存最近一段时间的写命令数据。
🥝当主从连接恢复后,由于从节点之前保存了自身已复制的偏移量和主节点的运行ID。因此会把它们当做psync参数发送给主节点,要求进行部分复制。
🥝主节点接收到psync命令后首先核对参数runId是否与自身一致,如果一致,说明之前复制的是当前主节点;之后根据参数offset在复制积压缓冲区中查找,如果offset之后的数据存在,则对从节点发送+COUTINUE命令,表示可以进行部分复制。因为缓冲区大小固定,若发生缓冲溢出,则进行全量复制。
🥝主节点根据偏移量把复制积压缓冲区里的数据发送给从节点,保证主从复制进入正常状态。
⭐那主从复制会存在哪些问题呢
🍀一旦主节点宕机,从节点晋升为主节点,同时需要修改应用方的主节点地址,还需要命令所有从节点去复制新的主节点,整个过程需要人工干预。
🍀主节点的写能力受到单机的限制。
🍀主节点的存储能力受到单机的限制。
🍀原生复制的弊端在早期的版本中也会比较突出,比如:redis复制中断后,从节点会发起psync。此时如果同步不成功,则会进行全量同步,主库执行全量备份的同时,可能会造成毫秒或秒级的卡顿。
⭐比较主流的解决方案是什么呢
是Redis Sentinel(哨兵)的架构图。Redis Sentinel(哨兵)主要功能包括主节点存活检测、主从运行情况检测、自动故障转移、主从切换。Redis Sentinel最小配置是一主一从。Redis的Sentinel系统可以用来管理多个Redis服务器,该系统可以执行以下四个任务:
1、监控:不断检查主服务器和从服务器是否正常运行。
2、通知:当被监控的某个redis服务器出现问题,Sentinel通过API脚本向管理员或者其他应用程序发出通知。
3、自动故障转移:当主节点不能正常工作时,Sentinel会开始一次自动的故障转移操作,它会将与失效主节点是主从关系的其中一个从节点升级为新的主节点,并且将其他的从节点指向新的主节点,这样人工干预就可以免了。
4、配置提供者:在Redis Sentinel模式下,客户端应用在初始化时连接的是Sentinel节点集合,从中获取主节点的信息。
⭐哨兵模式
能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重
⭐哨兵工作原理
1、每个Sentinel节点都需要定期执行以下任务:每个Sentinel以每秒一次的频率,向它所知的主服务器、从服务器以及其他的Sentinel实例发送一个PING命令。
2、如果一个实例距离最后一次有效回复PING命令的时间超过down-after-milliseconds所指定的值,那么这个实例会被Sentinel标记为主观下线。
3、如果一个主服务器被标记为主观下线,那么正在监视这个服务器的所有Sentinel节点,要以每秒一次的频率确认主服务器的确进入了主观下线状态。
4、如果一个主服务器被标记为主观下线,并且有足够数量的Sentinel(至少要达到配置文件指定的数量)在指定的时间范围内同意这一判断,那么这个主服务器被标记为客观下线。
5、一般情况下,每个Sentinel会以每10秒一次的频率向它已知的所有主服务器和从服务器发送INFO命令,当一个主服务器被标记为客观下线时,Sentinel向下线主服务器的所有从服务器发送INFO命令的频率,会从10秒一次改为每秒一次。
6、Sentinel和其他Sentinel协商客观下线的主节点的状态,如果处于SDOWN状态,则投票自动选出新的主节点,将剩余从节点指向新的主节点进行数据复制。
7、当没有足够数量的Sentinel同意主服务器下线时,主服务器的客观下线状态就会被移除。当主服务器重新向Sentinel的PING命令返回有效回复时,主服务器的主观下线状态就会被移除。
⭐Redis 集群方案应该怎么做?都有哪些方案?
1. codis:目前用的最多的集群方案,基本和twemproxy一致的效果,但它支持在节点数量改变的情况下,旧节点数据客恢复到新hash节点。
2. Redis cluster3.0自带的集群,特点在于他的分布式算法不是一致性hash,而是hash槽的概念,以及自身支持节点设置从节点。
3. 在业务代码层变现,起几个毫无关联的Redis实例,在代码层,对key进行hash计算,然后去对应的redis实例操作数据。这种方式对hash层代码要求比较高,考虑部分包括,节点失效后的替代算法方案,数据震荡后的字典脚本恢复,实例的监控等等。
Redis 并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会表失写操作
Redis 集群之间异步复制
⭐Redis 集群方案什么情况下会导致整个集群不可用?
⭐Jedis 与 Redisson 对比有什么优缺点?
Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;
Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson 的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
⭐说说 Redis 哈希槽的概念
Redis 集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384 取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
⭐Redis 中的管道有什么用?
一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应,这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。
这就是管道(pipelining),是一种几十年来广泛使用的技术。
⭐Redis 如何做内存优化?
尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的 web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key,而是应该把这个用户的所有信息存储到一张散列表里面。
⭐Redis 回收进程如何工作的?
一个客户端运行了新的命令,添加了新的数据。
Redis检查内存使用情况,如果大于max memory的限制,则根据设定好的策略进行回收。
一个新的命令被执行等等,所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断回收到边界以下。
如果一个命令的结果导致大量内存被使用,不用多久内存限制就会被这个内存使用量超越。
⭐Redis 的并发竞争问题如何解决?
1.分布式锁+时间戳
(1)如果对这个key操作,不要求顺序
这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可,比较简单。
(2)如果对这个key操作,要求顺序
假设有一个key1,系统A需要将key1设置为valueA,系统B需要将key1设置为valueB,系统C需要将key1设置为valueC.
期望按照key1的value值按照 valueA–>valueB–>valueC的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。假设时间戳如下
系统A key 1 {valueA 3:00}
系统B key 1 {valueB 3:05}
系统C key 1 {valueC 3:10}
那么,假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推
2.消息队列
在并发量过大的情况下,可以通过消息中间件进行处理,把并行读写进行串行化。
把Redis.set操作放在队列中使其串行化,必须的一个一个执行。
这种方式在一些高并发的场景中算是一种通用的解决方案。
⭐redis使用场景
缓存
幂等性校验
分布式锁
CSRF攻击
计数器
发布/订阅
⭐缓存和数据库数据一致性问题
分布式环境下非常容易出现缓存和数据库间数据一致性问题,针对这一点,如果项目对缓存的要求是强一致性的,那么就不要使用缓存。我们只能采取合适的策略来降低缓存和数据库间数据不一致的概率,而无法保证两者间的强一致性。合适的策略包括合适的缓存更新策略,更新数据库后及时更新缓存、缓存失败时增加重试机制。
双写模式:
数据更新时,写完数据库,写缓存
劣势:并发下会有脏数据;读到的数据有延迟:保证的是最终一致性
脏数据问题:暂时性的脏数据问题,但是在数据稳定,缓存过期以后,又能得到正确的数据
失效模式:
更新数据时,立马删掉。也会有脏数据问题
⭐保证接口幂等性
幂等性:
一个函数, 使用任意次数和使用一次的效果相同的时候就是幂等的。即用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。
接口幂等性解决方案:
唯一索引:场景->新增数据,如注册用户(username/id_card_no->unique key),通过唯一索引,防止用户重复提交造成数据重复插入。
联合索引:原理同唯一索引,有时唯一索引无法满足,可考虑联合索引;
Token校验:
此处声明的token并非用户登陆所留下的token,而是对于请求指定的一个随机口令,如uuid。在用户进入创建页面或修改页面时,调用查询接口,将token返回前端并同时保存到redis中,前端在调用创建与修改操作时,将token放入header,请求进入接口,判断两份token是否一致:
一致,删除redis中的token
不一致,返回提示语,请勿重复提交
⭐使用方式
一般有两种方式:
通过RedisTemplate来使用
使用spring cache集成Redis(也就是注解的方式)
评论区