Redis 面试题
设计
单线程
跳表
用法
看门狗
如何保证缓存与数据库的一致性
Redis有什么优缺点?为什么用Redis查询会比较快?
- 优点:
- 高速:数据存储在内存中,访问速度快。
- 支持丰富的数据结构:如字符串、列表、哈希、集合等。
- 支持持久化:可以将内存数据持久化到磁盘。
- 分布式支持:支持集群部署,具有高可用性。
- 缺点:
- 内存消耗大:数据量过大会占用大量内存,导致成本上升。
- 数据持久化有限:虽然支持持久化,但不能保证完全持久性,存在丢失数据的风险。
- 不适合复杂查询:仅能存储和查询简单数据,复杂查询性能不佳。
- 查询速度快原因:
- Redis基于内存存储,读写速度远高于基于磁盘的数据库。
- 操作简单、直接且优化良好,查询无需复杂的联接或计算。
Redis是什么,有哪些特点
Redis是一个开源的基于内存的数据库,读写速度非常快,通常被用作缓存、消息队列、分布式锁和键值存储数据库。它支持多种数据结构,如字符串、哈希表、列表、集合、有序集合等,除此之外,Redis 还支持事务 、持久化、Lua 脚本、多种集群方案(主从复制模式、哨兵模式、切片机群模式)、发布/订阅模式,内存淘汰机制、过期删除机制等。
具有以下特点:
- 基于内存: Redis 将数据存储在内存中,使得它具有快速的读写访问速度。这也使得 Redis 适用于需要高性能的应用场景,比如缓存。
- 持久性:虽然 Redis 主要是内存中的存储系统,但它可以通过将数据持久化到磁盘上的快照和日志文件来保证数据的持久性,以防止数据丢失。
- 多数据结构:Redis 不仅支持简单的键值对存储,还提供了丰富的数据结构,如列表、哈希表、集合等,使得它更灵活地适应不同的应用场景。
- 原子性操作: Redis 支持原子性操作,这意味着它可以保证一个操作是原子的,要么执行成功,要么完全不执行,这对于并发环境下的数据一致性很重要。
- 分布式: Redis 提供了分布式特性,可以将数据分布在多个节点上,以提高可扩展性和可用性。
为什么要使用 Redis 而不仅仅依赖 MySQL
Redis 具备「高性能」和「高并发」两种特性
-
Redis读写非常快速,对于需要频繁读写的数据,特别是**缓存数据,**Redis 的性能优势非常明显,将一部分常用的、频繁访问的数据存储在 Redis 中,可以有效减轻对 MySQL 等持久性数据库的压力。
-
Redis 提供了丰富的数据结构,这使得 Redis 在处理特定类型数据和实现特定功能时更为灵活。
-
Redis 能够更好地处理高并发请求,尤其适用于一些需要快速响应的场景。通过缓存和原子性操作,可以降低数据库的并发压力
Redis 更适合处理高速、高并发的数据访问,以及需要复杂数据结构和功能的场景,在实际应用中,很多系统会同时使用 MySQL 和 Redis。
Redis是单线程吗
Redis 是单线程的,但是Redis 单线程指的是网络请求模块使用单线程进行处理,其他模块仍用多个线程,Redis程序并不是单线程的,在启动的时候,会启动后台线程。
- Redis 在 2.6 版本,会启动 2 个后台线程,分别处理关闭文件、AOF 刷盘这两个任务;
- Redis 在 4.0 版本之后,新增了一个新的后台线程,用来异步释放 Redis 内存,也就是 lazyfree 线程。
之所以 Redis 为「关闭文件、AOF 刷盘、释放内存」这些任务创建单独的线程来处理,是因为这些任务的操作都是很耗时的,如果把这些任务都放在主线程来处理,那么 Redis 主线程就很容易发生阻塞,这样就无法处理后续的请求了。
为了提高网络 I/O 的并行度,Redis 6.0 采用了多个 I/O 线程来处理网络请求,但是对于命令的执行,Redis 仍然使用单线程来处理。
Redis单线程为什么还这么快
- 基于内存存储:Redis 将数据存储在内存中,而不是磁盘上。内存访问速度远远快于磁盘访问速度,因此 Redis 能够快速读取和写入数据。
- 非阻塞单线程: Redis 使用单线程模型,不需要进行多线程间的上下文切换,避免了多线程带来的竞争和同步开销。此外,Redis 使用了非阻塞 I/O,可以更高效地处理并发请求。
- 高效的数据结构:Redis专门设计了STRING、LIST、HASH等高效的数据结构,依赖各种数据结构提升了读写的效率。
- I/O多路复用:采用I/O多路复用机制同时监听多个Socket,内核会一直监听这些 Socket 上的连接请求或数据请求。一旦有请求到达,就会根据Socket上的事件来选择对应的事件处理器进行处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。
Redis的数据类型有那些?
Redis五种数据结构和应用场景
Redis 提供了丰富的数据类型,常见的有五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。

- String
String类型的底层的数据结构实现主要是SDS(简单动态字符串)。应用场景主要有:
- 缓存对象:例如可以用STRING缓存整个对象的JSON。
- 计数:Redis处理命令是单线程,所以执行命令的过程是原子的,因此String数据类型适合计数场景,比如计算访问次数、点赞、转发、库存数量等等。
- 分布式锁:可以利用SETNX命令。
- 共享Session信息:服务器都会去同一个Redis获取相关的Session信息,解决了分布式系统下Session存储的问题。
命令:
get、set、del
- List
List 类型的底层数据结构是由双向链表或压缩列表实现的:
- 如果列表的元素个数小于 512 个,列表每个元素的值都小于 64 字节,Redis 会使用压缩列表作为 List 类型的底层数据结构;
- 如果列表的元素不满足上面的条件,Redis 会使用双向链表作为 List 类型的底层数据结构;
在 Redis 3.2 版本之后,List 数据类型底层数据结构只由 quicklist 实现,替代了双向链表和压缩列表。在 Redis 7.0 中,压缩列表数据结构被废弃,由 listpack 来实现。应用场景主要有:
- 微信朋友圈点赞:要求按照点赞顺序显示点赞好友信息,如果取消点赞,移除对应好友信息。
- 消息队列:可以使用左进右出的命令组成来完成队列的设计。比如:数据的生产者可以通过Lpush命令从左边插入数据,多个数据消费者,可以使用BRpop命令阻塞的”抢”列表尾部的数据。
命令:
rpush、lrange、index、lpop
- Set
Set 类型的底层数据结构是由哈希表或整数集合实现的:
- 如果集合中的元素都是整数且元素个数小于 512个,Redis 会使用整数集合作为 Set 类型的底层数据结构;
- 如果集合中的元素不满足上面条件,则 Redis 使用哈希表作为 Set 类型的底层数据结构。
应用场景主要有:
- 点赞:key 是文章id,value 是用户id。
- 共同关注:Set 类型支持交集运算,所以可以用来计算共同关注的好友、公众号等。key 可以是用户id,value 则是已关注的公众号的id。
- 抽奖活动:存储某活动中中奖的用户名 ,Set 类型因为有去重功能,可以保证同一个用户不会中奖两次。
命令:
sadd、smember、sismember、srem
- Hash
Hash 类型的底层数据结构是由压缩列表或哈希表实现的:
- 如果哈希类型元素个数小于 512 个,所有值小于 64 字节的话,Redis 会使用压缩列表作为 Hash 类型的底层数据结构;
- 如果哈希类型元素不满足上面条件,Redis 会使用哈希表作为 Hash 类型的底层数据结构。
在Redis 7.0 中,压缩列表数据结构被废弃,交由 listpack 来实现。应用场景主要有:
- 缓存对象:一般对象用 String + Json 存储,对象中某些频繁变化的属性可以考虑抽出来用 Hash 类型存储。
- 购物车:以用户 id 为 key,商品 id 为 field,商品数量为 value,恰好构成了购物车的3个要素。
命令:
hset、hget、hgetall、hdel
- Zset
Zset类型(Sorted Set,有序集合)可以根据元素的权重来排序,可以自己来决定每个元素的权重值。比如说,可以根据元素插入Sorted Set 的时间确定权重值,先插入的元素权重小,后插入的元素权重大。
Zset 类型的底层数据结构是由压缩列表或跳表实现的, 应用场景主要有:
- 在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,可以优先考虑使用 Zset。
- 排行榜:有序集合比较典型的使用场景就是排行榜。例如学生成绩的排名榜、游戏积分排行榜、视频播放排名、电商系统中商品的销量排名等。
命令:
zadd、zrange、zrangebyscore、zrem
随着 Redis 版本的更新,后面又支持了四种数据类型: BitMap(2.2 版新增)、HyperLogLog(2.8 版新增)、GEO(3.2 版新增)、Stream(5.0 版新增)
BitMap
bit 是计算机中最小的单位,使用它进行储存将非常节省空间,特别适合一些数据量大且使用二值统计的场景。可以用于签到统计、判断用户登陆态等操作。
HyperLogLog
HyperLogLog用于基数统计,统计规则是基于概率完成的,不准确,标准误算率是 0.81%。优点是,在输入元素的数量或者体积非常非常大时,所需的内存空间总是固定的、并且很小。比如百万级网页 UV 计数等;
GEO
主要用于存储地理位置信息,并对存储的信息进行操作。底层是由Zset实现的,使用GeoHash编码方法实现了经纬度到Zset中元素权重分数的转换,这其中的两个关键机制就是「对二维地图做区间划分」和「对区间进行编码」。一组经纬度落在某个区间后,就用区间的编码值来表示,并把编码值作为Zset元素的权重分数。
Stream
Redis专门为消息队列设计的数据类型。相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息ID,支持以消费组形式消费数据。
之前方法缺陷:不能持久化,无法可靠的保存消息,并且对于离线重连的客户端不能读取历史消息。
主从复制的同步过程
- 建立连接
从节点通过向主节点发送SYNC命令请求建立连接。如果是初次同步,主节点会创建一个专门的后台线程进行数据传输。
- 主节点创建RDB快照
如果是初次同步,主节点会执行BGSAVE命令,创建一个RDB(Redis Database Dump)快照,将当前内存中的数据保存到一个文件中。
- 主节点发送RDB文件和AOF缓冲区内容:
主节点在创建RDB快照后,会将这个RDB文件发送给从节点。同时,主节点将AOF缓冲区中的写命令也发送给从节点。这确保了从节点在收到RDB文件后,能够通过执行AOF缓冲区中的写命令将数据更新至主节点执行BGSAVE时的状态。
- 从节点载入RDB文件和执行AOF缓冲区命令:
从节点收到RDB文件后,会载入这个文件,将自己的数据状态更新为主节点在执行BGSAVE时的状态。接着,从节点会执行AOF缓冲区中的写命令,以追赶主节点的最新状态。
- 增量复制:
一旦初次同步完成,从节点就会转入增量复制的阶段。主节点会实时将写入命令发送给从节点,从节点接收并执行这些写入命令,保持和主节点数据的一致性。
- 心跳和命令传播
主节点和从节点之间会维持心跳连接,以检测对方的存活状态。主节点将写入命令通过命令传播机制发送给从节点,确保数据的实时同步。
Redis哨兵机制是什么
Redis的哨兵用于监控和管理Redis实例,它可以实现自动故障转移和高可用性。哨兵系统由一组独立运行的进程组成,这些进程定期检查Redis主节点和从节点的状态,并在主节点宕机时选择一个从节点升级为新的主节点。
如何避免主从数据的不一致
- 持久化和复制配置:确保在主节点和从节点上都启用了持久化(如RDB快照和AOF文件)以及复制配置。这样可以保证从节点在重启后能够通过持久化文件进行数据的完整恢复。
- 保证网络稳定性:确保主节点和从节点之间的网络连接是稳定的,比如让主从节点处于同一机房,降低网络延迟
- 监控和报警:使用监控系统来实时监测主从节点的状态,包括复制延迟、连接状态等。设置报警机制。
Redis切片集群的工作原理
当 Redis 缓存数据量大到一台服务器无法缓存时,就需要使用 Redis 切片集群(Redis Cluster )方案,它将数据分布在不同的服务器上,以此来降低系统对单主节点的依赖,从而提高 Redis 服务的读写性能。
- 数据分片
切片集群将整个数据集划分为16384个槽(slot),每个槽对应一个整数值。每个节点负责管理其中一部分槽的数据。
- 节点间通信
切片集群中的节点通过gossip协议相互通信,分享节点信息和集群状态。节点使用PING、PONG等消息保持通信,并通过定期的集群信息交换来保持一致性。
- 数据分布
当一个新的键值对被添加到切片集群时,根据键的CRC16哈希值确定它属于哪个槽,然后将数据存储到负责该槽的节点上。这确保了数据在集群中均匀分布。
- 故障检测
切片集群使用哨兵机制来检测节点的健康状态。当一个节点不可用时,其他节点会发起故障检测流程,重新分配槽到可用节点,并选举一个新的主节点。
- 客户端路由
客户端连接到切片集群时,通过计算键的CRC16哈希值确定它所属的槽,然后将请求发送到负责该槽的节点上。这样,客户端可以直接与负责相应数据的节点通信,而无需中间代理。
- 数据复制
切片集群使用主从复制机制,每个槽都有一个主节点和若干个从节点。数据在主节点上写入后,会被异步地复制到从节点上,确保数据的持久性和高可用性。
内存淘汰策略
-
不进行数据淘汰
-
NoEviction: 如果内存不足以执行写操作,Redis将返回错误,默认的淘汰策略。
-
进行数据淘汰
-
VolatileTTL:优先淘汰更早过期的键值。
-
VolatitleLRU: 只对带有过期时间的键使用LRU策略,其他键使用NoEviction策略。
-
VolatitleRandom: 只对带有过期时间的键使用随机淘汰,其他键使用NoEviction策略。
-
VolatitleLFU: 只对带有过期时间的键使用LFU策略,其他键使用NoEviction策略。
-
AllKeysLRU: 根据最近最少使用的原则淘汰最久未使用的键。
-
AllKeysRandom: 随机选择一个键进行淘汰。
-
AllKeysLFU: 根据最少频繁使用的原则淘汰最少使用的键。
可以使用 config get maxmemory-policy 命令,来查看当前 Redis 的内存淘汰策略
Redis的LRU算法和LFU算法有什么区别
-
LRU(Least Recently Used):
-
LRU算法假设最近被访问的数据更有可能在未来被再次访问,所以选择最近最少被使用的对象进行淘汰。
-
Redis 实现的是一种近似 LRU 算法,在 Redis 的对象结构体中添加一个额外的字段,用于记录此数据的最后一次访问时间。当 Redis 进行内存淘汰时,会使用随机采样的方式来淘汰数据,它是随机取 5 个值(此值可配置),然后淘汰最久没有使用的那个。
-
-
LFU(Least Frequently Used):
- LFU算法假设使用频率较低的对象在未来仍然会较少被访问,所以选择使用频率最低的对象进行淘汰。
- LFU维护一个使用计数,每当一个对象被访问时,其使用计数增加。在需要淘汰对象时,选择使用计数最低的对象淘汰。
区别:
- LRU关注的是最近的访问情况,认为最近被访问的对象更可能在未来被再次访问。
- LFU关注的是使用频率,认为使用频率较低的对象在未来仍然会较少被访问。
如何保证删除缓存操作一定能成功?
重试机制
引入消息队列
- 如果应用删除缓存失败,可以从消息队列中重新读取数据,然后再次删除缓存,这个就是重试机制。如果重试超过的一定次数,还是没有成功,就需要向业务层发送报错信息了。
- 如果删除缓存成功,就要把数据从消息队列中移除,避免重复操作,否则就继续重试。
订阅BINLog
订阅 binlog 日志,拿到具体要操作的数据,然后再执行缓存删除。可以让删除服务模拟自己伪装成一个 MySQL 的从节点,向 MySQL 主节点发送 dump 请求,主节点收到请求后,就会开始推送 BINLog ,删除服务解析 BINLog 字节流之后,转换为便于读取的结构化数据,再进行删除。
Redis持久化机制有哪些?
- AOF 追加日志:每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里;
- RDB 快照:将某一时刻的内存数据,以二进制的方式写入磁盘,当需要恢复数据时,就直接从RDB快照中进行恢复;
- 混合持久化方式:Redis 4.0 新增的方式,集成了 AOF 和 RDB 的优点,在Redis重启时优先加载AOF文件从而回复数据;
Redis为什么又要引入多线程呢
因为Redis的瓶颈不在内存,而是在网络I/O模块带来CPU的耗时,所以Redis6.0的多线程是用来处理网络I/O这部分,充分利用CPU资源,减少网络I/O阻塞带来的性能损耗。Redis引入的多线程 I/O 特性对性能提升至少是一倍以上。
Redis数据类型
Redis 常见的五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及 Zset(sorted set:有序集合)。
- 字符串
STRING:存储字符串数据,最基本的数据类型。 - 哈希表
HASH:存储字段和值的映射,用于存储对象。 - 列表
LIST:存储有序的字符串元素列表。 - 集合
SET:存储唯一的字符串元素,无序。 - 有序集合
ZSET:类似于集合,但每个元素都关联一个分数,可以按分数进行排序。
Redis版本更新,又增加了几种数据类型,
BitMap: 存储位的数据结构,可以用于处理一些位运算操作。HyperLogLog:用于基数估算的数据结构,用于统计元素的唯一数量。GEO: 存储地理位置信息的数据结构。Stream:专门为消息队列设计的数据类型。
Redis数据类型的应用场景
Redis 五种常见数据类型的应用场景:
- String 类型的应用场景:缓存对象、常规计数、分布式锁、共享session信息等。
- List 类型的应用场景:消息队列(有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。
- Hash 类型:缓存对象、购物车等。
- Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。
- Zset 类型:排序场景,比如排行榜、电话和姓名排序等。
Redis 后续版本又支持四种数据类型,它们的应用场景如下:
- BitMap(2.2 版新增):二值状态统计的场景,比如签到、判断用户登陆状态、连续签到用户总数等;
- HyperLogLog(2.8 版新增):海量数据基数统计的场景,比如百万级网页 UV 计数等;
- GEO(3.2 版新增):存储地理位置信息的场景,比如滴滴叫车;
- Stream(5.0 版新增):消息队列,相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息ID,支持以消费组形式消费数据。
Redis底层使用了什么数据结构
-
SDS: 简单动态字符串:用于存储二进制安全的字符串数据,包含字符串的长度信息以及字符数组,支持动态扩展。
-
双端链表:Redis中的
List就是用双端链表实现的,支持在两端进行元素的快速插入和删除。 -
压缩列表:紧凑的由连续内存块组成的顺序型数据结构,可以在元素较少时减少内存占用。
-
哈希表:用于实现 Redis 的哈希表数据类型。它使用两种不同的底层实现:ziplist 和哈希表,当哈希表的元素较少时,使用压缩列表(ziplist)来存储,而在元素较多时,切换到更为传统的哈希表结构。
-
整数集合:整数集合是一种专门用于存储整数值的数据结构,通过紧凑的二进制表示,提高了整数存储的效率。整数集合被用于存储Redis中的集合数据结构的整数元素。
-
跳表:跳跃表是一种在链表基础上改进过来的,实现了一种「多层」的有序链表,当数据量很大时,跳表的查找复杂度就是O(logN)。用于实现有序集合(Sorted Set)。
-
quicklist:quicklist就是双向链表 + 压缩列表组合,quicklist 就是一个链表,而链表中的每个元素又是一个压缩列表。 -
listpack:listpack 没有压缩列表中记录前一个节点长度的字段,listpack 只记录当前节点的长度,当我们向 listpack 加入一个新元素的时候,不会影响其他节点的长度字段的变化,从而避免了压缩列表的连锁更新问题。
Redis与MySQL的区别是什么
- Redis基于键值对,支持多种数据结构;而MySQL是一种关系型数据库,使用表来组织数据。
- Redis将数据存在内存中,通过持久化机制将数据写入磁盘,MySQL通常将数据存储在磁盘上。
- Redis不使用SQL,而是使用自己的命令集,MySQL使用SQL来进行数据查询和操作。
- Redis以高性能和低延迟为目标,适用于读多写少的应用场景,MySQL 适用于需要支持复杂查询、事务处理、拥有大规模数据集的场景。
Redis 更适合处理高速、高并发的数据访问,以及需要复杂数据结构和功能的场景,在实际应用中,很多系统会同时使用 MySQL 和 Redis。
Redis与Memcached有什么区别
- Redis支持更多的数据结构,而Memcached主要关注于简单的键值存储。
- Redis支持数据的持久性,而Memcached重启服务会导致数据丢失。
- Redis通常比Memcached更快,部分原因是由于Redis采用单线程模型,而Memcached使用多线程来处理并发请求
- 总结来说Redis适用于需要更丰富数据结构和功能、需要数据持久性支持、以及对高可用性有要求的场景,而**Memcached:**适用于简单的键值对缓存。
Redis为什么不使用红黑树,而使用跳表
- 跳表相对于红黑树来说实现更加简单
- 跳表是一种相对简单的数据结构,更容易理解和调试。
- 跳表对于范围查询的支持较好,这符合有序集合数据类型的特性。有序集合中的元素是有序的,而跳表天然支持按范围查找。
- 跳表在插入和删除操作上的性能表现较好,尤其是在有序集合的场景下。
Redis实现分布式锁
使用SETNX命令,只有插入的key不存在才插入,如果SETNX的key存在就插入失败,key插入成功代表加锁成功,否则加锁失败;解锁的过程就是将key删除,保证执行操作的客户端就是加锁的客户端,加锁时候要设置unique_value,解锁的时候,要先判断锁的 unique_value 是否为加锁客户端,是才将 lock_key 键删除。此外要给锁设置一个过期时间,以免客户端拿到锁后发生异常,导致锁一直无法释放,可以指定 EX/PX 参数设置过期时间。
AOF的三种写回策略
Always、Everysec 和 No,这三种策略在可靠性上是从高到低,而在性能上从低到高。
Always是每次写操作命令执行完后,同步将 AOF 日志数据写回硬盘;Everysec每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写回到硬盘;No就是不控制写回硬盘的时机。每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,再由操作系统决定何时将缓冲区内容写回硬盘。
什么是Redis事务
在 Redis 中,事务是一组命令的有序执行序列,它们被一起执行,而在执行期间不会被其他客户端的命令打断。Redis 使用 MULTI 和 EXEC 命令来实现事务。
基本流程如下:
-
MULTI: 开始一个事务,标记事务的开始。
-
在 MULTI 和 EXEC 之间,可以输入多个 Redis 命令。这些命令并不会立即执行,而是放入一个队列中,等待执行。
-
EXEC: 执行所有在 MULTI 和 EXEC 之间输入的命令。这些命令作为一个事务一起执行,而且是按照顺序执行的。如果在 EXEC 之前发生错误,整个事务会被取消。
-
如果事务执行成功,客户端会收到一个包含所有命令执行结果的数组。
-
如果事务执行失败(比如在 MULTI 和 EXEC 之间发生了错误),那么事务中的所有命令都不会被执行,Redis 将回滚到 MULTI 命令执行之前的状态。
在事务中,还可以使用 WATCH 命令来监视一个或多个键。如果在事务执行期间监视的键被其他客户端修改了,事务就会被取消,从而确保事务执行的原子性。
缓存中的热点数据和冷数据是什么
- 热数据指的就是访问次数较多的数据
- 冷数据就是访问很少或者从不访问的数据
只有热数据,缓存才有价值。
Redis集群模式有哪些
- 主从复制:将一个Redis实例的数据复制到其他实例,其中一个是主节点,其余是从节点。主节点将写操作传播到所有从节点。
- 哨兵:监控Redis实例的状态,发现主节点的故障并自动进行故障转移。
- 切片集群:将数据分布在不同的服务器上,以此来降低系统对单主节点的依赖
Redis的哨兵是用来做什么的
Redis哨兵用于监控Redis实例的状态,发现主节点的故障并自动进行故障转移。
- 监控:监控 Redis 主服务器和从服务器的状态,包括连接状态、是否能够执行命令、是否有持久性问题等
- 故障转移:当 Sentinel (哨兵)发现主服务器不可用时(比如宕机),它会通过一定的选举机制选择一个从服务器升级为新的主服务器,然后通知其他从服务器将其切换到新的主服务器。
主从模式的同步过程
- 连接协商:从服务器先发送命令给主服务器表示要进行数据同步,命令内容包括主服务器的runID和复制进度两个参数,主服务器收到命令之后会给从服务响应命令,响应包括主服务器的runID和复制进度。从服务器收到响应之后会记录这两个值。
- 主从数据同步:主服务器执行
BGSAVE命令,生成 RDB 快照文件。这个文件包含了主服务器当前时刻的数据状态,将生成的 RDB 快照文件发送给从服务器。从服务器接收到 RDB 快照文件后,通过LOAD命令将其加载到内存中,从服务器的数据现在与主服务器的数据一致。 - 主服务器开启增量同步:主服务器将开启 TCP 连接,等待从服务器连接,并开始记录所有的写命令,将它们发送给从服务器,从服务器连接到主服务器,执行
PSYNC命令,开始接收主服务器发送的增量同步命令,从服务器按顺序执行主服务器发送的所有写命令,确保与主服务器同步。
哨兵的工作原理
判断节点是否存活
每个哨兵定期向 Redis 服务器发送 PING 命令,以检测服务器是否处于活跃状态。如果哨兵连续一定次数未收到服务器的响应,就认为服务器主观下线。然后哨兵会从从节点中选择一个作为主节点。
选出新主节点
在发现主服务器下线后,哨兵会协调选举一个新的主服务器。这个过程中,哨兵会考虑每个可用的从服务器,选择一个作为新的主服务器,并将其他从服务器配置为复制新的主服务器。
具体过程:
- 选择候选从服务器: 哨兵会从可用的从服务器中选择一组候选服务器,通常选择复制偏移量(replication offset)最大的从服务器。
- 计算投票: 每个哨兵为每个候选从服务器投票。投票的考量因素包括从服务器的复制偏移量、连接质量、优先级等。
- 达成共识: 哨兵们根据投票结果达成共识,选择一个从服务器作为新的主服务器。这通常需要获得多数哨兵的同意。
更新配置信息
一旦新的主服务器被选出,哨兵会更新 Redis 集群的配置信息,包括将新的主服务器的地址和端口通知给其他哨兵和客户端。
通知客户端
哨兵会向客户端发送通知,告知客户端新的主服务器的位置,以便客户端能够重新连接。
缓存雪崩、击穿、穿透和解决办法
-
缓存雪崩是指在某个时间点,大量缓存同时失效,导致请求直接访问数据库或其他后端系统,增加了系统负载。
对于缓存雪崩,可以通过合理设置缓存的过期时间,分散缓存失效时间点,或者采用永不过期的策略,再结合定期更新缓存。
-
缓存击穿是指一个缓存中不存在但是数据库中存在的数据,当有大量并发请求查询这个缓存不存在的数据时,导致请求直接访问数据库,增加数据库的负载。典型的场景是当一个缓存中的数据过期或被清理,而此时有大量请求访问这个缓存中不存在的数据,导致大量请求直接访问底层存储系统。
对于缓存击穿,可以采用互斥锁(例如分布式锁)或者在查询数据库前先检查缓存是否存在,如果不存在再允许查询数据库,并将查询结果写入缓存。
-
缓存穿透是指查询一个在缓存和数据库都不存在的数据,这个数据始终无法被缓存,导致每次请求都直接访问数据库,增加数据库的负载。典型的情况是攻击者可能通过构造不存在的 key 大量访问缓存,导致对数据库的频繁查询。
对于缓存穿透,可以采用布隆过滤器等手段来过滤掉恶意请求,或者在查询数据库前先进行参数的合法性校验。
如何保证数据库和缓存的一致性
Cache Aside
- 原理:先从缓存中读取数据,如果没有就再去数据库里面读数据,然后把数据放回缓存中,如果缓存中可以找到数据就直接返回数据;更新数据的时候先把数据持久化到数据库,然后再让缓存失效。
- 问题:假如有两个操作一个更新一个查询,第一个操作先更新数据库,还没来及删除数据库,查询操作可能拿到的就是旧的数据;更新操作马上让缓存失效了,所以后续的查询可以保证数据的一致性;还有的问题就是有一个是读操作没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,也会造成脏数据。
- 可行性:出现上述问题的概率其实非常低,需要同时达成读缓存时缓存失效并且有并发写的操作。数据库读写要比缓存慢得多,所以读操作在写操作之前进入数据库,并且在写操作之后更新,概率比较低。
Read/Write Through
- 原理:Read/Write Through原理是把更新数据库(Repository)的操作由缓存代理,应用认为后端是一个单一的存储,而存储自己维护自己的缓存。
- Read Through:就是在查询操作中更新缓存,也就是说,当缓存失效的时候,Cache Aside策略是由调用方负责把数据加载入缓存,而Read Through则用缓存服务自己来加载,从而对调用方是透明的。
- Write Through:当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由缓存自己更新数据库(这是一个同步操作)。
Write Behind
- 原理:在更新数据的时候,只更新缓存,不更新数据库,而缓存会异步地批量更新数据库。这个设计的好处就是让数据的I/O操作非常快,带来的问题是,数据不是强一致性的,而且可能会丢。
- 第二步失效问题:这种可能性极小,缓存删除只是标记一下无效的软删除,可以看作不耗时间。如果会出问题,一般程序在写数据库那里就没有完成:故意在写完数据库后,休眠很长时间再来删除缓存。
Redis过期删除策略有哪些
- 定时删除:设置 key 的过期时间,当时间到达时,自动执行 key 的删除操作。
- 惰性删除: 每次从数据库访问 key 时,都检测 key 是否过期,如果过期则删除该 key。
- 定期删除:每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期key。
Redis 选择「惰性删除+定期删除」这两种策略配和使用
在应用中是怎么使用Redis的
- 可以用作缓存层,存储频繁访问的数据,提高访问速度。
- 用作消息队列,通过发布订阅模式传递消息。
- 存储会话数据、计数器等。