【Redis】八股总结一:基础问题、线程模型、内存管理、事务

762 阅读15分钟

一、基础等

Redis 为什么这么快?

  1. 基于内存。(展开与磁盘相比较更快)
  2. 事件处理模型:单线程事件循环 和 IO多路复用机制。(展开单线程优点,多线程切换及锁竞争缺点等。)
  3. 高效的数据结构。(展开任意几种数据结构)

为什么用redis?

高性能:直接操作内存数据,操作内存较快

高并发:redis支持高并发访问,qps(每秒处理的请求)能达到5w以上

场景:缓存(高并发读)。

分布式缓存常见的技术选型方案有哪些?

Memcached 和 Redis,腾讯有Tendis,开源的Dragonfly和KeyDB

说一下 Redis 和 Memcached 的区别和共同点(死背咯?)

共同点

  1. 都是基于内存的数据库,一般都用来当做缓存使用。
  2. 都有过期策略
  3. 两者的性能都非常高

区别:(数据类型、持久化、集群、单线程)

  1. Redis 支持更丰富的数据类型(支持更复杂的应用场景)。
  2. Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中。而 Memcached 把数据全部存在内存之中
  3. Redis 在服务器内存使用完之后,可以将不用的数据放到磁盘上。Memcached会报异常。
  4. Memcached 没有原生的集群模式
  5. Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型
  6. Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持

Redis和本地缓存的区别

  1. redis支持持久化,本地内存缓存不支持持久化
  2. redis支持分布式及分片集群存储数据更多,而本地缓存通常内存是有限制的
  3. 但是使用redis会有一些网络开销,而本地缓存没有网络开销。

Redis除了缓存还能做什么?Redis一般能干嘛? 注重理解

限流、分布式锁、消息队列、分布式session、缓存

  • 缓存
  • 分布式锁:通过setnx命令实现
  • 限流
  • 消息队列:List数据结构可以作为一个队列
  • 分布式session:利用String或者Hash保存Session,所有服务器都可以访问。

Redis如何根据一个key去找value?(美团)

redis中整体的数据结构

redis中所有数据由redisDB结构体组成,redisDB中有一个指针指向全局哈希字典,这个全局哈希字典中保存了redis的所有键值对。哈希字典由entry数组构成,每个键值对又是一个dictEntrydictEntry分别有key和value,key和value都指向一个redisObject,然后redisObject根据类型和编码才指向具体的底层数据结构。

说一下有缓存情况下查询数据和修改数据的流程。

查询数据:先查缓存,缓存中有则直接返回,缓存中没有去数据库中查,数据库查不到返回null/缓存null;数据库查到了加入缓存,然后再返回给客户端。

修改数据(旁路缓存策略) :先更新数据库,再删除缓存。涉及到问题(为什么删除缓存不更新缓存?为什么不先删缓存再更新数据库?现在这样先更新数据库再删缓存就没问题吗?)

常见的缓存读写策略有哪些?Redis生产问题

  1. 旁路缓存策略(涉及延迟双删策略
  2. 读写穿透策略(调用者操作缓存服务,缓存服务同步完成db和cache的数据一致性)
  3. 写回策略(调用者只操作缓存,其他线程异步持久化到数据库,适用于一致性要求不高的场景。)

Redis的核心数和 节点数是越多越好吗?

  1. 核心数不是 。因为只有主线程执行命令,其他的线程要么执行网络IO读写,要么是子进程进行RDB快照或aof重写。
  2. 节点也不是。因为redis集群是去中心化的,并且节点间通信带来的性能开销也就越大。此外,去中心化的结构也导致我们很难清晰的掌握整个集群的状态,节点状态变化的延迟性也会带来极大的管理成本。

mysql和redis的流量,QPS是多少

mysql的QPS 能有 2k到4k

redis的QPS 能有 5w 以上

数据例如对象 存到redis中是如何序列化的?(pdd,低频)

redisTemplate可以设置序列化器

有4-5种序列化器

  • JdkSerializationRedisSerializer(默认):将pojo类通过ObjectInputStream/ObjectOutputStream进行序列化操作,最终redis-server中将存储字节序列
  • StringRedisSerializer:将java中的string 直接序列化到redis,二进制字节方式。
  • JacksonJsonRedisSerializer:可以将pojo实例序列化成json格式存储在redis中,也可以将json格式的数据转换成pojo实例。

二、线程模型(很重要)

Redis 是单线程,那怎么监听大量的客户端连接呢?

重要理解

Redis 采用了 I/O 多路复用机制处理大量的客户端 Socket 请求,IO 多路复用机制是指一个线程处理多个 IO 流,其中用到 select/epoll 机制。

(简单的描述IO多路复用)在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听 Socket 和已连接 Socket内核一直 监听 这些 Socket 上的 连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理。

并且是基于epoll模型实现的(扩展epoll的三个阶段)。

Redis 6.0 之前为什么使用单线程?

  1. 首先redis的性能瓶颈 不是CPU,更多情况下在于 内存和网络IO
  2. 然后单线程比较容易维护。因为多线程存在 线程切换、加锁解锁、死锁并发读写数据的问题。
  3. 最后总结,也就是说多线程模型带来的性能提升不能抵消它带来的开发成本和维护成本。

Redis6.0 之后为何引入了多线程? 详解视频: 原理篇-27.Redis网络模型-Redis单线程及多线程网络模型变更_哔哩哔哩_bilibili

  1. 因为redis的性能瓶颈有时候会出现在网络IO上
  2. 所以6.0后使用了多线程 进行网络请求的读和写( IO一般比较耗时
  3. io读取网络请求并将请求解析成操作是多线程,将执行结果IO写回网络也是多线程。但具体执行命令操作仍然是单线程 串行执行的,所以不用担心线程安全问题。

三、持久化机制(重要)

宕机了,Redis 如何避免数据丢失?redis的持久化方式?(小米)

Redis 实现了数据持久化的机制,这个机制会把数据存储到磁盘,这样在 Redis 重启就能够从磁盘中恢复原有的数据

总共有三种持久化方式。

  • aof日志:执行一个写命令后,就会把这个修改命令追加到aof日志中。
  • RDB快照:将某一时刻的redis中的数据,以二进制方式写入磁盘。
  • 混合持久化:redis4.0新加的,结合了aof和RDB的优点。

如何选择 RDB 和 AOF?

首先说RDB和AOF的优缺点。

RDB 优点是数据恢复速度快,但是快照的频率不好把握。频率太低,丢失的数据就会比较多,频率太高,就会影响性能(因为RDB是全量复制)。

AOF 优点是丢失数据少(安全性高),但是数据恢复慢

所以选择

  1. 如果对数据安全性要求没那么高(允许分钟级别的数据丢失), 可以只用RDB。
  2. 如果对数据安全性较高,那就rdb和aof都开启。
  3. 不建议只开启AOF,因为RDB快照可以数据备份、重启更快、还用于主从复制(功能上有点像bin log哈哈)

但是在Redis4.0之后采用了混合方式,介绍一下

四、Redis 性能优化(重要)

什么是大key?

总的来说是占用内存较多的key。

  • 比如String类型key的value值 有5MB
  • 或者如hash和list中成员过多,有10 000 个。
  • 或者hash或list或set中成员1000,但每个成员都很大。

Redis 大 key 有什么危害?如何排查和处理? 小米

危害,:

  • 网络阻塞:每次获取大 key 产生的网络流量较大,增加带宽压力。
  • 超时阻塞:由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时,那么就会阻塞 Redis
  • 内存空间不平衡:比如redis分片集群情况下,有大key的redis节点占用内存会多。

如何排查大key?

排查:

  1. 在使用 Redis 自带的 --bigkeys 参数查找大 key 时,最好选择在从节点上执行该命令,因为主节点上执行时,会阻塞主节点
  2. 通过分析 RDB 文件来找出 big key,利于分析工具

如何解决大key?

解除 :直接使用del删除可能导致 阻塞

  • 分批次删除:每次删除一部分(可能是set list 等的一部分)
  • 异步删除:redis4.0之后采用 UNLINK 命令异步删除

为什么会有 Redis 内存碎片?如何清理 Redis 内存碎片?

原因

  1. Redis 存储存储数据的时候向操作系统申请的内存空间可能会大于数据实际需要的存储空间。
  2. 频繁修改 Redis 中的数据也会产生内存碎片

清理:

  1. 重启节点 会重新整理内存碎片。
  2. Redis4.0-RC3 版本以后自带了内存整理。

五、内存管理(过期策略和淘汰策略)

原理篇-30.Redis内存回收-过期key处理_哔哩哔哩_bilibili

过期

Redis 给缓存数据设置过期时间有啥用?

因为内存有限,不设置过期很有可能OOM

而且很多时候 缓存业务 只需要数据在某段时间有用。

Redis 是如何判断数据是否过期的呢?

Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键)过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间

过期数据的删除策略?

惰性删除只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。

定期删除:每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。

定期删除和惰性删除 有什么优缺点?

定期删除对内存友好一点。但是cpu需要定期抽取key,会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。

惰性删除对内存不友好。但是对cpu友好,因为不需要去主动删除过期key。

所以redis采用定期删除+惰性删除方案

淘汰

Redis内存淘汰机制 哪些?

相关问题:如何保证 Redis 中的数据都是热点数据?

volatile(都是针对有过期时间的key)

  • volatile-lru(least recently used)从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
  • volatile-ttl从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。
  • volatile-random从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。
  • volatile-lfu(least frequently used)从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰。

allkeys(针对所有key)

  • allkeys-lru(least recently used) :当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰。
  • allkeys-lfu(least frequently used) :当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key。
  • no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!

我的总结:

总共有八种,但归纳起来是五种,不同区别是 按针对所有键(allkeys)设置了过期时间的键(volatile) 的区别。

  1. LRU:不经常使用的淘汰,可以用双向链表实现。
  2. LFU:在LRU的基础上,增加了数据的访问频率,淘汰访问频率低的, 确保淘汰数据是非热点的,所以redis里面都是热点key。
  3. 随机删除
  4. TTL:只有volatile策略有,淘汰过期时间最近的key。
  5. 禁止驱逐,直接报错。

这些配置都可以在redis.conf里面配置和修改。\

扩展:那一般如何选择淘汰策略?

  • 根据场景而定

一般设置了永不过期的key肯定是不适合淘汰的(all-key) ,具体场景中也是不想要被淘汰的。

那么就讨论设置了过期时间的key(volatile)中去淘汰:

  1. 随机策略肯定不太行
  2. LRU的优缺点
  3. LFU的优缺点
  4. ttl的优缺点。。。。

扩展:那LRU和LFU各自有什么优缺点和适合什么场景呢?

LRU:淘汰最久没有使用的。

  • 缺点:缓存污染问题低频访问的大批量数据占满内存(比如MySQL中的联表查询占bufferPool) ,对于访问频繁高但最近没有被访问的数据,可能被淘汰。也就是可能淘汰热点数据

LFU:增加访问频次,淘汰访问频率最低的。

  • 优点:
    • 适合热点数据。
    • 避免缓存污染有效避免缓存被大量一次性访问的数据填满,从而淘汰真正重要的热数据。
  • 缺点:
    • 冷启动问题,比如在缓存预热的时候,预热的缓存访问评论较低。就有被淘汰的可能。

总得来说:

如果你的业务数据的访问比较平均,不存在明显的冷热区别,那么 LRU 可以满足一般的使用需求。如果你的业务具备很强的时效性,而且是存在大促商品这种明显的热点数据,那么推荐你使用 LFU。

什么场景下适合用哪些淘汰策略

分布式锁适合的淘汰策略? 肯定不能TTL,因为分布式锁的过期时间一般很短,不能被淘汰。

热点数据可以设置LFU的淘汰策略。

LRU怎么实现

  • 在Redis中给redisObject加时钟,而当需要淘汰 Key 时,将会从全部数据中进行抽样,然后再移除样本中上次访问时间最早的 key(注意是抽样,不是全部数据哦)
  • 自己实现:采用哈希表+双向链表,哈希表是为了查找时间复杂度为O1,双向链表是为了插入和删除时间复杂度为O1

那为什么redis不采用哈希表加双向链表?

因为redis的内存淘汰策略是不确定的(就是不一定是LRU),可能随时更改,给元素加上双向链表也增加了维护成本。

扩展:Redis的LRU是如何实现的,了解吗, 是哈希表加双向链表吗?

部分LRU,不是全局LRU

深入解析Redis的LRU与LFU算法实现

  • Redis是随机抽取一批数据去按照淘汰策略排序,不再需要对所有数据排序。
  • RedisObject是Redis核心的底层数据结构,成员变量lru字段用于记录了此key最近一次被访问的LRU时钟(server.lruclock),每次Key被访问或修改都会引起lru字段的更新。
  • 淘汰的时候按时钟去淘汰,并没有使用双向链表。

六、Redis事务

Redis如何实现事务

你可以通过 watch命令监听指定的 Key,当调用 EXEC 命令执行事务时,如果一个被 WATCH 命令监视的 Key 被 其他客户端/Session 修改的话,整个事务都不会被执行。

Redis 事务支持原子性吗?

不满足原子性

Redis 事务在运行错误的情况下,除了执行过程中 出现错误的命令外 ,其他命令都能正常执行。 并且,Redis 事务是不支持回滚(roll back) 操作的

Redis事务支持持久性吗?

虽然redis可以持久化,但事务持久性的概念是已经提交的事务对数据库数据更改的持久性,从概念的方面取分析是否支持持久性。

但RDB的方式 实时性 不够好,不满足持久性

AOF有三种写回方式,always策略基本可以满足但性能很差,everysec 和 no 无法保证持久性。

七、Redis限流相关

令牌桶和滑动窗口等