Redis 使用场景有哪些?
-
缓存:缓存热点数据,如数据库查询结果、页面片段等,减少数据库压力,提高系统响应速度。
-
分布式锁:利用 Redis 的原子操作实现分布式锁,保证在分布式环境下同一时刻只有一个线程能获取到锁,常用于分布式系统中的资源竞争控制。
-
消息队列:使用 Redis 的
LIST
数据结构实现简单的消息队列,生产者将消息放入列表,消费者从列表中获取消息,实现异步通信。 -
计数器:利用 Redis 的原子递增和递减操作,实现计数器功能,如统计网站访问量、点赞数等。
-
排行榜:使用 Redis 的
ZSET
数据结构,根据分数对元素进行排序,实现各种排行榜,如游戏排行榜、商品销量排行榜等。 -
社交网络:点赞、踩、关注/被关注、共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库类型不适合存储这种类型的数据,Redis提供的哈希、集合等数据结构能很方便的的实现这些功能。
-
最新列表:Redis列表结构,LPUSH可以在列表头部插入一个内容ID作为关键字,LTRIM可用来限制列表的数量,这样列表永远为N个ID,无需查询最新的列表,直接根据ID去到对应的内容 页即可。
-
消息系统:消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间 件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。Redis提供了发布/订阅及 阻塞队列功能,能实现一个简单的消息队列系统。另外,这个不能和专业的消息中间件相比。
-
限流:采用redis的key过期策略,将业务id和业务值存起来设置一定的过期时间,等请求再次进入时,如果有值,就认为是重复性请求,然后过滤掉。
假如一个业务依赖单点redis,此redis 故障将导致业务不可用,如何改进?
集群部署方案:主从、哨兵、cluster
Redis 有哪些数据类型?
-
STRING:字符串类型,可用于存储简单的字符串、数字等,支持对字符串进行赋值、获取、追加、自增自减等操作。
-
HASH:哈希类型,用于存储键值对集合,适合存储对象的属性,如用户信息(
user:1
为键,包含name
、age
等属性),可对单个属性进行操作。 -
LIST:列表类型,按插入顺序存储数据,可用于实现消息队列、栈、简单列表等,支持从列表两端插入和弹出元素。
-
SET:集合类型,无序且唯一,可用于实现去重、交集、并集、差集等操作,如统计网站的独立访客数。
-
ZSET:有序集合类型,每个元素都关联一个分数,根据分数进行排序,可用于实现排行榜等场景。
说说 Redis 持久化机制?
-
RDB(Redis Database):将 Redis 在内存中的数据以快照的形式保存到磁盘上,生成一个
.rdb
文件。触发方式有手动执行SAVE
或BGSAVE
命令,以及根据配置文件中的save
参数自动触发(如save 900 1
表示 900 秒内至少有 1 个键被修改就触发 RDB 持久化)。优点是恢复速度快,文件体积小;缺点是可能会丢失最后一次持久化到发生故障期间的数据。 -
AOF(Append Only File):以日志的形式记录 Redis 执行的写命令,将每次写操作追加到文件末尾。默认不开启,可通过配置文件开启。AOF 持久化有三种策略:
always
(每次写操作都同步到 AOF 文件,数据安全性高,但性能较低)、everysec
(每秒同步一次,兼顾性能和数据安全性)、no
(由操作系统决定何时同步,性能最高,但数据安全性最低)。优点是数据安全性高,基本不丢失数据;缺点是 AOF 文件体积较大,恢复速度相对较慢,且可能存在日志文件过大需要重写的问题。
RDB 和 AOF 如何选择?
- RDB 持久化方式能够在指定的时间间隔能对你的数据进行快照储存
- AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行只写命令来恢复原始的数据,AOF 命令以及 Redis 协议追加保存每次写的操作到文件末尾
- Redis 还能对 AOF 文件进行后台重写,使得 AOF 文件的体积不至于过大。
- 只做缓存:如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式
- 同时开启两种持久化方式:在这种情况下,当 redis 重启的时候会优先载入 aof 文件来恢复数据,因为在通常情况下,AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整
- RDB 的数据集不实时,同时使用两者时服务器重启也只会找 AOF 文件,那要不要只使用 AOF 呢?
- 建议不要,因为 RDB 更适合用于备份数据库(AOF 在不断变化不好备份),快速重启,而且不会有 AOF 可能潜在的 bug,留着作为一个以防万一。
Redis 为什么是单线程的?
- 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。
- 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
- 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致 的切换面消耗CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出 现死锁而导致的性能消耗;
- 使用多路I/O复用模型,非阻塞IO;
- 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM机制,因为一般的系统调用系统函数的话,会浪费一定的时间 去移动和请求
Redis6.0 为什么是引入多线程?
- Redis的网络IO瓶颈已经越来越明显了:redis的网络IO读写占据了大部分的CPU时间(换句话说,读写网络的
read/write
系统调用在 Redis执行期间占用了大部分CPU 时间) - 引入多线程的好处:
- 1.可以充分利用服务器的CPU资源,目前单线程只能利用一个核
- 2.多线程任务可以分摊 redis同步 IO 读写负荷
Redis 是单线程为什么还能处理大量的读请求呢?
- redis 是基于内存的,内存的读写速度非常快(纯内存);
- redis 是单线程的,省去了很多上下文切换线程的时间(避免线程切换和竞态消耗)。
- IO 多路复用:“多路”是指多个网络连接,“复用”是指同一个线程,I/O 多路复用模型是利用 select、poll、epoll 函数可以同时监测多个 I/O 流的能力,这些函数会轮询一遍所有的 I/O 流(epoll只轮询真正发出了事件的流),依次处理返回了数据,处于就绪状态的流,让单个线程高效地处理了多个连接请求,尽量减少网络 IO 的时间消耗,而且 Redis 是在内存中操作的,速度非常快。所以,Redis 具有很高的吞吐量。
什么是缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级?
-
缓存雪崩:指在同一时刻大量的缓存过期失效,导致大量请求直接访问数据库,造成数据库压力过大甚至崩溃。解决方法有设置不同的过期时间,避免集中过期;使用互斥锁,保证同一时间只有一个请求去查询数据库并更新缓存。
-
缓存穿透:指查询一个不存在的数据,由于缓存中没有,每次都会去查询数据库,若被恶意利用,大量请求会击垮数据库。解决方法有使用布隆过滤器,提前判断数据是否存在;对查询结果为空的情况也进行缓存,设置较短的过期时间。
-
缓存预热:在系统上线前,将一些热点数据提前加载到缓存中,避免上线后大量请求同时查询数据库,导致数据库压力瞬间增大。可以通过脚本或定时任务实现。
-
缓存更新:当数据库中的数据发生变化时,需要及时更新缓存中的数据,以保证数据一致性。常见策略有读写锁、先更新数据库再删除缓存、先删除缓存再更新数据库等,每种策略都有其适用场景和优缺点。
-
缓存降级:当系统出现高并发、服务器故障等情况时,为保证核心服务的可用性,暂时关闭部分非核心服务的缓存,直接返回默认值或错误信息,避免因缓存问题导致整个系统崩溃。
说说 Redis 常见的内存淘汰策略?
-
volatile-lru:从设置了过期时间的键中,使用 LRU(最近最少使用)算法淘汰数据。
-
volatile-ttl:从设置了过期时间的键中,优先淘汰剩余时间(TTL)最短的数据。
-
volatile-random:从设置了过期时间的键中,随机淘汰数据。
-
allkeys-lru:从所有键中,使用 LRU 算法淘汰数据,不区分是否设置了过期时间。
-
allkeys-random:从所有键中,随机淘汰数据。
-
noeviction:不淘汰数据,当内存不足时,写入操作会报错,适用于需要确保数据不丢失的场景。
Redis 的 key 过期会立即删除吗?
- 定时删除:在设置键过期时间的同时,创建一个定时器,让定时器在键过期时,立即删除键;
对内存友好而对 CPU 不友好; - 惰性删除:只有用到的时候才会判断是否删除;对 CPU 友好但对内存不友好,如果一个过
期键永远不被访问,那该键占用的内存也就永远得不到释放; - 定期删除:每隔一段时间,就检查一次;折中方案,需要合理设置删除操作的执行时间和执
行频率;
redis 实际使用的是惰性删除和定期删除两种。所以不会
Hash 一致性算法是什么?
Hash 一致性算法是一种分布式系统中常用的算法,用于将数据均匀地分布到多个节点上。它通过一个哈希函数将数据和节点映射到一个固定的哈希环上,当有数据需要存储或读取时,先计算数据的哈希值,然后在哈希环上顺时针查找第一个大于等于该哈希值的节点,将数据存储到该节点或从该节点读取数据。当节点增加或减少时,只有该节点及其相邻节点的数据会受到影响,其他节点的数据不受影响,从而保证了数据分布的稳定性和扩展性,减少了数据迁移的开销。
Redis 和数据库数据一致性的问题如何解决?
-
读写锁:在读取数据时加读锁,多个线程可以同时读取;在更新数据时加写锁,只有一个线程可以进行写操作,写操作完成后,同时更新缓存和数据库,保证数据一致性,但会影响并发性能。
-
先更新数据库再删除缓存:当数据发生变化时,先更新数据库,然后删除对应的缓存。若删除缓存失败,可能导致数据不一致,可通过重试机制或消息队列异步处理来解决。
-
先删除缓存再更新数据库:先删除缓存,再更新数据库。但在高并发情况下,可能出现缓存被删除后,还未更新数据库时,其他线程读取数据,发现缓存为空,从数据库读取旧数据并写入缓存,导致数据不一致,可通过设置缓存过期时间或使用读写锁来降低这种风险。
Redis 中 zSet 跳跃表问题原理是什么?
zSet(有序集合)中的跳跃表是一种有序的数据结构,用于实现快速的插入、删除和查找操作。它的原理基于多层链表,每个节点包含多个指针,指向不同层级的下一个节点。在插入节点时,通过随机函数决定该节点的层数,层数越高,节点出现的概率越低。查找节点时,从最高层开始,根据节点的分数进行比较,若分数小于目标分数,则移动到下一个节点;若分数大于目标分数,则下降一层继续查找,直到找到目标节点或确定不存在。跳跃表的平均时间复杂度为 O (log n),与平衡二叉树相当,但实现相对简单,且在插入和删除操作时无需频繁调整树的结构,因此在 Redis 中被用于实现有序集合的高效操作。
Redis 常见性能问题和解决方案
- Master 好不要做任何持久化工作,包括内存快照和 AOF 日志文件,特别是不要启用内存快照做持久化。
- 如果数据比较关键,某个 Slave 开启 AOF 备份数据,策略为每秒同步一次。
- 为了主从复制的速度和连接的稳定性, Slave 和 Master 好在同一个局域网内。
- 尽量避免在压力较大的主库上增加从库
- Master 调用 BGREWRITEAOF 重写 AOF 文件,AOF 在重写的时候会占大量的 CPU和内存资源,导致服务 load 过高,出现短暂服务暂停现象。
- 为了 Master 的稳定性,主从复制不要用图状结构,用单向链表结构更稳定,即主从关系为: Master ← Slave1 ← Slave2 ← Slave3 …,这样的结构也方便解决单点故障问题, 实现 Slave 对 Master 的替换,如果 Master 挂了,可以立马启用 Slave1 做 Master ,其他不变。
Redis 的同步机制了解么?
同步机制又分为:全量同步、增量同步
- 全量同步:指 slave 启动时进行的初始化同步。
- 增量同步:指 Redis 运行过程中的修改同步。当 Redis 的 master/slave 服务启动后,首先进行全同步。之后,所有的写操作都在 master上,而所有的读操作都在 slave 上。因此写操作需要及时同步到所有的 slave 上,这种同步就是部分同步。
全量同步过程如下
:
1)在 slave 启动时,会向 master 发送一条 SYNC 指令。
2)master 收到这条指令后,会启动一个备份进程将所有数据写到 rdb 文件中去。
3)更新 master 的状态(备份是否成功、备份时间等),然后将 rdb 文件内容发送给等待中的 slave。注意,master 并不会立即将 rdb 内容发送给 slave。而是为每个等待中的 slave 注册写事件,当 slave 对应的 socket 可以发送数据时,再将 rdb 内容发送给 slave。增量同步过程如下
:
1)master 收到一个操作,然后判断是否需要同步到 salve。
2)如果需要同步,则将操作记录到 aof 文件中。
3)遍历所有的 salve,将操作的指令和参数写入到 savle 的回复缓存中。
4)一旦 slave 对应的 socket 发送缓存中有空间写入数据,即将数据通过 socket 发出去。
Redis 主从复制原理
Redis 主从复制通过 异步复制 机制,使 Slave 通过 全量同步(RDB 快照 + 增量数据) 或 增量同步(基于 backlog) 从 Master 获取数据,以实现读写分离和数据冗余。
哨兵 Sentinel 原理
是否使用过 Redis 集群,集群的原理是什么?
Redis 集群主要基于 分片(Sharding)+ 主从复制(Replication)+ Gossip 协议 来实现数据的高可用与负载均衡。
核心机制:
1. 数据分片(Sharding):将数据划分为 16384 个槽(slot),不同的 Redis 节点负责不同的 slot,分布式存储数据。
2. 主从复制(Replication):每个 Master 节点都有至少一个 Slave 作为备份,保证数据的高可用性。
3. Gossip 协议:集群中的节点定期交换状态信息(PING/PONG),实现故障检测和状态同步。
4. 故障自动转移(Failover):当 Master 失效时,其 Slave 会自动提升为 Master,保证集群可用。
redis 集群如何保证一致性?
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有 N-1
个复制品。可以了解一下redis的同步机制。
redis 集群为什么是 16384?
正常的心跳数据包携带节点的完整配置,它能以幂等方式来更新配置。如果采用 16384 个插槽,占空间 2KB (16384/8);如果采用 65536 个插槽,占空间 8KB (65536/8)。Redis Cluster 不太可能扩展到超过 1000 个主节点,太多可能导致网络拥堵。16384 个插槽范围比较合适,当集群扩展到 1000 个节点时,也能确保每个 master 节点有足够的插槽,8KB 的心跳包看似不大,但是这个是心跳包每秒都要将本节点的信息同步给集群其他节点。比起 16384 个插槽,头大小增加了 4 倍,ping 消息的消息头太大了,浪费带宽。
keys 命令存在的问题?
- KEYS 命令的性能随着数据库数据的增多而越来越慢
- KEYS 命令会引起阻塞,连续的 KEYS 命令足以让 Redis 阻塞
为了避免 KEYS 的性能问题,推荐用 scan 命令替代 keys 命令
SCAN 命令的特点:
• 渐进式扫描(不会一次性遍历整个数据库)。
• 非阻塞,允许 Redis 在执行 SCAN 时仍然处理其他请求。
• 时间复杂度 O(1) 对 O(N)(每次只扫描部分 key)。
• 适合大数据量场景,可以结合分页处理。
--------------------------------------场景题--------------------------------------
当大量数据要求用 redis 保存,单机单点难以满足需要,设计一个负载均衡的方案
参考 Redis cluster 原理
当 redis 采用 hash 做 sharding,现在有 8 个节点,负载方案是 pos= hash(key) % 8,然后保存在 pos 节点上。这样做有什么好处、坏处?当 8 个节点要扩充到 10 个节点,应该怎么办?有什么更方便扩充的方案吗?
- 好处:集群下 key 的查询效率非常高
- 坏处:一旦某一个 master 节点宕机,所有新请求都会基于最新的剩余 master 节点数去取模,尝试去取数据,而取不到有效缓存,导致大量的流量涌入数据库。
当8个节点扩充到10个节点时,原本的“pos = hash(key) % 8”变为“pos = hash(key) % 10”,大部分数据会被映射到不同节点,需要进行大规模数据迁移。可以考虑以下更方便的扩充方案:
- 一致性Hash算法:引入虚拟节点,将每个真实节点映射为多个虚拟节点,分布在Hash环上。数据根据Hash值映射到离它最近的虚拟节点,再对应到真实节点。增加新节点时,只需在Hash环上添加虚拟节点,迁移部分原节点负载到新节点,成本低,还能避免热点问题,扩展性好。
- Presharding(预分片):在集群初始化时就按Hash算法对数据进行划分。扩容时,增加新节点,并转移原节点部分负载。该方案可避免扩容时的数据迁移,但需提前划分数据,并对新节点初始化,增加了部署和维护的复杂性。
MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证 redis中的数据都是热点数据
思路:首先计算出 20w 数据所需的内存空间,设置最大内存,然后选择合适的内存淘汰策略。
你们mysql和redis出现了数据不一致的情况如何解决?为什么使用双删策略?如果你 mysql 存入了, 但是 redis 删除失败了怎么解决?反过来呢? 你又怎么解决?
- 使用延迟双删:先删除缓存,再更新数据库,延迟几秒再删除缓存,一般我们在更新数据库数据时,需要同步 redis 中缓存的数据。
- 为什么要延迟双删?
当请求 1 执行清除缓存后,还未进行 update 操作,此时请求 2 进行查询到了旧数据并写入了 redis。 - redis 删除失败解决方案:
- 先更新 db, 再删除缓存, 会结合 MQ 异步删除缓存
- 如果更新 db 成功, 但是删除缓存失败如何处理? MQ 重试机制,保证该 message 消息必须消费成功。