系列目录

spring 常见面试题

spring-boot 常见面试题

redis 常见面试题

mysql 常见面试题

mq 常见面试题

rpc/dubbo 常见面试题

jvm/gc/双亲委派/类加载流程/jvm 调优 常见面试题

ZooKeeper 面试题

【面试】mybatis 常见面试题汇总

前言

大家好,我是老马。

Redis 使我们日常开发中常用的缓存框架。

面试中自然出现频率也比较高,对常见问题进行整理,便于平时查阅收藏。

拓展阅读

java从零手写实现redis(一)如何实现固定大小的缓存?

java从零手写实现redis(三)redis expire 过期原理

java从零手写实现redis(三)内存数据如何重启不丢失?

java从零手写实现redis(四)添加监听器

java从零手写实现redis(五)过期策略的另一种实现思路

java从零手写实现redis(六)AOF 持久化原理详解及实现

java从零开始手写redis(七)LRU 缓存淘汰策略详解

java从零开始手写redis(八)朴素 LRU 淘汰算法性能优化

java 从零开始手写 redis(九)LRU 缓存淘汰算法如何避免缓存污染

java 从零开始手写 redis(十)缓存淘汰算法 LFU 最少使用频次

java 从零开始手写 redis(11)clock时钟淘汰算法详解及实现

java 从零开始手写 redis(12)redis expire 过期如何实现随机获取keys?

java从零开始手写 redis(13)HashMap 源码原理详解

java 从零开始手写 redis(14)redis渐进式rehash详解

java 从零开始手写 redis(15)实现自己的 HashMap

java 从零开始手写 redis(16)实现渐进式 rehash map

【实战汇总】

缓存实战(1)缓存雪崩、缓存击穿和缓存穿透入门简介及解决方案

缓存实战(2)布隆过滤器是啥?guava 的 BloomFilter 使用

缓存实战(3)让你彻底搞懂布隆过滤器!实现一个自己的BloomFilter

缓存实战(4)bloom filter 使用最佳实践,让你少踩坑

java 从零实现属于你的 redis 分布式锁

3天时间,我是如何解决redis bigkey删除问题的?

redis 多路复用

Redis 是什么?

redis 常见功能

简单来说 redis 就是一个数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向。

另外,redis 也经常用来做分布式锁。

redis 提供了多种数据类型来支持不同的业务场景。

除此之外,redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。

为什么要用 redis/为什么要用缓存

主要从“高性能”和“高并发”这两点来看待这个问题。

高性能

假如用户第一次访问数据库中的某些数据。

这个过程会比较慢,因为是从硬盘上读取的。

将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。

操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!

高性能

高并发

直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。

高并发

为什么要用 redis 而不用 map/guava 做缓存?

缓存分为本地缓存和分布式缓存。

以 Java 为例,使用自带的 map 或者 guava 实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。

使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。

缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。

基础类型

String

String 类型是 Redis 中最常使用的类型,内部的实现是通过 SDS(Simple Dynamic String )来存储的。

SDS 类似于 Java 中的 ArrayList,可以通过预分配冗余空间的方式来减少内存的频繁分配。

这是最简单的类型,就是普通的 set 和 get,做简单的 KV 缓存。

但是真实的开发环境中,很多仔可能会把很多比较复杂的结构也统一转成String去存储使用,比如有的仔他就喜欢把对象或者List转换为JSONString进行存储,拿出来再反序列话啥的。

我在这里就不讨论这样做的对错了,但是我还是希望大家能在最合适的场景使用最合适的数据结构,对象找不到最合适的但是类型可以选最合适的嘛,之后别人接手你的代码一看这么规范,诶这小伙子有点东西呀,看到你啥都是用的String,垃圾!

应用场景

String 的实际应用场景比较广泛的有:

缓存功能:String字符串是最常用的数据类型,不仅仅是Redis,各个语言都是最基本类型,因此,利用Redis作为缓存,配合其它数据库作为存储层,利用Redis支持高并发的特点,可以大大加快系统的读写速度、以及降低后端数据库的压力。

计数器:许多系统都会使用Redis作为系统的实时计数器,可以快速实现计数和查询的功能。而且最终的数据结果可以按照特定的时间落地到数据库或者其它存储介质当中进行永久保存。

共享用户 Session:用户重新刷新一次界面,可能需要访问一下数据进行重新登录,或者访问页面缓存Cookie,但是可以利用Redis将用户的Session集中管理,在这种模式只需要保证Redis的高可用,每次用户Session的更新和获取都可以快速完成。大大提高效率。

Redis 的 String 类型使用 SSD 方式实现的好处?

SDS 实现方式相对 C 语言的 String 的好处有:

  • 避免缓冲区溢出;对字符修改时,可以根据 len 属性检查空间是否满足要求。

  • 常数复杂度获取字符串长度;获取字符串的长度直接读取 len 属性就可以获取。而 C 语言中,因为字符串是简单的字符数组,求长度时内部其实是直接顺序遍历数组内容,找到 ‘\0’ 对应的字符,计算出字符串的长度。复杂度即 O(N)。

  • 减少内存分配次数;通过结构中的 len 和 free 两个属性,更好的协助空间预分配以及惰性空间释放。

  • 二进制安全;SSD 不是以空字符串来判断是否结束,而是以 len 属性来判断字符串是否结束。而在 C 语言中,字符串要求除了末尾之外不能出现空字符,否则会被程序认为是字符串的结尾。这就使得 C 字符串只能存储文本数据,而不能保存图像,音频等二进制数
  • 据。

  • 兼容 C 字符串函数;可以重用 C 语言库的 的一部分函数。

请介绍一下 Redis 的 String 类型底层实现?

Redis 底层实现了简单动态字符串的类型(SDS),来表示 String 类型。没有直接使用 C 语言定义的字符串类型。

Redis 底层使用简单动态字符串(simple dynamic string,SDS)的抽象类型实现的。默认以 SDS 作为自己的字符串表示。而没有直接使用 C 语言定义的字符串类型。

SDS 的定义格式如下:

struct sdshdr{
    //记录 buf 数组中已使用字节的数量
    //等于 SDS 保存字符串的长度
    int len;
    //记录 buf 数组中未使用字节的数量
    int free;
    //字节数组,用于保存字符串
    char buf[];  //buf的大小等于len+free+1,其中多余的1个字节是用来存储‘\0’的。
}

SDS 的存储示例如下:

SDS

Hash

这个是类似 Map 的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没嵌套其他的对象)给缓存在 Redis 里,然后每次读写缓存的时候,可以就操作 Hash 里的某个字段。

但是这个的场景其实还是多少单一了一些,因为现在很多对象都是比较复杂的,比如你的商品对象可能里面就包含了很多属性,其中也有对象。

我自己使用的场景用得不是那么多。

List:

List 是有序列表,这个还是可以玩儿出很多花样的。

比如可以通过 List 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的东西。

比如可以通过 lrange 命令,读取某个闭区间内的元素,可以基于 List 实现分页查询,这个是很棒的一个功能,基于 Redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走。

比如可以搞个简单的消息队列,从 List 头怼进去,从 List 屁股那里弄出来。

List本身就是我们在开发过程中比较常用的数据结构了,热点数据更不用说了。

消息队列:Redis的链表结构,可以轻松实现阻塞队列,可以使用左进右出的命令组成来完成队列的设计。比如:数据的生产者可以通过Lpush命令从左边插入数据,多个数据消费者,可以使用BRpop命令阻塞的“抢”列表尾部的数据。 文章列表或者数据分页展示的应用。

比如,我们常用的博客网站的文章列表,当用户量越来越多时,而且每一个用户都有自己的文章列表,而且当文章多时,都需要分页展示,这时可以考虑使用Redis的列表,列表不但有序同时还支持按照范围内获取元素,可以完美解决分页查询功能。大大提高查询效率。

Redis 的链表数据结构的特征有哪些?

具有以下特征:

  • 通过 len 属性来记录链表长度;

  • 链表的最前和最后节点都有引用,获取前后节点的复杂度都为 O(1);

  • 对链表的访问都是以 null 结束。

Set

Set 是无序集合,会自动去重的那种。

直接基于 Set 将系统里需要去重的数据扔进去,自动就给去重了,如果你需要对一些数据进行快速的全局去重,你当然也可以基于 JVM 内存里的 HashSet 进行去重,但是如果你的某个系统部署在多台机器上呢?得基于Redis进行全局的 Set 去重。

可以基于 Set 玩儿交集、并集、差集的操作,比如交集吧,我们可以把两个人的好友列表整一个交集,看看俩人的共同好友是谁?对吧。

反正这些场景比较多,因为对比很快,操作也简单,两个查询一个Set搞定。

Sorted Set

Sorted set 是排序的 Set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序。

有序集合的使用场景与集合类似,但是set集合不是自动有序的,而Sorted set可以利用分数进行成员间的排序,而且是插入时就排序好。所以当你需要一个有序且不重复的集合列表时,就可以选择Sorted set数据结构作为选择方案。

排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。

用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。

微博热搜榜,就是有个后面的热度值,前面就是名称

请介绍一下 Redis 的数据类型 Sorted Set(zset)以及底层实现机制?

zset 的功能和 sets 类似,但是它在集合内的元素是有顺序,不能重复的。

所以适合做排行榜之类的功能。

它底层实现机制的实现方式有两种,分别为 ziplist(压缩列表) 或者 skiplist(跳跃表)。它们的区别为:

底层使用的数据结构实现不同:ziplist 编码的有序集合对象使用压缩列表作为底层实现,而 skiplist 编码的有序集合对象使用 zset 结构作为底层实现。

底层集合元素保存的方式不同;ziplist 中的每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个节点保存元素的分值。并且压缩列表内的集合元素按分值从小到大的顺序进行排列,小的放置在靠近表头的位置,大的放置在靠近表尾的位置。

skiplist 的一个 zset 结构同时包含一个字典和一个跳跃表。字典的键保存元素的值,字典的值则保存元素的分值;跳跃表节点的 object 属性保存元素的成员,跳跃表节点的 score 属性保存元素的分值。

当有序集合对象保存的元素数量小于 128 个,并且保存的所有元素长度都小于 64 字节时,对象使用 ziplist 编码。否则使用 skiplist 编码。

高级用法:

Bitmap

位图是支持按 bit 位来存储信息,可以用来实现 布隆过滤器(BloomFilter);

HyperLogLog

供不精确的去重计数功能,比较适合用来做大规模数据的去重统计,例如统计 UV;

Geospatial

可以用来保存地理位置,并作位置距离计算或者根据半径计算位置等。有没有想过用Redis来实现附近的人?或者计算最优地图路径?

这三个其实也可以算作一种数据结构,不知道还有多少朋友记得,我在梦开始的地方,Redis基础中提到过,你如果只知道五种基础类型那只能拿60分,如果你能讲出高级用法,那就觉得你有点东西。

pub/sub:

功能是订阅发布功能,可以用作简单的消息队列。

Pipeline:

可以批量执行一组指令,一次性返回全部结果,可以减少频繁的请求应答。

Lua:

Redis 支持提交 Lua 脚本来执行一系列的功能。

我在前电商老东家的时候,秒杀场景经常使用这个东西,讲道理有点香,利用他的原子性。

话说你们想看秒杀的设计么?

事务:

最后一个功能是事务,但 Redis 提供的不是严格的事务,Redis 只保证串行执行命令,并且能保证全部执行,但是执行命令失败时并不会回滚,而是会继续执行下去。

什么是 Redis 事务?原理是什么?

Redis 中的事务是一组命令的集合,是 Redis 的最小执行单位。

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

它的原理是先将属于一个事务的命令发送给 Redis,然后依次执行这些命令。

Redis 事务的注意点有哪些?

需要注意的点有:

Redis 事务是不支持回滚的,不像 MySQL 的事务一样,要么都执行要么都不执行;

Redis 服务端在执行事务的过程中,不会被其他客户端发送来的命令请求打断。直到事务命令全部执行完毕才会执行其他客户端的命令。

Redis 为什么不支持回滚?

Redis 的事务不支持回滚,但是执行的命令有语法错误,Redis 会执行失败,这些问题可以从程序层面捕获并解决。

但是如果出现其他问题,则依然会继续执行余下的命令。

这样做的原因是因为回滚需要增加很多工作,而不支持回滚则可以保持简单、快速的特性

Redis 事务相关的命令有哪几个?

事务从开始到执行会经历的三个阶段:开始事务、命令入队、 执行事务。

它以 MULTI 开始一个事务,然后让多个命令入队到事务中,最后通过命令 EXEC 触发执行事务。

它们的执行命令有:

命令 作用 使用方法
MULTI 标记一个事务块的开始 Multi
DISCARD 取消事务、放弃执行事务块的所有命令 Discard
WATCH key [key …] 监视一个 (或多个) key,如果在事务执行之前这个 (或这些) key 被其他命令所改动,那么事务将被打断 Watch key
EXEC 执行所有事务块内的命令 Exec
UNWATCH 取消 WATCH 命令对所有 key 的监视 Unwatch

持久化

Redis 提供了 RDB 和 AOF 两种持久化方式,RDB 是把内存中的数据集以快照形式写入磁盘,实际操作是通过 fork 子进程执行,采用二进制压缩存储;AOF 是以文本日志的形式记录 Redis 处理的每一个写入或删除操作。

RDB 把整个 Redis 的数据保存在单一文件中,比较适合用来做灾备,但缺点是快照保存完成之前如果宕机,这段时间的数据将会丢失,另外保存快照时可能导致服务短时间不可用。

AOF 对日志文件的写入操作使用的追加模式,有灵活的同步策略,支持每秒同步、每次修改同步和不同步,缺点就是相同规模的数据集,AOF 要大于 RDB,AOF 在运行效率上往往会慢于 RDB。

细节的点大家去高可用这章看,特别是两者的优缺点,以及怎么抉择。

高可用

Redis 的同步机制了解是什么?

Redis 支持主从同步、从从同步。

如果是第一次进行主从同步,主节点需要使用 bgsave 命令,再将后续修改操作记录到内存的缓冲区,等 RDB 文件全部同步到复制节点,复制节点接受完成后将 RDB 镜像记载到内存中。

等加载完成后,复制节点通知主节点将复制期间修改的操作记录同步到复制节点,即可完成同步过程。

Redis 高可用

来看 Redis 的高可用。

Redis 支持主从同步,提供 Cluster 集群部署模式,通过 Sentine l哨兵来监控 Redis 主服务器的状态。

当主挂掉时,在从节点中根据一定策略选出新主,并调整其他从 slaveof 到新主。

选主的策略简单来说有三个:

  • slave 的 priority 设置的越低,优先级越高;

  • 同等情况下,slave 复制的数据越多优先级越高;

  • 相同的条件下 runid 越小越容易被选中。

在 Redis 集群中,sentinel 也会进行多实例部署,sentinel 之间通过 Raft 协议来保证自身的高可用。

Redis Cluster 使用分片机制,在内部分为 16384 个 slot 插槽,分布在所有 master 节点上,每个 master 节点负责一部分 slot。

数据操作时按 key 做 CRC16 来计算在哪个 slot,由哪个 master 进行处理。

数据的冗余是通过 slave 节点来保障。

哨兵

哨兵必须用三个实例去保证自己的健壮性的,哨兵+主从并不能保证数据不丢失,但是可以保证集群的高可用

为啥必须要三个实例呢?

我们先看看两个哨兵会咋样。

M1          R1
S1          S2

master宕机了 s1和s2两个哨兵只要有一个认为你宕机了就切换了,并且会选举出一个哨兵去执行故障,但是这个时候也需要大多数哨兵都是运行的。

那这样有啥问题呢?

M1宕机了,S1没挂那其实是OK的,但是整个机器都挂了呢?

哨兵就只剩下S2个裸J了,没有哨兵去允许故障转移了,虽然另外一个机器上还有R1,但是故障转移就是不执行。

经典的哨兵集群是这样的:

     M1
     S1

R2        R3      
S2        S3  

M1所在的机器挂了,哨兵还有两个,两个人一看他不是挂了嘛,那我们就选举一个出来执行故障转移不就好了。

总结下哨兵组件的主要功能:

  • 集群监控:负责监控 Redis master 和 slave 进程是否正常工作。

  • 消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。

  • 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。

  • 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。

主从

提到这个,就跟我前面提到的数据持久化的RDB和AOF有着比密切的关系了。

我先说下为啥要用主从这样的架构模式,前面提到了单机QPS是有上限的,而且Redis的特性就是必须支撑读高并发的,那你一台机器又读又写,这谁顶得住啊,不当人啊!

但是你让这个master机器去写,数据同步给别的slave机器,他们都拿去读,分发掉大量的请求那是不是好很多,而且扩容的时候还可以轻松实现水平扩容。

master-slave

你启动一台slave 的时候,他会发送一个psync命令给master ,如果是这个slave第一次连接到master,他会触发一个全量复制。

master就会启动一个线程,生成RDB快照,还会把新的写请求都缓存在内存中,RDB文件生成后,master会将这个RDB发送给slave的,slave拿到之后做的第一件事情就是写进本地的磁盘,然后加载进内存,然后master会把内存里面缓存的那些新命名都发给slave。

Jian_Shen_Zer 问了个问题:

主从同步的时候,新的slaver进来的时候用RDB,那之后的数据呢?有新的数据进入master怎么同步到slaver啊

AOF嘛,增量的就像MySQL的Binlog一样,把日志增量同步给从服务就好了

key 失效机制

Redis 的 key 可以设置过期时间,过期后 Redis 采用主动和被动结合的失效机制,一个是和 MC 一样在访问时触发被动删除,另一种是定期的主动删除。

定期+惰性+内存淘汰

缓存常见问题

缓存更新方式

这是决定在使用缓存时就该考虑的问题。

缓存的数据在数据源发生变更时需要对缓存进行更新,数据源可能是 DB,也可能是远程服务。更新的方式可以是主动更新。数据源是 DB 时,可以在更新完 DB 后就直接更新缓存。

当数据源不是 DB 而是其他远程服务,可能无法及时主动感知数据变更,这种情况下一般会选择对缓存数据设置失效期,也就是数据不一致的最大容忍时间。

这种场景下,可以选择失效更新,key 不存在或失效时先请求数据源获取最新数据,然后再次缓存,并更新失效期。

但这样做有个问题,如果依赖的远程服务在更新时出现异常,则会导致数据不可用。

改进的办法是异步更新,就是当失效时先不清除数据,继续使用旧的数据,然后由异步线程去执行更新任务。这样就避免了失效瞬间的空窗期。另外还有一种纯异步更新方式,定时对数据进行分批更新。实际使用时可以根据业务场景选择更新方式。

ps: 异步更新的问题,会导致数据不是最新的。要结合具体的业务。

数据不一致

第二个问题是数据不一致的问题,可以说只要使用缓存,就要考虑如何面对这个问题。

缓存不一致产生的原因一般是主动更新失败,例如更新 DB 后,更新 Redis 因为网络原因请求超时;或者是异步更新失败导致。

解决的办法是,如果服务对耗时不是特别敏感可以增加重试;如果服务对耗时敏感可以通过异步补偿任务来处理失败的更新,或者短期的数据不一致不会影响业务,那么只要下次更新时可以成功,能保证最终一致性就可以。

缓存穿透

缓存穿透。产生这个问题的原因可能是外部的恶意攻击,例如,对用户信息进行了缓存,但恶意攻击者使用不存在的用户id频繁请求接口,导致查询缓存不命中,然后穿透 DB 查询依然不命中。这时会有大量请求穿透缓存访问到 DB。

解决的办法如下。

对不存在的用户,在缓存中保存一个空对象进行标记,防止相同 ID 再次访问 DB。不过有时这个方法并不能很好解决问题,可能导致缓存中存储大量无用数据。

使用 BloomFilter 过滤器,BloomFilter 的特点是存在性检测,如果 BloomFilter 中不存在,那么数据一定不存在;如果 BloomFilter 中存在,实际数据也有可能会不存在。非常适合解决这类的问题。

缓存击穿

缓存击穿,就是某个热点数据失效时,大量针对这个数据的请求会穿透到数据源。

解决这个问题有如下办法。

可以使用互斥锁更新,保证同一个进程中针对同一个数据不会并发请求到 DB,减小 DB 压力。

使用随机退避方式,失效时随机 sleep 一个很短的时间,再次查询,如果失败再执行更新。

针对多个热点 key 同时失效的问题,可以在缓存时使用固定时间加上一个小的随机数,避免大量热点 key 同一时刻失效。

缓存雪崩

缓存雪崩,产生的原因是缓存挂掉,这时所有的请求都会穿透到 DB。

解决方法:

使用快速失败的熔断策略,减少 DB 瞬间压力;

使用主从模式和集群模式来尽量保证缓存服务的高可用。

实际场景中,这两种方法会结合使用。

老朋友都知道为啥我没有大篇幅介绍这个几个点了吧,我在之前的文章实在是写得太详细了,忍不住点赞那种,我这里就不做重复拷贝了。

Redis 相比 Memcached 有哪些优势?

Redis 相比 Memcache 有以下的优势:

数据结构:Memcache 只支持 key value 存储方式,Redis 支持更多的数据类型,比如 Key value、hash、list、set、zset;

多线程:Memcache 支持多线程,Redis 支持单线程;CPU 利用方面 Memcache 优于 Redis;

持久化:Memcache 不支持持久化,Redis 支持持久化;

内存利用率:Memcache 高,Redis 低(采用压缩的情况下比 Memcache 高);

过期策略:Memcache 过期后,不删除缓存,会导致下次取数据数据的问题,Redis 有专门线程,清除缓存数据;

适用场景:Redis 适用于对读写效率要求都很高,数据处理业务复杂和对安全性要求较高的系统。Redis 只使用单核,而 Memcached 可以使用多核,所以平均每一个核上 Redis 在存储小数据时比 Memcached 性能更高。Memcached 适用于在动态系统中减少数据库 负载,提升性能,做缓存,提高性能(适合读多写少,对于数据量比较大,可以采用 sharding)。

Memcache 与 Redis 的区别都有哪些?

它们的主要区别有:

支持的数据类型不同,Memcache 支持的数据类型简单,只有 key-value,而 Redis 支持多种数据结构,包括字符串、列表、集合、有序集合、哈希等;

Redis 支持数据持久化,Memcache 不支持数据持久化。如果服务器重启,Memcache 中的数据会被清空,而 Redis 开启持久化的话,重启后可以自动加载数据恢复到内存中;

它们的底层结构、实现方式的不同,服务端和客户端通信的协议也不同。

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

Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。

所以 Redis 具有快速和数据持久化的特征。

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

在内存越来越便宜的今天, Redis 将会越来越受欢迎。如果设置了最大使用的内存, 则数据已有记录数达到内存限值后不能继续插入新值。

MySQL里有2000w数据,Redis中只存20w的数据

如何保证Redis中的数据都是热点数据?

Redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。

LFU

LRU

Reids 6 种淘汰策略:

noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。大多数写命令都会导致占用更多的内存(有极少数会例外。

allkeys-lru: 所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。

volatile-lru: 只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。

allkeys-random: 所有key通用; 随机删除一部分 key。

volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。

volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。

Redis 还提供的高级工具

像慢查询分析、性能测试、Pipeline、事务、Lua自定义命令、Bitmaps、HyperLogLog、/订阅、Geo等个性化功能。

ps: 慢查询分析默认只有 100 条,可以定时持久化道数据库,然后删除。

Pipeline

Pipeline 有什么好处,为什么要用 pipeline?

可以将多次 IO 往返的时间缩减为一次,前提是 pipeline 执行的指令之间没有因果相关性。

使用 Redis-benchmark 进行压测的时候可以发现影响 Redis 的 QPS 峰值的一个重要因素是 pipeline 批次指令的数目。

使用 pipeline(管道)的好处在于可以将多次 I/O 往返的时间缩短为一次,但是要求管道中执行的指令间没有因果关系。

用 pipeline 的原因在于可以实现请求/响应服务器的功能,当客户端尚未读取旧响应时,它也可以处理新的请求。如果客户端存在多个命令发送到服务器时,那么客户端无需等待服务端的每次响应才能执行下个命令,只需最后一步从服务端读取回复即可。

请说明一下 Redis 的批量命令与 Pipeline 有什么不同?

它们主要的不同有:

原子性不同;批量命令操作需要保证原子性的,Pipeline 执行是非原子性的;

支持的命令不同;批量命令操作是一个命令对应多个 key,Pipeline 支持多个命令的执行;

实现方式的不同;批量命令操作是由 Redis 服务端实现的,而 Pipeline 是需要服务端和客户端共同实现的。

请介绍一下 Redis 的 Pipeline(管道),以及使用场景

因为 Redis 是基于 TCP 协议的请求/响应服务器,每次通信都需要经过 TCP 协议的三次握手,所以当需要执行的命令足够大时,会产生很大的网络延迟。

并且网络的传输时间成本和服务器开销没有计入其中,总的延迟可能更大。

Pipeline 主要就是为了解决存在这种情况的场景,对此存在类似的场景都可以考虑使用 Pipeline。

可以适用场景有:如果存在批量数据需要写入 Redis,并且这些数据允许一定比例的写入失败,那么可以使用 Pipeline,后期再对失败的数据进行补偿即可。

Redis 集群方案什么情况下会导致整个集群不可用?

有 A, B, C 三个节点的集群,在没有复制模型的情况下,如果节点 B 失败了, 那么整个集群就会以为缺少 5501-11000 这个范围的槽而不可用。

Redis 的内存用完了会发生什么?

如果达到设置的上限,Redis 的写命令会返回错误信息( 但是读命令还可以正常返回。) 或者你可以将 Redis 当缓存来使用配置淘汰机制, 当 Redis 达到内存上限时会冲刷掉旧的内容。

Redis 集群最大节点个数是多少?

16384个。

ps: 这个问题比较无聊

Redis 到底是怎么实现“附近的人”

GEOADD key longitude latitude member [longitude latitude member ...]

将给定的位置对象(纬度、经度、名字)添加到指定的key。

其中,key为集合名称,member为该经纬度所对应的对象。

在实际运用中,当所需存储的对象数量过多时,可通过设置多key(如一个省一个key)的方式对对象集合变相做sharding,避免单集合数量过多。

成功插入后的返回值:

(integer) N

其中N为成功插入的个数。

MySQL里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?

Redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。

Redis 过期键的删除策略?

1、 定时删除: 在设置键的过期时间的同时,创建一个定时器 timer). 让定时器在键的过期时间来临时, 立即执行对键的删除操作。

2、 惰性删除: 放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是 否过期, 如果过期的话, 就删除该键;如果没有过期, 就返回该键。

3、 定期删除: 每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至 于要删除多少过期键, 以及要检查多少个数据库, 则由算法决定。

mySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据

相关知识:Redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略(回收策略)。

Redis key的过期时间和永久有效分别怎么设置?

EXPIRE 和 PERSIST 命令。

请用Redis和任意语言实现一段恶意登录保护的代码,

限制1小时内每用户Id最多只能登录5次。具体登录函数或功能用空函数即可,不用详细写出。

用列表实现: 列表中每个元素代表登陆时间,只要最后的第5次登陆时间和现在时间差不超过1小时就禁止登陆.

用 python 写的代码如下:

#!/usr/bin/env python3 

import Redis 
import sys 
import time 
r = Redis.StrictRedis(host=127.0.0.1, port=6379, db=0) 
try: id = sys.argv[1] 
except: print(input argument error) sys.exit(0) 
if r.llen(id) >= 5 and time.time()  float(r.lindex(id, 4)) <= 3600: print(you are forbidden logining) 
else: print(you are allowed to login) 
r.lpush(id, time.time()) 
# login_func()

Redis 安全

Redis 如何设置密码及验证密码?

Redis 密码设置有两种方式:

修改配置文件,需要重启 Redis。在 redis.conf 中可以找到 requirepass 参数,设置 Redis 的访问密码。配置方法为:requirepass 访问密码。

使用命令设置,不需要重启 Redis。使用命令设置的方法为:config set requirepass 访问密码。如果需要查询密码,可以使用 config get requirepass 命令。如果需要验证密码,可以使用 auth 访问密码,再执行 config get requirepass 获取。

需要注意的是,通过这种方式设置访问密码,如果 redis.conf 配置文件中没有设置对应的访问密码,那么服务器重启后访问密码会失效。

修改配置不重启 Redis 会实时生效吗?

因为 Redis 在重启才能加载配置项中的配置,所以需要重启才能生效。

针对运行实例,有许多配置选项可以通过 CONFIG SET 命令进行修改,而无需执行任何形式的重启。

从 Redis 2.2 开始,可以从 AOF 切换到 RDB 的快照持久性或其他方式而不需要重启 Redis。

检索 ‘CONFIG GET *’ 命令获取更多信息。

但偶尔重新启动是必须的,如为升级 Redis 程序到新的版本,或者当你需要修改某些目前 CONFIG 命令还不支持的配置参数的时候。

可以介绍一些 Redis 常用的安全设置吗?

常用的设置有:

  1. 端口设置

只允许信任的客户端发送过来的请求,对其他所有请求都拒绝。如果存在暴露在外网的服务,那么需要使用防火墙阻止外部访问 Redis 端口。

  1. 身份验证

使用 Redis 提供的身份验证功能,在 redis.conf 文件中进行配置生效,客户端可以发送(AUTH 密码)命令进行身份认证。

  1. 禁用特定的命令集

可以考虑禁止一些容易产生安全问题的命令,预防被人恶意操作。

如何设置 Redis 的最大连接数?查看 Redis 的最大连接数?查看 Redis 的当前连接数?

设置 Redis 的最大连接数的命令为:

redis-server -maxclients 100000(连接数); 

查看 Redis 最大连接数的命令为:

config get maxclients 

查看 Redis 连接数的命令为:

info 命令

内存消耗

内存消耗

Redis 的内存消耗分类有哪些?

内存统计使用什么命令?

内存消耗可以分类为:

对象内存:该内存占用最大,存储用户的所有数据。包括所有 key 的大小和 value 的大小。

缓冲内存:主要有客户端缓存、复制积压缓存、AOF 缓存等;

内存碎片:对 key 数据更新,数据过期等都可能产生内存碎片。

可以使用 info 命令来获取内存统计使用信息。

怎么发现 bigkey?

可以使用 redis-cli –bigkeys 命令统计 bigkey 的分布。

也可以在生产环节下执行 debug object key 命令查看 serializedlength 属性,获得 key 对应的 value 序列化之后的字节数。

什么是 bigkey?有什么影响?

bigkey 是指键值占用内存空间非常大的 key。

例如一个字符串 a 存储了 200M 的数据。

bigkey 的主要影响有:

  • 网络阻塞;获取 bigkey 时,传输的数据量比较大,会增加带宽的压力。

  • 超时阻塞;因为 bigkey 占用的空间比较大,所以操作起来效率会比较低,导致出现阻塞的可能性增加。

  • 导致内存空间不平衡;一个 bigkey 存储数据量比较大,同一个 key 在同一个节点或服务器中存储,会造成一定影响。

高并发处理(21 题)

1. Redis 常见性能问题和解决方案有哪些?

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

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

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

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

  • 尽量避免在压力很大的主库上增加从库;

  • 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3….;这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变。

2. MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?

因为内存的空间是有限的,所以 Redis 淘汰机制主要为了解决在某个时间点,Redis 中存在很多过期键,定期删除策略随机抽查时没有抽查到,并且也没有走惰性删除策略时,大量过期键占用内存的问题。

如果内存只能存 20w 数据,而我们需要存储 2000w 的数据时,自然就需要对多出来的数据进行删除或覆盖,保证内存中存储的数据都是热数据。

所以当 Redis 内存数据集的大小上升到一定数量时,就会执行数据淘汰策略。

3. 假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?

我们可以使用 keys 命令和 scan 命令,但是会发现使用 scan 更好。

    1. 使用 keys 命令

直接使用 keys 命令查询,但是如果是在生产环境下使用会出现一个问题,keys 命令是遍历查询的,查询的时间复杂度为 O(n),数据量越大查询时间越长。

而且 Redis 是单线程,keys 指令会导致线程阻塞一段时间,会导致线上 Redis 停顿一段时间,直到 keys 执行完毕才能恢复。

这在生产环境是不允许的。除此之外,需要注意的是,这个命令没有分页功能,会一次性查询出所有符合条件的 key 值,会发现查询结果非常大,输出的信息非常多。所以不推荐使用这个命令。

    1. 使用 scan 命令

scan 命令可以实现和 keys 一样的匹配功能,但是 scan 命令在执行的过程不会阻塞线程,并且查找的数据可能存在重复,需要客户端操作去重。

因为 scan 是通过游标方式查询的,所以不会导致 Redis 出现假死的问题。

Redis 查询过程中会把游标返回给客户端,单次返回空值且游标不为 0,则说明遍历还没结束,客户端继续遍历查询。

scan 在检索的过程中,被删除的元素是不会被查询出来的,但是如果在迭代过程中有元素被修改,scan 不能保证查询出对应元素。

相对来说,scan 指令查找花费的时间会比 keys 指令长。

4. 什么是缓存穿透?怎么解决?

大量的请求瞬时涌入系统,而这个数据在 Redis 中不存在,所有的请求都落到了数据库上把数据库打死。

造成这种情况的原因有系统设计不合理、缓存数据更新不及时,或爬虫等恶意攻击。

解决办法有:

1. 使用布隆过滤器

将查询的参数都存储到一个 bitmap 中,在查询缓存前,再找个新的 bitmap,在里面对参数进行验证。

如果验证的 bitmap 中存在,则进行底层缓存的数据查询,如果 bitmap 中不存在查询参数,则进行拦截,不再进行缓存的数据查询。

2. 缓存空对象

如果从数据库查询的结果为空,依然把这个结果进行缓存,那么当用 key 获取数据时,即使数据不存在,Redis 也可以直接返回结果,避免多次访问数据库。

但是缓存空值的缺点是:

如果存在黑客恶意的随机访问,造成缓存过多的空值,那么可能造成很多内存空间的浪费。

但是也可以对这些数据设置很短的过期时间来控制;

如果查询的 key 对应的 Redis 缓存空值没有过期,数据库这时有了新数据,那么会出现数据库和缓存数据不一致的问题。

但是可以保证当数据库有数据后更新缓存进行解决。

5. 什么是缓存雪崩?怎么解决?

缓存雪崩是指当大量缓存失效时,大量的请求访问直接请求数据库,导致数据库服务器无法抗住请求或挂掉的情况。

这时网站常常会出现 502 错误,导致网站不可用问题。

在预防缓存雪崩时,建议遵守以下几个原则:

  1. 合理规划缓存的失效时间,可以给缓存时间加一个随机数,防止统一时间过期;

  2. 合理评估数据库的负载压力,这有利于在合理范围内部分缓存失,数据库也可以正常访问;

  3. 对数据库进行过载保护或应用层限流,这种情况下一般是在网站处于大流量、高并发时,服务器整体不能承受时,可以采用的一种限流保护措施;

最后还可以考虑多级缓存设计,实现缓存的高可用。

6. 如果有大量的 key 需要设置同一时间过期,一般需要注意什么?

如果有大量的 key 在同一时间过期,那么可能同一秒都从数据库获取数据,给数据库造成很大的压力,导致数据库崩溃,系统出现 502 问题。

也有可能同时失效,那一刻不用都访问数据库,压力不够大的话,那么 Redis 会出现短暂的卡顿问题。

所以为了预防这种问题的发生,最好给数据的过期时间加一个随机值,让过期时间更加分散。

7. Redis 哨兵和集群的区别是什么?

Redis 的哨兵作用是管理多个 Redis 服务器,提供了监控、提醒以及自动的故障转移的功能。

哨兵可以保证当主服务器挂了后,可以从从服务器选择一台当主服务器,把别的从服务器转移到读新的主机。

Redis 哨兵的主要功能有:

  • 集群监控:对 Redis 集群的主从进程进行监控,判断是否正常工作。

  • 消息通知:如果存在 Redis 实例有故障,那么哨兵可以发送报警消息通知管理员。

  • 故障转移:如果主机(master)节点挂了,那么可以自动转移到从(slave)节点上。

  • 配置中心:当存在故障时,对故障进行转移后,配置中心会通知客户端新的主机(master)地址。

Redis 的集群的功能是为了解决单机 Redis 容量有限的问题,将数据按一定的规则分配到多台机器,对内存的每秒访问不受限于单台服务器,可受益于分布式集群高扩展性。

9. 请介绍几个可能导致 Redis 阻塞的原因

Redis 产生阻塞的原因主要有内部和外部两个原因导致:

    1. 内部原因

如果 Redis 主机的 CPU 负载过高,也会导致系统崩溃;

数据持久化占用资源过多;

对 Redis 的 API 或指令使用不合理,导致 Redis 出现问题。

    1. 外部原因

外部原因主要是服务器的原因,例如服务器的 CPU 线程在切换过程中竞争过大,内存出现问题、网络问题等。

10. Redis 的同步机制了解么?

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

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

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

11. 如何处理 Redis 集群中 big key 和 hot key?

对于 big key 先分析业务存在大键值是否合理,如果不合理我们可以把它们拆分为多个小的存储。

或者看是否可以使用别的存储介质存储这种 big key 解决占用内存空间大的问题。

对于 hot key 我们可以在其他机器上复制这个 key 的备份,让请求进来的时候,去随机的到各台机器上访问缓存。

所以剩下的问题就是如何做到让请求平均的分布到各台机器上。

12. 怎么去发现 Redis 阻塞异常情况?

可以从以下两方面准备:

(1). 使用 Redis 自身监控系统

使用 Redis 自身监控系统,可以对 CPU、内存、磁盘、命令耗时等阻塞问题进行监控,当监控系统发现各个监控指标存在异常的时候,发送报警。

(2). 使用应用服务监控

当 Redis 存在阻塞时,应用响应时间就会延长,应用可以感知发现问题,并发送报警给管理人员。

ps: redis 本身有 slow log

13. 你知道有哪些 Redis 分区实现方案?

客户端分区就是在客户端就已经决定数据会被存储到哪个 Redis 节点或者从哪个 Redis 节点读取。

大多数客户端已经实现了客户端分区。

代理分区意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。

代理根据分区规则决定请求哪些 Redis 实例,然后根据 Redis 的响应结果返回给客户端。

Redis 和 Memcached 的一种代理实现就是 Twemproxy。

查询路由(Query routing)的意思是客户端随机地请求任意一个 Redis 实例,然后由 Redis 将请求转发给正确的 Redis 节点。

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

14. Redis 集群之间的复制方式是?

需要知道 Redis 的复制方式前,需要知道主从复制(Master-Slave Replication)的工作原理,具体为:

  1. Slave 从节点服务启动并连接到 Master 之后,它将主动发送一个 SYNC 命令;

  2. Master 服务主节点收到同步命令后将启动后台存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕后,Master 将传送整个数据库文件到 Slave,以完成一次完全同步;

  3. Slave 从节点服务在接收到数据库文件数据之后将其存盘并加载到内存中;

  4. 此后,Master 主节点继续将所有已经收集到的修改命令,和新的修改命令依次传送给 Slaves,Slave 将在本次执行这些数据修改命令,从而达到最终的数据同步。

整个执行的过程都是使用异步复制的方式进行复制。

15. 什么是数据库缓存双写一致性?

当一个数据需要更新时,因为不可能做到同时更新数据库和缓存,那么此时读取数据的时候就一定会发生数据不一致问题,而数据不一致问题在金融交易领域的系统中是肯定不允许的。

解决办法:读的时候先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。

更新的时候,先更新数据库,然后再删除缓存。

16. 为什么要做 Redis 分区?

分区可以让 Redis 管理更大的内存,Redis 将可以使用所有机器的内存。

如果没有分区,你最多只能使用一台机器的内存。

分区使 Redis 的计算能力通过简单地增加计算机得到成倍提升,Redis 的网络带宽也会随着计算机和网卡的增加而成倍增长。

17. 分布式 Redis 是前期做还是后期规模上来了再做好,为什么?

为防止以后扩容增加难度,最好的办法就是一开始就使用分布式。

即便只有一台服务器,也可以一开始就让 Redis 以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。

刚开始操作时比较麻烦,但是当数据不断增长,需要更多的 Redis 服务器时,只需要将 Redis 实例从一台服务迁移到另外一台服务器即可,而不用考虑重新分区的问题。

一旦添加了另一台服务器,只需要将一半的 Redis 实例从第一台机器迁移到第二台机器。

18. 如果有大量的 key 需要设置同一时间过期,一般需要注意什么?

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

一般需要在时间上加一个随机值,使得过期时间分散一些。

19. 什么是分布式锁?有什么作用?

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。

在单机或者单进程环境下,多线程并发的情况下,使用锁来保证一个代码块在同一时间内只能由一个线程执行。

比如 Java 的 Synchronized 关键字和 Reentrantlock 类。

分布式锁的作用是当多个进程不在同一个系统中,用分布式锁可以控制多个进程对资源的访问。

20. 分布式锁可以通过什么来实现?

可以使用 Memcached 实现分布式锁:Memcached 提供了原子性操作命令 add,线程获取到锁。key 已存在的情况下,则 add 失败,获取锁也失败。

也可以使用 Redis 实现分布式锁:Redis 的 setnx 命令为原子性操作命令。只有在 key 不存在的情况下,才能 set 成功。和 Memcached 的 add 方法比较类似。

还可以使用 ZooKeeper 分布式锁:利用 ZooKeeper 的顺序临时节点,来实现分布式锁和等待队列。

还有 Chubby 实现分布式锁:Chubby 底层利用了 Paxos 一致性算法,实现粗粒度分布式锁服务。

21. 介绍一下分布式锁实现需要注意的事项?

分布式锁实现需要保证以下几点:

互斥性:任意时刻,只有一个资源能够获取到锁;

容灾性:在未成功释放锁的的情况下,一定时限内能够恢复锁的正常功能;

统一性:加锁和解锁保证同一资源来进行操作。

持久化(8 题)

1. Redis 中的 bgsave 的原理是什么?

你给出两个词汇就可以了,fork 和 cow。

fork 是指 Redis 通过创建子进程来进行 bgsave 操作,cow 指的是 copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

2. 如何选择合适的持久化方式?

在实际的生产环境当中对于选择 RDB 持久化方式还是 AOF 持久化方式都需要根据数据量、应用对数据的安全要求、硬件相关预算等情况综合考虑,同时也可以考虑主从复制的策略,可以设置主机(master)和从机(slave)使用不同的持久化方案。

对此以下只是简单的进行一个选择的介绍,具体在实际的开发应用过程中还需要根据实际情况综合选择。

如果在一段时间内对 Redis 里的数据完全丢弃也不会有什么影响的情况,那么选择 RDB 持久化方式比选择 AOF 持久化更有利;但是如果需要预防数据秒级别的丢失时,就只能选择 AOF 持久化方式了。

当 Redis 的结构是主从架构的情况下,可以考虑使用读写分离来分担 Redis 的读请求压力,也预防主机(master)宕机后继续提供服务。

这时候可以采用这样一种策略:关闭主机(master)的持久化功能,这样保持主机(master)的高效性,然后关闭从机(slave)的 RDB 持久化,开启 AOF 持久化,同时建议关闭 AOF 的自动重写功能,通过设置一个定时任务,在应用访问次数比较少的时间点再通过调用 bgrewriteaof 命令进行自动重写,为了防止持久化数据的丢失,建议设置一个定时器在某个时间点对持久化文件进行备份并标记好备份时间点。

3. Redis 持久化数据和缓存怎么做扩容?

如果 Redis 被当做缓存使用,使用一致性哈希实现动态扩容缩容。

如果 Redis 被当做一个持久化存储使用,必须使用固定的 keys-to-nodes 映射关系,节点的数量一旦确定不能变化。

否则的话(即 Redis 节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有 Redis 集群可以做到这样。

4. Redis 持久化机制有哪些?

Redis 主要支持的持久化机制为 RDB(快照)和 AOF(追加文件)。

RDB 持久化是在指定时间间隔内保存数据快照到硬盘中。但 RDB 的持久化方式没有办法实现实时性的持久化。

当应用使用 RDB 持久化后,如果 Redis 系统发生崩溃,那么使用 RDB 恢复数据时,恢复后的数据中,存在丢失最近一次生成快照之后更改的所有数据。

所以 RDB 持久化不适用于丢失一部分数据也会对应用造成很大影响的备份中。

AOF 持久化是把命令追加到操作日志的尾部,然后保存所有历史操作。AOF 主要是解决数据持久化的实时性。

Redis 服务器默认开启 RDB,关闭 AOF;要开启 AOF,需要在配置文件中配置:

appendonly yes

AOF 持久化相对于 RDB 持久化的优点在于可以实时的对 Redis 缓存进行写入记录,保证快速恢复缓存时的完整性。

5. Redis 持久化机制 AOF 和 RDB 有哪些不同之处?

RDB 和 AOF 的区别:

持久化的方式不同:RDB 持久化是在指定时间间隔内保存数据快照到硬盘中(快照的方式)。

AOF 持久化是把命令追加到操作日志的尾部,然后保存所有历史操作(追加文件)。

恢复的数据安全性不同:RDB 恢复数据时,恢复后的数据中,存在丢失最近一次生成快照之后更改的所有数据;AOF 持久化的数据实时性和安全性更高。

缓存恢复的速度不同:RDB 产生的文件紧凑压缩高,读取 RDB 文件恢复时速度比 AOF 快;由于 AOF 实时追加写命令,所以 AOF 的缓存文件体积比较大,但是可以通过重写(rewrite)压缩 AOF 持久化文件体积。

6. 请介绍一下 RDB 持久化机制的优缺点

它的优点有:

RDB 是在某个时间点上的数据快照,非常适合使用在全量备份的情况,生成的 RDB 是一个紧凑压缩的二进制文件。快照产生的 RDB 文件可以拷贝到远程机器或文件系统中,用于灾难恢复;

RDB 的加载恢复数据速度快于 AOF 的恢复方式。

它的缺点有:

因为 bgsave 每次运行都要执行 fork 操作创建子进程,操作比较繁琐,如果实时存储快照会导致成本过高,所以 RDB 只能在特定条件下进行一次持久化,从而容易出现数据丢失的情况;

RDB 文件是一个特定二进制格式保存的文件,Redis 的版本更新过程中,有对 RDB 的版本格式修改,会出现老版本的 RDB 文件无法兼容新版本的 RDB 格式问题。

7. 请介绍一下 AOF 持久化机制的优缺点

它的优点有:

AOF 持久化可以保证数据非常完整,故障恢复时相对 RDB 持久化丢失的数据最少;

由于 AOF 是可以实时对缓存命令追加到 AOF 文件的末尾,所以可以对历史操作的缓存命令进行处理。

它的缺点有:

由于 AOF 持久化是不断对 AOF 文件进行追加记录的,会导致 AOF 文件体积很大,极端的情况下可能会出现 AOF 文件用完硬盘的可用空间;但是 Redis 2.4 版本以后支持 AOF 自动重写,有效的解决 AOF 文件过大的问题;

当 AOF 文件体积很大时,会出现恢复速度慢,对性能影响大的问题;

当开启 AOF 后,对 QPS 会有一定影响,相对 RDB 来说,写 QPS 会下降

8. 如果 AOF 文件的数据出现异常,Redis 服务怎么处理?

如果 AOF 文件数据出现异常,为了保证数据的一致性,Redis 服务器会拒绝加载 AOF 文件。

可以尝试使用 redis-check-aof -fix 命令修复。

五、集群、复制(12 题)

1. 是否使用过 Redis 集群,集群的原理是什么?

使用过 Redis 集群,它的原理是:

  • 所有的节点相互连接

  • 集群消息通信通过集群总线通信,集群总线端口大小为客户端服务端口 + 10000(固定值)

  • 节点与节点之间通过二进制协议进行通信

  • 客户端和集群节点之间通信和通常一样,通过文本协议进行

  • 集群节点不会代理查询

  • 数据按照 Slot 存储分布在多个 Redis 实例上

  • 集群节点挂掉会自动故障转移

  • 可以相对平滑扩/缩容节点

Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0~16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。

2. Redis 集群方案什么情况下会导致整个集群不可用?

Redis 没有使用哈希一致性算法,而是使用哈希槽。

Redis 中的哈希槽一共有 16384 个,计算给定密钥的哈希槽,我们只需要对密钥的 CRC16 取摸 16384。

假设集群中有 A、B、C 三个集群节点,不存在复制模式下,每个集群的节点包含的哈希槽如下:

  • 节点 A 包含从 0 到 5500 的哈希槽;

  • 节点 B 包含从 5501 到 11000 的哈希槽;

  • 节点 C 包含从 11001 到 16383 的哈希槽;

这时,如果节点 B 出现故障,整个集群就会出现缺少 5501 到 11000 的哈希槽范围而不可用。

3. Redis 集群架构模式有哪几种?

Redis 集群架构是支持单节点单机模式的,也支持一主多从的主从结构,还支持带有哨兵的集群部署模式。

4. 说说 Redis 哈希槽的概念?

Redis 集群并没有使用一致性 hash,而是引入了哈希槽的概念。

Redis 集群有 16384(2^14)个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。

5. Redis 集群的主从复制模型是怎样的?

Redis 集群支持的主从复制,数据同步主要有两种方法:一种是全量同步,一种是增量同步。

1. 全量同步

刚开始搭建主从模式时,从机需要从主机上获取所有数据,这时就需要 Slave 将 Master 上所有的数据进行同步复制。

复制的步骤为:

从服务器发送 SYNC 命令,链接主服务器;

主服务器收到 SYNC 命令后,进行存盘的操作,并继续收集后续的写命令,存储缓冲区;

存盘结束后,将对应的数据文件发送到 Slave 中,完成一次全量同步;

主服务数据发送完毕后,将进行增量的缓冲区数据同步;

Slave 加载数据文件和缓冲区数据,开始接受命令请求,提供操作。

2. 增量同步

从节点完成了全量同步后,就可以正式的开启增量备份。

当 Master 节点有写操作时,都会自动同步到 Slave 节点上。Master 节点每执行一个命令,都会同步向 Slave 服务器发送相同的写命令,当从服务器接收到命令,会同步执行。

6. Redis 集群会有写操作丢失吗?为什么?

Redis 集群中有可能存在写操作丢失的问题,但是丢失概率一般可以忽略不计。

主要是 Redis 并没有一个机制保证数据一定写不丢失。

在以下问题中可能出现键值丢失的问题:

  1. 超过内存的最大值,键值被清理

Redis 中可以设置缓存的最大内存值,当内存中的数据总量大于设置的最大内存值,会导致 Redis 对部分数据进行清理,导致键值丢失的问题。

  1. 大量的 key 过期,被清理

这种情况比较正常,只是因为键值设置的时间过期了,被自动清理了。

  1. Redis 主库服务器故障重启

由于 Redis 的数据是缓存在内存中的,如果 Redis 主库服务器出现故障重启,会出现数据被清空的问题。这时可能导致从库的数据同步被清空。如果有使用数据持久化,那么故障重启后数据是可以自动恢复的。

  1. 网络问题

可能出现网络故障,导致短时间内数据写入失败。

7. Redis 集群之间是如何复制的?

使用异步复制。

9. Redis 集群如何选择数据库?

Redis 集群不支持选择数据库操作,默认在 0 数据库。

10. Redis 集群方案应该怎么做,有哪些方案?

Redis 可以使用的集群方法有:

Redis cluster 3.0:这是 Redis 自带的集群功能,它采用的分布式算法是哈希槽,而不是一致性 hash。支持主从结构,可以扩展多个从服务器,当主节点挂了可以很快的切换到一个从节点做主节点,然后从节点都读取到新的主节点。

Twemproxy,它是 Twitter 开源的一个轻量级后端代理,可以管理 Redis 或 Memcache 集群。它相对于 Redis 集群来说,易于管理。它的使用方法和 Redis 集群没有任何区别,只需要设置好多个 Redis 实例后,在本需要连接 redis 的地方改为连接 Twemproxy,它会以一个代理的身份接收请求并使用一致性 hash 算法,将请求转接到具体 Redis 节点,将结果再返回 Twemproxy。对于客户端来说,Twemproxy 相当于是缓存数据库的入口,它不需要知道后端如何部署的。Twemproxy 会检测与每个节点的连接是否正常,如果存在异常节点,则会被剔除,等一段时间后,Twemproxy 还会再次尝试连接被剔除的节点。

Codis,它是一个 Redis 分布式的解决方式,对于应用使用 Codis Proxy 的连接和使用 Redis 服务的没有明显差别,应用能够像使用单机 Redis 一样,让 Codis 底层处理请求转发,不停机的数据迁移等工作。

11. Redis sentinel(哨兵)模式优缺点有哪些?

Redis 哨兵的好处在于可以保证系统的高可用,各个节点可以对故障自动转移。

但缺点是使用的主从模式,主节点单点风险高,主从切换过程可能会出现丢失数据的问题。

12. Redis 的主从复制模式有什么优缺点?

主从复制的模式相对于单节点的好处在于,实行读写分离提高了系统的读写效率,提高了网站数据的读取加载速度。

但是缺点是由于写数据主要在主节点上操作,主节点内存空间有限,并且主节点存在单点风险。

参考资料

https://zhuanlan.zhihu.com/p/423894153

https://zhuanlan.zhihu.com/p/91539644

http://learn.lianglianglee.com/%E6%96%87%E7%AB%A0/%E6%9C%80%E5%85%A8%E7%9A%84%20116%20%E9%81%93%20Redis%20%E9%9D%A2%E8%AF%95%E9%A2%98%E8%A7%A3%E7%AD%94.md

https://www.cnblogs.com/javazhiyin/p/13839357.html