Redis面试复习总结
该文章是建立在已经对Redis有初步了解和使用经验的情况下。
目的是梳理Redis的知识点,查漏补缺。
如果是新手,简易先学习一下什么是Redis以及如何使用。
Redis已经成为绝大多数互联网公司的核心缓存组件,更多关注的应该是如何用好Redis。
在这里推荐几本书,基本囊括了学习Redis的大部分知识:
Redis开发与运维
Redis设计与实现
Redis in Action(中文版叫 Redis实战)
Redis的优缺点
Redis的优点
1、速度快
Redis是基于key-value模型,类似于HashMap,同时,Redis的数据是存储在内存中的,充分利用了纯内存操作的的高性能和HashMap的查找时间优势。
2、丰富的数据类型
Redis支持的五种数据类型:string, list, set, sorted set, hash。
Redis单个Value支持的最大容量限制是1GB。因此可以由很多功能,例如:
- 用Redis 的 List 来做 FIFO 的双向链表,实现一个轻量级高性能的消息队列服务。
- 用Redis 的 Set 可以做高性能的 tag 系统。
3、丰富的特性
Redis提供了诸多特性,例如:
- 订阅发布 Pub/Sub 功能。
- Key 过期策略(内存回收策略)
- 事务
- 支持多台DB
- 计数器
...
在 Redis5.0 新增了 Stream 功能,一个新的强大的支持多播和可持久化的消息队列,功能类似于kafka。
4、持久化存储
Redis支持持久化存储,解决万一Redis挂掉导致的数据丢失问题。
主要持久化方案分为:RDB 和 AOF
Redis的缺点
1、由于 Redis 是内存数据库,所以,单台机器存储的数据量,受限于机器本身的内存大小。虽然Redis自身也有内存回收策略,但还是需要提前预估内存使用和节约内存,如果内存增长过快,需要定期删除过期的Key。
另外,可以使用 Redis Cluster,Codis 等方案,构建Redis集群。
2、进行完整同步,由于需要生成RDB文件并且进行传输,这个时候会占用宿主机的CPU,并且会小号网络带宽。
在 Redis 2.8 版本之后,已经支持部分重同步的功能,但还是有需要完整重同步的情况,例如,新上线一台备机。
3、修改配置文件,进行重启,将硬盘中的持久化数据装载入内存耗时比较久,这个过程中 Redis 无法提供服务。
Redis 和 Memcached 的区别
面试问的比较多,但感觉这个问题很无脑,因为Memcached已经很少很少用到了,这个问题主要还是说 Redis 的优势。
个人感觉,与其花时间记区别,不如好好深入研究一下Redis的特性和内部原理。
Redis的线程模型
Redis内部使用文件事件处理器 file event handler
。
这个文件事件处理器是单线程的,所以,Redis 才叫做单线程的模型。
它采用 IO 多路复用机制,同时监听多个 socket ,根据 socket 上的事件来选择对应的事件处理器进行处理。
文件事件处理器的结构包含4部分:
- 多个 socket
- IO 多路复用程序
- 文件事件分派器
- 事件处理器(连接应答处理器,命令请求处理器,命令恢复处理器)
多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是IO 多路复用程序会监听多个 socket,将 socket 产生的事件放入一个等待队列中,事件分派器每次从等待队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
客户端与Redis的一次通信过程客户端与Redis的一次通信过程:
-
1)客户端 socket01 向 redis 的 server socket 请求建立连接,此时 server socket 会产生一个
AE_READABLE
事件,IO多路复用程序监听到 server socket 产生的事件后,将该事件压入等待队列中。
2)文件事件分派器从队列中获取该时间,交给连接应答处理器
。然后连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的AE_READABLE
事件与命令请求处理器关联。 -
1)假设客户端发送了一个
set key value
请求,此时 redis 中的 socket01 会产生AE_READABLE
事件,IO多路复用程序将该事件压入等待队列,事件分派器从队列中获取该事件。
2)由于前面 socket01 的AE_READABLE
事件已经与命令请求处理器关联,因此事件分派器将事件直接交给命令请求处理器来处理。
3)命令请求处理器读取 socket01 的key value
并在自己内存中完成key value
的设置。
4)操作完成后,它会将 socket01 的AE_READABLE
事件与命令回复处理器关联 -
1)如果此时准备好接受返回结果了,那么 redis 中的 socket01 会产生一个
AE_WRITABLE
事件,同样压入等待队列中。
2)事件分派器找到相关联的命令回复处理器,由命令恢复处理器对 socket02 输入本次操作的结果,比如ok
,之后解除 socket01 的AE_WRITABLE
事件命令回复处理器关联。
这样,便完成了一次通信
为什么 Redis 单线程模型也能效率这么高
1、纯内存操作。
Redis为了达到最快的读写速度,将数据全部都读取到内存中,并通过异步的方式将数据持久化到磁盘,所以Redis具有快速 和 持久化 的特性。
如果不放在内存中,硬盘 I/O 将严重影响 Redis 性能
2、核心是基于非阻塞 IO 的多路复用机制
3、单线程避免了多线程的频繁上下文切换问题
4、Redis 全部使用Hash结构,根据Key查找Value的时间复杂度为O(1)。
还有一些特殊的数据结构,同时对数据存储进行了优化,例如:
压缩表:对端数据进行压缩存储
跳表:使用有序的数据结构加快读取速度。
Redis 的持久化方式
持久化方式
1、【全量】RDB持久化
是指在指定的时间间隔内,将内存中 数据集快照 写入磁盘。
实际操作过程是:fork一个子进程,先将数据集写入临时文件,写入成功后,再替换上一次RDB的持久化文件。用二进制压缩存储。
2、【增量】AOF持久化
以日志的形式记录服务器所处理的每一个 写,删除操作,查询操作不会记录,以文本方式记录,可以打开文件看到详细的操作记录。
RDB优缺点
优点
1、可以灵活设置备份频率和周期。
2、非常适合冷备份,对于灾难恢复,RDB是非常不错的选择。
因为我们可以非常轻松的将一个单独文件压缩,然后转移到其他存储介质上。
3、性能最大化。
对于Redis服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后由子进程完成持久化操作。也就是说,RDB对Redis对外提供的读写服务影响非常小,可以让Redis持续保持高性能。
4、恢复更快。
相比于AOF而言,RDB恢复速度更快,更适合数据恢复,特别是在数据量非常大的情况。
缺点
1、无法保证高可用性。如果系统在下一次持久化之前宕机,那么这个过程中的数据将丢失。
(所以实际场景下,需要 RDB 和 AOF 一起使用)
2、RDB是通过子进程写出来完成持久化的,如果数据集很大,可能会导致整个服务器停止较长时间,几百毫秒,甚至1秒钟。
(所以,RDB建议在业务低谷进行,例如凌晨)
AOF优缺点
优点
1、该机制可以带来高可用性,即更高的数据安全性。Redis提供了 3 种AOF同步策略。即秒同步、每修改同步,和不同步。
- 秒同步:事实上秒同步也是异步完成的,其效率也非常高,所差的只是一旦系统出现宕机,那么这一秒的数据将会丢失。
- 修改同步:可以视为同步持久化,即每次发生的数据变化都会被立刻记录到磁盘中,这种方式的效率是最低的。
- 无同步:那就是不同步。
2、该机制对日志文件的写入操作采用的是 append 模式,因此在写入过程中即使出现宕机,也不会破坏日志文件中已经存在的内容。
- 因为以 append-only 模式写入,所以没有任何磁盘寻址开销,性能非常高。
- 另外,如果本次操作只是写入了一般数据就出现了系统崩溃也不用担心。Redis在下一次启动前,可以通过 redis-check-aof 工具来帮助解决数据一致性问题。
3、如果日志过大,redis可以自动启动 rewrite机制。
即使出现后台重写操作,也不会影响客户顿的读写。因为在 rewrite log 的时候,会对其中的指令进行压缩,创建一份需要恢复数据的最小日子出来。再创建新日志文件的时候,老的日志文件照常写入。
当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。
(即将切换磁盘时,redis在写入老磁盘文件的同时,也会新建一个文件用于记录此期间执行的命令。以保证在rewrite有更高的数据安全性)
4、AOF包含一个格式清晰,易于理解的日志文件,用于记录所有的 修改操作。事实上,我们也可以通过AOF文件完成数据重建。
缺点
1、对于相同数量的数据集而言,AOF文件比RDB文件要大,恢复速度也更慢。
2、根据同步策略的不同,AOF在运行效率上要慢于RDB。
秒同步效率是比较高的,不同步的效率和RDB一样。
3、AOF数据恢复会更脆弱。容易出Bug。
如何选择
1、不要仅仅使用RDB,这样数据丢失的风险很大。
2、不要仅仅使用 AOF,因为这样有两个问题。
第一、AOF的数据冷备份没有RDB的冷备份恢复速度快。
第二、RDB每次直接生成数据快照更加健壮,避免AOF这种复杂机制的Bug。
3、Redis支持同时开启两种持久化模式,我们可以综合使用两种方式,是希望达到安全的持久化方式。
a)AOF用来保证数据不丢失,作为数据恢复的第一选择。
b)RDB用来做不同程度的数据冷备,在AOF文件丢失或损坏的情况下,用RDB来快速恢复。
c)同时使用AOF和RDB,那么在Redis重启的时候,会使用AOF来重建数据,因为AOF中的数据更加完整。
在 Redis4.0 版本开始,允许使用 RDB-AOF 混合持久化方式。
可以参考:Redis4.0之RDB-AOF混合持久化
Redis持久化知识点总结(更适合面试)
1、bgsave做镜像全量持久化,AOF做增量持久化。
因为 bgsave 会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要AOF配合使用。
在 Redis 实例重启时,会使用 bgsave 持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现完整恢复重启之前的状态。
2、如果突然机器掉电会怎样?
取决于 AOF 日志 sync 属性的配置。如果不要求性能,在每条写指令时都 sync 一下磁盘,就不会丢失数据,达到最高的数据安全性。
但是在高性能的要求下,每次 sync 是不现实的,一般使用定时 sync ,比如1秒一次,这样最多只丢失1秒的数据。
3、bgsave的原理是什么?
两个词汇 fork 和 cow。
fork:是指 Redis 通过创建子进程来进行 bgsave 操作
cow:是指 copy on write ,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
bgsave 操作后,会产生 RDB 快照文件。
实际案例
实际场景下:主 AOF,从 RDB+AOF
Q:为什么不建议在 主 Redis 上开启 RDB 呢?
A:因为会带来一定时间的阻塞,特别是数据量大的时候。
-
子进程/fork相关的阻塞:在bgsave的时候,redis主进程会fork一个子进程,利用操作系统的写时复制技术,这个子进程在拷贝父进程的时候理论上是很快的,因为并不需要全拷贝,比如主进程虽然占了10g内存,但子进程拷贝他可能只要200毫秒,我认为也就阻塞了200毫秒(此耗时基本跟主进程占用的内存是成正比的),这个具体的时间可以通过统计项info stats 里的last_fork_usec查看。
-
cpu/单线程相关的阻塞:redis主进程是单线程跑在单核cpu上的,如果显示绑定了CPU,则子进程会与主进程共享一个CPU,而子进程进行持久化的时候是非常占CPU(强势90%),因此这种情况也可能导致提供服务的主进程发生阻塞(因此如果需要持久化功能,不建议绑定CPU);
-
内存相关的阻塞:虽然利用写时复制技术可以大大降低进程拷贝的内存消耗,但这也导致了父进程在处理写请求时需要维护修改的内存页,因此这部分内存过大的话(修改页数多或每页占空间大)也会导致父进程的写操作阻塞。(而不巧的是,Linux中TransparentHugePage会将复制内存页面单位有4K变成2M,这对于redis来说是比较不友好的,也是建议优化的,具体可度之)
-
磁盘相关的阻塞:极端情况下,假设整个机器的内存已经所剩无几,触发了内存交换(SWAP),则整个redis的效率将会非常低下(显然这不仅仅针对save/bgsave),因此,关注系统的io情况,也是定位阻塞问题的一种方法。
Redis数据过期策略
Redis的过期策略,就是指当 Redis 中缓存的 key 过期了, Redis如何处理。
提供了 3 种过期策略:
1、被动删除:当读/写 一个已经过期的 key 时,会触发惰性删除策略,直接删除这个过期的 key。
2、定期删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已经过期的 key。
3、主动删除:当前已用内存,超过设定的 maxmemory 限制时,触发主动清理策略,即内存淘汰策略(下面讲到)。
Redis数据淘汰策略
如何配置
可以通过配置 redis.conf 中的 maxmemory 来开启内存淘汰功能。
Redis 也支持动态配置,无需重启:
config set maxmemory 100000
config set maxmemory-policy noeviction // no-eviction 表示没有使用限制
内存淘汰策略
当Redis内存占用达到限制时,就会触发数据淘汰策略。Redis提供了 6 种数据淘汰策略:
volatile-lru: 从设置过期时间的数据集中,选择最近最少使用的淘汰。
volatile-ttl: 从设置过期时间的数据集中,选择即将要过期的数据淘汰。
volatile-random: 从设置过期时间的数据集中,随机选择数据淘汰。
allkeys-lru: 从数据集中,挑选最近最少使用的淘汰。
allkeys-random: 从数据集中,随机选择数据淘汰。
no-envicition: 禁止驱逐数据
Redis确定驱逐某个键值对后,会删除这个数据,并将数据变更消息发布到本地(AOF持久化)和从机(主从链接)
LRU淘汰机制
Redis的 LRU 并不是严格的LRU实现,只是一个近似的LRU实现。
淘汰机制:在数据集中随机挑选几个键值对,取出其中 lru 最大的键值对淘汰。
所以,redis并不是保证取得所有数据集中最少使用(LRU)的键值对,而只是随机挑选的几个中,最少使用的。
TTL淘汰机制
redis 数据集数据结构中保存了键值对过期时间表,即 redisDb.expires。
淘汰机制:和 LRU 数据淘汰机制类似,TTL淘汰机制是,从过期时间的表中随机挑选几个键值对,取出其中 ttl 最大的键值对淘汰。
所以和LRU类似,redis并不是保证取得的数据是所有过期时间表中最快过期的,而是从随机取得的数据集中选择最快过期的
如何选择淘汰策略
每个淘汰策略都有不同的适用场景:
allkeys-lru:
如果我们的应用对缓存的访问符合幂律分布,也就是存在相对热点的数据,或者我们不太清楚我们的应用缓存访问分布状况,那么建议选择 allkes-lru
策略。
allkeys-random:
如果我们的应用对于缓存 key 的访问概率相等,那么可以使用这个策略
volatile-ttl:
这种策略使得我们可以向 Redis 提示哪些 key 更适合被驱逐
volatile-lru,volatile-ttl:
适合我们将一个 Redis 实例既应用于缓存 又应用于持久化存储的时候,然而我们可以使用两个 Redis 实例来达到相同效果。
Redis 将 key 设置过期将消耗更对内存,因此建议使用 allkeys-lru
策略从而更有效的使用内存
Redis有哪些数据结构
初级:
- 字符串 string
- 字典 hash
- 列表 list
- 集合 set
- 有序集合 sortedset
中级,可以加上下面几个:
- hyperLogLog
- Geo
- Pub / Sub
高级,比如玩过 Redis Module,可以加上下面几个数据结构:
- BloomFilter
- RedisSearch
- Redis-ML
- JSON
另外,Redis5.0 新增加了 Stream 功能,一个新的强大的支持多播的可持久化消息队列,提供类似 Kafka 的功能。
Redis的使用场景
Redis可用的场景非常之多:
- 数据缓存
- 会话缓存
- 消息队列
- 分布式锁
- 交集,并集,差集
- 时效性数据
- 访问频率
- 计数器
- 社交列表
- 记录用户判定信息
- 热门列表与排行榜
- 最新动态
Redis支持的客户端有哪些
Redis 官方推荐使用 Redisson 或 Jedis
Spring-boot 2.x 内置使用 Lettuce
1、Redisson
Redisson ,是一个高级的分布式协调 Redis 客服端,能帮助用户在分布式环境中轻松实现一些 Java 的对象 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。
2、Jedis
Jedis 是 Redis 的 Java 实现的客户端,其 API 提供了比较全面的 Redis 命令的支持。
Redisson 实现了分布式和可扩展的 Java 数据结构,和 Jedis 相比,Jedis 功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等 Redis 特性。
Redisson 的宗旨是促进使用者对 Redis 的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
3、Lettuce
Lettuc e是一个可伸缩线程安全的 Redis 客户端。多个线程可以共享同一个 RedisConnection 。它利用优秀 Netty NIO 框架来高效地管理多个连接。
如何用Redis实现分布式锁
1、使用 SET key value EX NX
命令。
在设置ex的同时,可以设置过期时间。(在往期文章里有讲到)
2、Redlock 方案。
SET指令的方案,适用于单机Redis节点场景下。对于多 Redis 节点场景下,会存在分布式锁丢失问题。
所以 Redis 作者 Antriez 基于分布式环境下提出了一种更高级的分布式锁实现方式:Redlock。
具体方案可以参考文末链接,这里就不细讲了
(如果有什么错误或者建议,欢迎留言指出)
(本文内容是对各个知识点的转载整理,用于个人技术沉淀,以及大家学习交流用)
参考资料:
芋道源码
Redis设计与实现——RDB
Redis设计与实现——AOF
Redis实战——内存淘汰机制
深入剖析 redis 数据淘汰策略
Redis 事务锁CAS实现以及深入误区
Redis内存优化
为什么Redis单线程却能支撑高并发
Redis的使用场景相关:
聊聊 Redis 的使用场景
Redis 应用场景及实例
Redis 常见的应用场景解析
Redis分布式锁相关
Redis分布式锁的正确实现方式(Java版)
Redlock:redis分布式锁最牛逼的实现
Redisson 实现Redis 分布式锁的N种姿势
Redis高可用
Redis Sentnel 实现高可用
Redis Cluster教程