字节跳动腾讯百度 Redis高频面试题(2020最新整理)

791 阅读34分钟

原创文章首发于公众号:「码农富哥」,致力于分享后端技术 (高并发架构, 中间件, Linux, TCP/IP, HTTP, MySQL, Redis), 高性能,分布式,微服务等原创干货面试指南

概述

什么是Redis

Redis 是一个使用 C 语言写成的,开源的 key-value 数据库。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。目前,Vmware在资助着redis项目的开发和维护。

Redis为什么这么快

1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1);

2、数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;

3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

4、使用多路 I/O 复用模型,非阻塞 IO;

5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

Redis 数据类型

Redis有哪些数据类型?

Redis目前支持5种数据类型,分别是: String(字符串):

  • 说明:String是简单的 key-value 键值对,value 不仅可以是 String,也可以是数字。
  • 使用场景:String是最常用的一种数据类型,普通的key/value存储都可以归为此类

List(列表):

  • 说明:Redis列表是简单的字符串列表,简单的说就是一个链表或者说是一个队列。可以从头部或尾部向Redis列表添加元素。Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。
  • 使用场景:比如twitter的关注列表、粉丝列表等都可以用Redis的list结构来实现,再比如有的应用使用Redis的list类型实现一个简单的轻量级消息队列,生产者push,消费者pop/bpop。

Hash(字典):

  • 说明:类似C#中的dict类型或者C++中的hash_map类型。
  • 使用场景:假设有多个用户及对应的用户信息,可以用来存储以用户ID为key,将用户信息序列化为比如json格式做为value进行保存。

Set(集合):

  • 说明:可以理解为一堆值不重复的列表,类似数学领域中的集合概念,且Redis也提供了针对集合的求交集、并集、差集等操作。 set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。
  • 使用场景:Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。 又或者在微博应用中,每个用户关注的人存在一个集合中,就很容易实现求两个人的共同好友功能。

Sorted Set(有序集合):

  • 说明:Redis有序集合类似Redis集合,不同的是增加了一个功能,即集合是有序的。一个有序集合的每个成员带有分数,用于进行排序。
  • 使用场景:Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。 又比如用户的积分排行榜需求就可以通过有序集合实现。还有上面介绍的使用List实现轻量级的消息队列,其实也可以通过Sorted Set实现有优先级或按权重的队列。

Redis的zset实现原理及时间复杂度

数据量少的时候使用压缩链表ziplist实现,有序集合使用紧挨在一起的压缩列表节点来保存,第一个节点保存member,第二个保存score。ziplist内的集合元素按score从小到大排序,score较小的排在表头位置。 数据量大的时候使用跳跃列表skiplist和哈希表hash_map结合实现,查找删除插入的时间复杂度都是O(longN)

持久化原理

什么是持久化

Redis 是一种内存数据库,将数据保存在内存中,一旦进程退出,Redis 的数据就会丢失。 为了解决这个问题,Redis 提供了 RDB 和 AOF 两种持久化方案,将内存中的数据保存到磁盘中,避免数据丢失。

持久化方式RDB和AOF底层原理

Redis 提供了不同级别的持久化方式:RDB(默认方式)和AOF

RDB 持久化方式能够在指定的时间间隔能对你的数据进行快照(snapshotting)存储,将内存中的数据不断写入二进制文件中,默认文件dump.rdb,可配置Redis在n秒内如果超过m个key被修改就自动保存快照。(性能高,但是可能会出现数据丢失) save 900 1 #900秒内如果超过1个key被修改,则发起快照保存。 save 300 10 #300秒内如果超过10个key被修改,则快照保存。

RDB持久化只会周期性的保存数据,在未触发下一次存储时服务宕机,就会丢失增量数据。当数据量较大的情况下,fork子进程这个操作很消耗cpu,可能会发生长达秒级别的阻塞情况。

SAVE是阻塞式持久化,执行命令时Redis主进程把内存数据写入到RDB文件中直到创建完毕,期间Redis不能处理任何命令。

BGSAVE属于非阻塞式持久化,创建一个子进程把内存中数据写入RDB文件里同时主进程处理命令请求。

如图展示了RDB使用save 或者 bgsave 进行fork子进程进行持久化的流程:

在这里插入图片描述

AOF(Append-only file) 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。(类似于MySql的日志方式,记录每次更新的日志)(性能低,但是数据完整)

当开启AOF后,服务端每执行一次写操作就会把该条命令追加到一个单独的AOF缓冲区的末尾,然后把AOF缓冲区的内容写入AOF文件里,由于磁盘缓冲区的存在写入AOF文件之后,并不代表数据已经落盘了,而何时进行文件同步则是根据配置的appendfsync来进行配置:

appendfsync选项:always、everysec和no: always:服务器在每执行一个事件就把AOF缓冲区的内容强制性的写入硬盘上的AOF文件里,保证了数据持久化的完整性,效率是最慢的但最安全的;

everysec:服务端每隔一秒才会进行一次文件同步把内存缓冲区里的AOF缓存数据真正写入AOF文件里,兼顾了效率和完整性,极端情况服务器宕机只会丢失一秒内对Redis数据库的写操作;

no:表示默认系统的缓存区写入磁盘的机制,不做程序强制,数据安全性和完整性差一些。

RDB和AOF的优缺点和使用场景

RDB优点:

  • RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
  • RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复.
  • RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.
  • 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.

RDB缺点:

  • RDB的数据安全性是不如AOF的,保存整个数据集的过程是比繁重的,根据配置可能要几分钟才快照一次,如果服务器宕机,那么就可能丢失几分钟的数据
  • Redis数据集较大时,fork的子进程要完成快照会比较耗CPU、耗时

AOF 优点:

  • 使用AOF 会让你的Redis更加耐久: 你可以使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.(由于os会在内核中缓存write做的修改,所以可能不是立即写到磁盘上,这样aof方式的持久化也还是有可能会丢失一部分数据。可以通过配置文件告诉redis我们想要通过fsync函数强制os写入到磁盘的时机)

  • AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题.

  • Redis 可以在 AOF 文件体积变得过大时,通过命令bgrewriteaof自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。 redis AOF的日志重写流程:

    在这里插入图片描述

  • AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。

AOF 缺点:

  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

如何选择RDB和AOF

  • 如果是数据不那么敏感,且可以从其他地方重新生成补回的,那么可以关闭持久化
  • 如果是数据比较重要,不想再从其他地方获取,且可以承受数分钟的数据丢失,比如缓存等,那么可以只使用RDB
  • 如果是用做内存数据库,要使用Redis的持久化,建议是RDB和AOF都开启,或者定期执行bgsave做快照备份,RDB方式更适合做数据的备份,AOF可以保证数据的不丢失

RDB 和 AOF在数据恢复时的优先级?

数据恢复时 AOF 优先于 RDB, 因为AOF的同步频率相对高,可靠性高

事务

什么是事务?

  • 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

  • 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

Redis事务的概念

Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。 总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

Redis事务命令

命令 格式 作用 返回结果
WATCH WATCH key [key ...] 将给出的Keys标记为监测态,作为事务执行的条件 always OK.
UNWATCH UNWATCH 清除事务中Keys的 监测态,如果调用了EXEC or DISCARD,则没有必要再手动调用UNWATCH always OK.
MULTI MULTI 显式开启redis事务,后续commands将排队,等候使用EXEC进行原子执行 always OK.
EXEC EXEC 执行事务中的commands队列,恢复连接状态。如果WATCH在之前被调用,只有监测中的Keys没有被修改,命令才会被执行,否则停止执行 成功: 返回数组 —— 每个元素对应着原子事务中一个 command的返回结果;失败: 返回NULL;
DISCARD DISCARD 清除事务中的commands队列,恢复连接状态。如果WATCH在之前被调用,释放 监测中的Keys always OK.

Redis事务使用方法

Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的

Redis会将一个事务中的所有命令序列化,然后按顺序执行。

  • 使用MULTI命令进入一个Redis事务,这个命令的返回值总是OK。
  • 用户可以发出多个Redis命令。Redis会将这些命令放入队列,而不是直接执行这些命令
  • 如果调用EXEC命令,那么Redis就会按顺序执行事务中的所有命令。
  • 如果调用DISCARD命令将会清除事务队列,然后退出事务。

以下示例会原子化地递增foo键和bar键的值:

http://ghoulich.xninja.org/wp-content/uploads/sites/2/2016/10/image-01_transaction-example.png

Redis事务中出错会怎样?

事务期间,可能会遇到几种命令错误:

  • 命令可能存在语法错误,进入队列的命令有误,比如参数数量错误,错误的命令名称
  • 执行EXEC运行时候时出错,比如给一个list类型的变量 执行incr + 1,这样的命令语法上没问题,只有在运行的时候才能发行

对于第一种错误,客户端会在EXEC调用之前检测, 通过检查排队命令的状态回复,如果命令使用QUEUED进行响应,则它已正确排队;否则Redis将返回错误。

对于第二种错误,服务端会记住在累积命令期间发生的错误,当EXEC命令调用时,将拒绝执行事务,并返回这些错误,同时自动清除命令队列。即使事务中的某些命令执行失败,其他命令仍会被正常执行。(包括出错命令之后的命令)

为什么Redis事务不支持回滚?

事实上Redis命令在事务执行时可能会失败,但仍会继续执行剩余命令而不是Rollback(事务回滚)。如果你使用过关系数据库,这种情况可能会让你感到很奇怪。然而针对这种情况具备很好的解释:

  • Redis命令可能会执行失败,仅仅是由于错误的语法被调用(命令排队时检测不出来的错误),或者使用错误的数据类型操作某个Key: 这意味着,实际上失败的命令都是编程错误造成的,都是开发中能够被检测出来的,生产环境中不应该存在。(这番话,彻底甩锅,“都是你们自己编程错误,与我们无关”。)
  • 由于不必支持Rollback,Redis内部简洁并且更加高效。

“如果错误就是发生了呢?”这是一个反对Redis观点的争论。然而应该指出的是,通常情况下,回滚并不能挽救编程错误。鉴于没有人能够挽救程序员的错误,并且Redis命令失败所需的错误类型不太可能进入生产环境,所以我们选择了不支持错误回滚(Rollback)这种更简单快捷的方法。

Redis事务支持隔离性吗

Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的。

Redis事务其他实现

基于Lua脚本,Redis可以保证脚本内的命令一次性、按顺序地执行, 其同时也不提供事务运行错误的回滚,执行过程中如果部分命令运行错误,剩下的命令还是会继续运行完 基于中间标记变量,通过另外的标记变量来标识事务是否执行完成,读取数据时先读取该标记变量判断是否事务执行完成。但这样会需要额外写代码实现,比较繁琐

乐观锁与悲观锁的区别?

Redis中的管道有什么用

一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多POP3协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程。

缓存雪崩,缓存击穿,缓存穿透现象及解决方案

缓存雪崩:

  • 现象:影响轻则,查询变慢,重则当请求并发更高时,出来大面积服务不可用。
  • 原因: 同一时间缓存大面积失效,就像没有缓存一样,所有的请求直接打到数据库上来,DB扛不住挂了,如果是重要的库,例如用户库,那牵联就一大片了,瞬间倒一片。
  • 案例:电商首页缓存,如果首页的key全部都在某一时刻失效,刚好在那一时刻有秒杀活动,那这样的话就所有的请求都被打到了DB。并发大的情况下DB必然扛不住,导致服务不可用。
  • 解决方案:批量往redis存数据的时候,把每个key的失效时间加上个随机数,这样的话就能保证数据不会在同一个时间大面积失效。

缓存穿透:

  • 现象与原因: 指用户不断发起请求的数据,在缓存和DB中都没有,比如DB中的用户ID是自增的,但是用户请求传了-1,或者是一个特别大的数字,这个时候用户很有可能就是一个攻击者,这样的功击会导致DB的压力过大,严重的话就是把DB搞挂了。因为每次都绕开了缓存直接查询DB

  • 解决方案:

    • 方法一:在接口层增加校验,不合法的参数直接返回。不相信任务调用方,根据自己提供的API接口规范来,作为被调用方,要考虑可能任何的参数传值。
    • 方法二:在缓存查不到,DB中也没有的情况,可以将对应的key的value写为null,或者其他特殊值写入缓存,同时将过期失效时间设置短一点,以免影响正常情况。这样是可以防止反复用同一个ID来暴力攻击。
    • 方法三:正常用户是不会这样暴力功击,只有是恶意者才会这样做,可以在网关NG作一个配置项,为每一个IP设置访问阀值。
    • 方法四:高级用户布隆过滤器(Bloom Filter),这个也能很好地防止缓存穿透。原理就是利用高效的数据结构和算法快速判断出你这个Key是否在DB中存在,不存在你return就好了,存在你就去查了DB刷新KV再return。

缓存击穿:

  • 现象与原因:跟缓存雪崩类似,但是又有点不一样。雪崩是因为大面积缓存失效,请求全打到DB;而缓存击穿是指一个key是热点,不停地扛住大并发请求,全都集中访问此key,而当此key过期瞬间,持续的大并发就击穿缓存,全都打在DB上。就又引发雪崩的问题。
  • 解决方案:
    • 方法一:设置热点数据永远不过期。
    • 方法二:加互斥锁,互斥锁

缓存高可用方案

这就是三者的区别,差不多,但又有一些区别。因为缓存雪崩、穿透和击穿,是缓存最大的问题,要么不出现,一旦出现就是致命性的问题 一般避免以上情况发生我们从三个时间段去分析下:

  • 事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。
  • 事中:本地 ehcache 缓存 + Hystrix 限流+降级,避免MySQL 被打死。
  • 事后:Redis 持久化 RDB+AOF,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

Redis的过期策略和内存淘汰策略

Redis key过期删除策略

使用过Redis的同学应该知道,我们在设置一个key之后,可以指定这个key的过期时间。那么这个key到了过期时间就会立即被删除吗?Redis是如何删除这些过期key的呢? Redis是使用定期删除 + 惰性删除 两者配合的过期策略。

  • 定期删除 定期删除指的是Redis默认每隔100ms就随机抽取一些设置了过期时间的key,检测这些key是否过期,如果过期了就将其删掉。过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。

    • 从过期字典中随机 20 个 key;
    • 删除这 20 个 key 中已经过期的 key;
    • 如果过期的 key 比率超过 1/4,那就重复步骤 1;

    同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。 因为是随机抽取一些key来删除。这样就有可能删除不完,需要惰性删除配合。

  • 惰性删除 惰性删除不再是Redis去主动删除,而是在客户端要获取某个key的时候,Redis会先去检测一下这个key是否已经过期,如果没有过期则返回给客户端,如果已经过期了,那么Redis会删除这个key,不会返回给客户端。

    所以惰性删除可以解决一些过期了,但没被定期删除随机抽取到的key。但有些过期的key既没有被随机抽取,也没有被客户端访问,就会一直保留在数据库,占用内存,长期下去可能会导致内存耗尽。 所以Redis提供了内存淘汰机制来解决这个问题。

内存淘汰策略

Redis在使用内存达到某个阈值(通过maxmemory配置)的时候,就会触发内存淘汰机制,选取一些key来删除。内存淘汰有许多策略,下面分别介绍这几种不同的策略。

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。默认策略

  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。(这个是最常用的)

  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。

  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。

  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。

  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

如何选取合适的策略?比较推荐的是两种lru策略。根据自己的业务需求。如果你使用Redis只是作为缓存,不作为DB持久化,那推荐选择allkeys-lru;如果你使用Redis同时用于缓存和数据持久化,那推荐选择volatile-lru。

缓存淘汰算法: LRU 和 LFU的区别

LRU是最近最少使用页面置换算法(Least Recently Used),也就是首先淘汰最长时间未被使用的页面!

LFU是最近最不常用页面置换算法(Least Frequently Used),也就是淘汰一定时期内被访问次数最少的页!

线程模型

Redis集群方案

Redis主从复制是什么及作用?

  • redis的主从复制功能是支持多个数据库之间的数据同步。一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。

  • 通过redis的复制功能可以很好的实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操作,而从数据库负责读操作。

主从复制的架构图:

在这里插入图片描述

主从复制的作用:

  • 数据冗余,实现数据的热备份
  • 故障恢复,避免单点故障带来的服务不可用
  • 读写分离,负载均衡。主节点负载读写,从节点负责读,提高服务器并发量
  • 高可用基础,是哨兵机制和集群实现的基础

Redis主从复制过程及原理

主从复制过程:

  • 1:当一个从数据库启动时,会向主数据库发送sync命令,
  • 2:主数据库接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来
  • 3:当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库。
  • 4:从数据库收到后,会载入快照文件并执行收到的缓存的命令。

主从复制过程如图所示:

在这里插入图片描述

Redis Sentinel 哨兵机制

Redis Sentinel 是 Redis 高可用的实现方案,它是一个管理多个 Redis 实例的工具。 Redis Sentinel 的主要功能包括 主节点存活检测、主从运行情况检测、自动故障转移 (failover)、主从切换。Redis 的 Sentinel 最小配置是 一主一从。 Redis 的 Sentinel 系统可以用来管理多个 Redis 服务器,该系统可以执行以下四个任务:

  • 监控: Sentinel 会不断的检查 主服务器 和 从服务器 是否正常运行。
  • 通知:当被监控的某个 Redis 服务器出现问题,Sentinel 通过 API 脚本 向 管理员 或者其他的 应用程序 发送通知。
  • 自动故障转移: 当主节点不能正常工作时,Sentinel 会开始一次 自动的故障转移操作,它会将与 失效主节点 是 主从关系 的其中一个 从节点 升级为新的 主节点,并且将其他的 从节点 指向 新的主节点。
  • 配置提供者:在 Redis Sentinel 模式下,客户端应用 在初始化时连接的是 Sentinel 节点集合,从中获取 主节点 的信息。

如图所示,Redis Sentinel 高可用架构 的示意图:

在这里插入图片描述

Redis 哨兵机制如何实现故障自动转移?

sentinel 集群通过主观下线和客观下线判断redis节点是否失效 默认情况下,每个 Sentinel 节点会以每秒一次的频率对Redis 节点和其它的Sentinel 节点发送 PING 命令,并通过节点的 回复 来判断节点是否在线。

  • 主观下线 主观下线 适用于所有 主节点 和 从节点。如果在 down-after-milliseconds 毫秒内,Sentinel 没有收到 目标节点 的有效回复,则会判定 该节点 为 主观下线。
  • 客观下线 客观下线 只适用于 主节点。如果 主节点 出现故障,Sentinel 节点会通过 sentinel is-master-down-by-addr 命令,向其它 Sentinel 节点询问对该节点的 状态判断。如果超过 个数的节点判定 主节点 不可达,则该 Sentinel 节点会判断 主节点为客观下线。

当判断某个Redis节点是客观下线后,Sentinel会把master转移到另外的slave节点,让它充当新的master接受请求,从而保证高可用性。

Redis集群的开源方案有哪些?

Twemproxy Twemproxy 是 twitter 开源的一个 redis 和 memcache 的 中间代理服务器 程序。Twemproxy 作为 代理,可接受来自多个程序的访问,按照 路由规则,转发给后台的各个 Redis 服务器,再原路返回。Twemproxy 存在 单点故障 问题,需要结合 Lvs 和 Keepalived 做 高可用方案。

在这里插入图片描述

  • 优点:应用范围广,稳定性较高,中间代理层 高可用。
  • 缺点:无法平滑地 水平扩容/缩容,无 可视化管理界面,运维不友好,出现故障,不能 自动转移。

Codis Codis 是一个 分布式 Redis 解决方案,对于上层应用来说,连接 Codis-Proxy 和直接连接 原生的 Redis-Server 没有的区别。Codis 底层会 处理请求的转发,不停机的进行 数据迁移 等工作。Codis 采用了无状态的 代理层,对于 客户端 来说,一切都是透明的。

在这里插入图片描述

  • 优点:实现了上层 Proxy 和底层 Redis 的 高可用,数据分片 和 自动平衡,提供 命令行接口 和 RESTful API,提供 监控 和 管理 界面,可以动态 添加 和 删除 Redis 节点。
  • 缺点: 部署架构 和 配置 复杂,不支持 跨机房 和 多租户,不支持 鉴权管理。

Redis Cluster 集群架构

Redis Cluster 实现了一种 混合形式 的 查询路由,但并不是 直接 将请求从一个 Redis 节点 转发 到另一个 Redis 节点,而是在 客户端 的帮助下直接 重定向( redirected)到正确的 Redis 节点。

在这里插入图片描述

  • 优点:无中心节点,数据按照 槽 存储分布在多个 Redis 实例上,可以平滑的进行节点 扩容/缩容,支持 高可用 和 自动故障转移,运维成本低。

  • 缺点: 严重依赖 Redis-trib 工具,缺乏 监控管理,需要依赖 Smart Client (维护连接,缓存路由表,MultiOp 和 Pipeline 支持)。Failover 节点的 检测过慢,不如 中心节点 ZooKeeper 及时。Gossip 消息具有一定开销。无法根据统计区分 冷热数据。

数据分区有哪些算法?

分布式数据库 首先要解决把 整个数据集 按照 分区规则 映射到 多个节点 的问题,即把 数据集 划分到 多个节点 上,每个节点负责 整体数据 的一个 子集。

在这里插入图片描述
数据分布通常有 哈希分区 和 顺序分区 两种方式,对比如下:

分区方式 特点 相关产品
哈希分区 离散程度好,数据分布与业务无关,无法顺序访问 Redis Cluster,Cassandra,Dynamo
顺序分区 离散程度易倾斜,数据分布与业务相关,可以顺序访问 BigTable,HBase,Hypertable

节点取余分区 使用特定的数据,如 Redis 的 键 或 用户 ID,再根据 节点数量 N 使用公式:hash(key)% N 计算出 哈希值,用来决定数据 映射 到哪一个节点上。

在这里插入图片描述

  • 优点: 这种方式的突出优点是 简单性,常用于 数据库 的 分库分表规则。一般采用 预分区 的方式,提前根据 数据量 规划好 分区数,比如划分为 512 或 1024 张表,保证可支撑未来一段时间的 数据容量,再根据 负载情况 将 表 迁移到其他 数据库 中。扩容时通常采用 翻倍扩容,避免 数据映射 全部被 打乱,导致 全量迁移 的情况。

  • 缺点: 当节点数量变化时,如 扩容 或 收缩 节点,数据节点 映射关系 需要重新计算,会导致数据的 重新迁移。

一致性哈希分区 一致性哈希 可以很好的解决 稳定性问题,可以将所有的 存储节点 排列在 收尾相接 的 Hash 环上,每个 key 在计算 Hash 后会 顺时针 找到 临接 的 存储节点 存放。而当有节点 加入 或 退出 时,仅影响该节点在 Hash 环上 顺时针相邻 的 后续节点。

在这里插入图片描述

  • 优点:加入和删除 节点只影响 哈希环 中 顺时针方向 的 相邻的节点,对其他节点无影响。

  • 缺点: 加减节点 会造成 哈希环 中部分数据 无法命中。当使用 少量节点 时,节点变化 将大范围影响 哈希环 中 数据映射,不适合 少量数据节点 的分布式方案。普通 的 一致性哈希分区 在增减节点时需要 增加一倍 或 减去一半 节点才能保证 数据 和 负载的均衡。

虚拟槽分区 虚拟槽分区 巧妙地使用了 哈希空间,使用 分散度良好 的 哈希函数 把所有数据 映射 到一个 固定范围 的 整数集合 中,整数定义为 槽(slot)。这个范围一般 远远大于 节点数,比如 Redis Cluster 槽范围是 0 ~ 16383。槽 是集群内 数据管理 和 迁移 的 基本单位。采用 大范围槽 的主要目的是为了方便 数据拆分 和 集群扩展。每个节点会负责 一定数量的槽,如图所示:

在这里插入图片描述

当前集群有 5 个节点,每个节点平均大约负责 3276 个 槽。由于采用 高质量 的 哈希算法,每个槽所映射的数据通常比较 均匀,将数据平均划分到 5 个节点进行 数据分区。Redis Cluster 就是采用 虚拟槽分区。

节点1: 包含 0 到 3276 号哈希槽。 节点2:包含 3277 到 6553 号哈希槽。 节点3:包含 6554 到 9830 号哈希槽。 节点4:包含 9831 到 13107 号哈希槽。 节点5:包含 13108 到 16383 号哈希槽。

这种结构很容易 添加 或者 删除 节点。如果 增加 一个节点 6,就需要从节点 1 ~ 5 获得部分 槽 分配到节点 6 上。如果想 移除 节点 1,需要将节点 1 中的 槽 移到节点 2 ~ 5 上,然后将 没有任何槽 的节点 1 从集群中 移除 即可。

由于从一个节点将 哈希槽 移动到另一个节点并不会 停止服务,所以无论 添加删除 或者 改变 某个节点的 哈希槽的数量 都不会造成 集群不可用 的状态.

说说 Redis Cluster 虚拟槽分区?

Redis Cluster 采用 虚拟槽分区,所有的 键 根据 哈希函数 映射到 0~16383 整数槽内,计算公式:slot = CRC16(key)& 16383。每个节点负责维护一部分槽以及槽所映射的 键值数据,如图所示:

在这里插入图片描述
Redis虚拟槽分区的特点:

  • 解耦 数据 和 节点 之间的关系,简化了节点 扩容 和 收缩 难度。
  • 节点自身 维护槽的 映射关系,不需要 客户端 或者 代理服务 维护 槽分区元数据。
  • 支持 节点、槽、键 之间的 映射查询,用于 数据路由、在线伸缩 等场景。

三主三从的集群使用多少台机器部署比较好?

土豪型: 使用6台机器,每台部署一个Redis节点 经济型:使用3台机器,每台机器部署2个Redis节点,主从混布,即同一组主从节点,分布在不同节点,从而保证高可用!

Redis性能优化

Redis常见性能问题和解决方案:

  • Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
  • 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
  • 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内 尽量避免在压力很大的主库上增加从库

总结

原创文章首发于公众号:「码农富哥」,致力于分享后端技术 (高并发架构, 中间件, Linux, TCP/IP, HTTP, MySQL, Redis), 高性能,分布式,微服务等原创干货面试指南

在这里插入图片描述