什么是 Redis,为什么用 Redis?
Redis(Remote Dictionary Server)是一个开源的、基于内存的数据结构存储系统,它可以用作数据库、缓存和消息中间件。使用 Redis 的原因如下:
- 高性能:数据存储在内存中,读写速度极快,能轻松应对高并发场景。
- 丰富的数据类型:支持字符串、哈希、列表、集合、有序集合等多种数据类型,能满足不同业务需求。
- 持久化:提供 RDB(Redis Database)和 AOF(Append - Only File)两种持久化机制,可将内存数据保存到磁盘,防止数据丢失。
- 分布式:支持主从复制、哨兵和集群模式,具备高可用性和可扩展性。
- 原子操作:对数据的操作都是原子性的,保证数据操作的一致性。
为什么 Redis 是单线程的以及为什么这么快?
单线程原因
- 简单易维护:单线程避免了多线程带来的锁竞争、上下文切换等复杂问题,代码逻辑更简单,易于开发和维护。
- 避免锁竞争开销:Redis 操作基于内存,单线程执行操作时不会因为锁竞争而降低性能。
速度快的原因
- 内存操作:数据存储在内存中,读写速度快,避免了磁盘 I/O 的开销。
- 单线程模型:没有多线程的上下文切换开销,减少了 CPU 资源的浪费。
- 高效的数据结构:采用了哈希表、跳表等高效的数据结构,能快速定位和操作数据。
- I/O 多路复用:使用 I/O 多路复用技术(如 epoll、kqueue 等),可以同时监听多个客户端连接,提高了并发处理能力。
Redis 一般有哪些使用场景?
- 缓存:作为数据库的缓存层,减少数据库的访问压力,提高系统响应速度。例如,将热门商品信息、用户信息等缓存到 Redis 中。
- 会话管理:存储用户会话信息,如登录状态、购物车信息等,方便在分布式系统中实现会话共享。
- 排行榜:利用有序集合可以轻松实现各种排行榜,如游戏得分榜、商品销量榜等。
- 消息队列:使用列表实现简单的消息队列,支持消息的发布和订阅。
- 分布式锁:通过 Redis 的原子操作实现分布式锁,保证在分布式系统中同一时间只有一个客户端可以访问共享资源。
- 计数器:使用字符串类型实现计数器,如网站的访问量统计、文章的点赞数等。
Redis 有哪些数据类型?
- 字符串(String):最基本的数据类型,可以存储字符串、整数或浮点数。
- 哈希(Hash):类似于 Python 中的字典,是键值对的集合,适合存储对象信息。
- 列表(List):是一个有序的字符串列表,支持在列表的两端进行插入和删除操作,可用于实现消息队列等。
- 集合(Set):是无序且唯一的字符串集合,支持交集、并集、差集等操作。
- 有序集合(Sorted Set):类似于集合,但每个成员都关联了一个分数,根据分数进行排序,可用于实现排行榜等。
- Stream:Redis 5.0 引入的新数据类型,是一种消息队列,支持消息的发布、订阅和消费,并且可以记录消息的偏移量。
谈谈 Redis 的对象机制(redisObject)?
Redis 使用 redisObject 结构体来表示各种数据类型。redisObject 结构体定义如下:
typedef struct redisObject {unsigned type:4;unsigned encoding:4;unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or* LFU data (least significant 8 bits frequency* and most significant 16 bits access time). */int refcount;void *ptr;
} robj;
- type:表示对象的数据类型,如字符串、哈希、列表等。
- encoding:表示对象的编码方式,即底层数据结构的类型。不同的数据类型可以有不同的编码方式,以提高存储效率和性能。
- lru:用于记录对象的最近访问时间,可用于内存淘汰策略。
- refcount:表示对象的引用计数,用于内存管理。当引用计数为 0 时,对象将被释放。
- ptr:指向实际的数据存储位置。
Redis 数据类型有哪些底层数据结构?
- 字符串(String):
- 简单动态字符串(SDS):用于存储长度小于等于 512MB 的字符串。
- 整数(int):当存储的是整数时,直接使用整数类型存储。
- 哈希(Hash):
- 压缩列表(ziplist):当哈希对象的键值对数量较少且键和值的长度较短时使用。
- 哈希表(hashtable):当哈希对象的键值对数量较多或键和值的长度较长时使用。
- 列表(List):
- 压缩列表(ziplist):当列表元素数量较少且元素长度较短时使用。
- 双向链表(linkedlist):当列表元素数量较多或元素长度较长时使用。
- 快速列表(quicklist):Redis 3.2 引入的一种数据结构,结合了压缩列表和双向链表的优点。
- 集合(Set):
- 整数集合(intset):当集合中的元素都是整数且元素数量较少时使用。
- 哈希表(hashtable):当集合中的元素包含非整数或元素数量较多时使用。
- 有序集合(Sorted Set):
- 压缩列表(ziplist):当有序集合的元素数量较少且元素长度较短时使用。
- 跳表(skiplist):当有序集合的元素数量较多或元素长度较长时使用。
- Stream:
- radix tree(基数树):用于存储消息 ID 和消息内容的映射关系。
- listpack(列表包):用于存储消息内容。
为什么要设计 sds?
SDS(Simple Dynamic String)即简单动态字符串,是 Redis 中字符串类型的底层实现。设计 SDS 的原因如下:
- 常数复杂度获取字符串长度:SDS 结构体中记录了字符串的长度,获取字符串长度的时间复杂度为 O (1),而 C 字符串需要遍历整个字符串,时间复杂度为 O (n)。
- 杜绝缓冲区溢出:SDS 在进行字符串操作时,会先检查内存空间是否足够,如果不足会自动扩展,避免了缓冲区溢出的问题。
- 减少内存重分配次数:SDS 采用了预分配和惰性释放的策略,减少了内存重分配的次数,提高了性能。
- 二进制安全:SDS 可以存储任意二进制数据,而 C 字符串以 '\0' 作为字符串的结束标志,不能存储包含 '\0' 的二进制数据。
Redis 一个字符串类型的值能存储最大容量是多少?
Redis 一个字符串类型的值能存储的最大容量是 512MB。
为什么会设计 Stream?
Redis 之前使用列表(List)和发布 - 订阅(Pub/Sub)来实现消息队列,但它们都有一些局限性。列表实现的消息队列不支持消息的确认机制,容易导致消息丢失;发布 - 订阅模式不支持消息的持久化,当订阅者离线时会丢失消息。Stream 的设计旨在提供一个功能强大、可靠的消息队列解决方案,支持消息的持久化、消费组、消息确认等功能。
Redis Stream 用在什么样场景?
- 消息队列:作为消息队列使用,支持多个消费者组同时消费消息,并且可以记录消息的消费偏移量,保证消息的可靠消费。
- 实时日志处理:可以将系统产生的实时日志发送到 Stream 中,然后由多个消费者进行处理,如日志分析、监控等。
- 事件驱动系统:在事件驱动的系统中,Stream 可以作为事件的存储和分发中心,不同的服务可以订阅不同的事件进行处理。
Redis Stream 消息 ID 的设计是否考虑了时间回拨的问题?
Redis Stream 消息 ID 的设计考虑了时间回拨的问题。消息 ID 的格式为<millisecondsTime>-<sequenceNumber>
,其中<millisecondsTime>
是生成消息的时间戳(毫秒级),<sequenceNumber>
是同一毫秒内生成的消息的序列号。当发生时间回拨时,Redis 会使用上一个消息 ID 的时间戳,并递增序列号,从而保证消息 ID 的唯一性和有序性。
Redis Stream 消费者崩溃带来的会不会消息丢失问题?
在默认情况下,如果消费者崩溃,未确认的消息可能会丢失。但 Redis Stream 支持消费者组和消息确认机制,可以避免消息丢失。消费者组中的每个消费者在消费消息时,需要向 Redis 发送确认消息,Redis 会记录消息的消费状态。当消费者崩溃后,未确认的消息会被重新分配给其他消费者进行处理,从而保证消息不会丢失。
Redis Steam 坏消息问题,死信问题?
- 坏消息问题:当消费者在处理消息时出现错误,导致消息无法正常处理,这就是坏消息问题。可以通过设置重试机制和最大重试次数来处理坏消息。当消息重试次数达到最大重试次数后,可以将消息发送到一个专门的死信队列中。
- 死信问题:死信是指无法被正常消费的消息。可以通过监控死信队列,分析死信产生的原因,并进行相应的处理,如人工干预、修复代码等。
Redis 的持久化机制是什么?各自的优缺点?一般怎么用?
RDB(Redis Database)
- 原理:RDB 是通过快照的方式将 Redis 内存中的数据保存到磁盘上。可以手动触发(使用 SAVE 或 BGSAVE 命令),也可以配置定期自动触发。
- 优点:
- 文件紧凑,适合备份和灾难恢复。
- 恢复速度快,因为只需要将 RDB 文件加载到内存中。
- 对 Redis 性能影响较小,因为 BGSAVE 是通过 fork 子进程来完成的。
- 缺点:
- 可能会丢失最后一次快照之后的数据,因为快照是定期进行的。
- fork 子进程会占用一定的内存和 CPU 资源。
- 使用场景:适合对数据恢复速度要求较高,对数据完整性要求相对较低的场景,如缓存。
AOF(Append - Only File)
- 原理:AOF 是将 Redis 执行的写命令追加到文件末尾,在重启时通过重新执行这些命令来恢复数据。
- 优点:
- 数据安全性高,因为 AOF 可以配置不同的同步策略,如每秒同步、每次写操作同步等。
- 日志文件是增量追加的,不会因为写入大量数据而导致文件损坏。
- 缺点:
- AOF 文件通常比 RDB 文件大,恢复速度相对较慢。
- 由于需要频繁写入磁盘,对性能有一定影响。
- 使用场景:适合对数据完整性要求较高的场景,如数据库。
一般使用方式
通常可以同时使用 RDB 和 AOF 两种持久化机制。RDB 用于定期备份和快速恢复,AOF 用于保证数据的完整性。在 Redis 配置文件中,可以同时开启 RDB 和 AOF:
# 开启RDB
save 900 1
save 300 10
save 60 10000# 开启AOF
appendonly yes
appendfsync everysec
RDB 触发方式?
- 手动触发:
- SAVE:阻塞 Redis 服务器进程,直到 RDB 文件创建完成,期间服务器不能处理其他客户端请求。
- BGSAVE:通过 fork 子进程来创建 RDB 文件,主进程继续处理客户端请求。子进程创建 RDB 文件完成后会向主进程发送信号。
- 自动触发:在 Redis 配置文件中可以配置自动触发 RDB 的条件,如在一定时间内发生一定数量的写操作:
save 900 1
save 300 10
save 60 10000
表示在 900 秒内至少有 1 个键被修改、300 秒内至少有 10 个键被修改、60 秒内至少有 10000 个键被修改时,自动触发 BGSAVE。
RDB 由于生产环境中我们为 Redis 开辟的内存区域都比较大(例如 6GB),那么将内存中的数据同步到硬盘的过程可能就会持续比较长的时间,而实际情况是这段时间 Redis 服务一般都会收到数据写操作请求。那么如何保证数据一致性呢?
在进行 RDB 快照操作时,Redis 使用 fork 子进程来完成。fork 子进程会复制主进程的内存数据,子进程负责将复制的数据写入 RDB 文件,主进程继续处理客户端的写操作请求。由于 Linux 的写时复制(Copy - On - Write,COW)机制,在子进程创建 RDB 文件的过程中,如果主进程有写操作,只会复制被修改的内存页,而不会影响子进程正在写入的 RDB 文件。因此,在 RDB 快照期间,Redis 可以保证数据的一致性。
在进行 RDB 快照操作的这段时间,如果发生服务崩溃怎么办?
如果在进行 RDB 快照操作期间发生服务崩溃,可能会导致 RDB 文件不完整。Redis 在启动时会检查 RDB 文件的完整性,如果发现文件不完整,会尝试修复或使用最近一次完整的 RDB 文件进行恢复。如果没有完整的 RDB 文件,可能会丢失部分数据。为了减少数据丢失的风险,可以同时使用 AOF 持久化机制。
可以每秒做一次 RDB 快照吗?
理论上可以每秒做一次 RDB 快照,但不建议这样做。因为 RDB 快照是通过 fork 子进程来完成的,频繁的 fork 操作会占用大量的 CPU 和内存资源,影响 Redis 的性能。而且,由于写时复制机制,频繁的写操作会导致大量的内存页复制,进一步增加系统的负担。如果需要高频率的持久化,可以使用 AOF 持久化机制,并配置每秒同步。
AOF 是写前日志还是写后日志?如何实现 AOF 的?
AOF 是写后日志,即 Redis 先执行写命令,然后将命令追加到 AOF 文件中。实现 AOF 的步骤如下:
- 命令追加:当 Redis 执行写命令时,会将命令以文本形式追加到 AOF 缓冲区中。
- 文件同步:根据配置的同步策略,将 AOF 缓冲区中的内容同步到磁盘上的 AOF 文件中。同步策略有以下几种:
- appendfsync always:每次写操作都同步到磁盘,数据安全性最高,但性能最低。
- appendfsync everysec:每秒同步一次,是默认的同步策略,兼顾了数据安全性和性能。
- appendfsync no:由操作系统决定何时同步,数据安全性最低,但性能最高。
- AOF 重写:随着 AOF 文件的不断增大,可能会导致文件过大,影响恢复速度和磁盘空间。可以通过 AOF 重写来压缩 AOF 文件,只保留最终的数据状态。
什么是 AOF 重写?AOF 重写会阻塞吗?AOF 日志何时会重写?AOF 重写日志时,有新数据写入咋整?
AOF 重写
AOF 重写是指将 Redis 内存中的数据以命令的形式重新写入到一个新的 AOF 文件中,只保留最终的数据状态,从而压缩 AOF 文件的大小。
AOF 重写是否阻塞
AOF 重写通过 fork 子进程来完成,主进程继续处理客户端请求,因此不会阻塞主进程。但在 fork 子进程时,会有短暂的阻塞,因为需要复制主进程的内存数据。
AOF 日志何时会重写
- 手动触发:使用 BGREWRITEAOF 命令手动触发 AOF 重写。
- 自动触发:在 Redis 配置文件中可以配置自动触发 AOF 重写的条件,如:
auto - aof - rewrite - percentage 100
auto - aof - rewrite - min - size 64mb
表示当 AOF 文件的大小比上一次重写后的大小增长了 100%,且 AOF 文件的大小超过 64MB 时,自动触发 AOF 重写。
AOF 重写日志时,有新数据写入咋整
在 AOF 重写期间,主进程会将新的写命令同时追加到旧的 AOF 文件和 AOF 重写缓冲区中。当子进程完成 AOF 重写后,主进程会将 AOF 重写缓冲区中的内容追加到新的 AOF 文件中,然后使用新的 AOF 文件替换旧的 AOF 文件。
主线程 fork 出子进程的是如何复制内存数据的?
在 Linux 系统中,Redis 的主线程通过 fork 系统调用创建子进程。fork 操作会复制主线程的内存页表,但不会立即复制内存数据,而是采用写时复制(Copy - On - Write,COW)机制。具体过程如下:
- fork 操作:主线程调用 fork 系统调用创建子进程,子进程会复制主线程的内存页表,指向相同的物理内存页。
- 写操作:在子进程创建后,如果主线程或子进程对某个内存页进行写操作,操作系统会先复制该内存页,然后再进行写操作。这样可以避免在子进程创建 RDB 文件或进行 AOF 重写时,对内存数据的修改影响到操作的结果。
在重写日志整个过程时,主线程有哪些地方会被阻塞?
在 AOF 重写过程中,主线程可能会在以下两个地方被阻塞:
- fork 子进程时:在调用 fork 系统调用创建子进程时,主线程需要复制内存页表,会有短暂的阻塞。
- 将 AOF 重写缓冲区的内容追加到新的 AOF 文件时:当子进程完成 AOF 重写后,主线程需要将 AOF 重写缓冲区中的内容追加到新的 AOF 文件中,这个过程可能会有短暂的阻塞。
为什么 AOF 重写不复用原 AOF 日志?
AOF 重写不复用原 AOF 日志的原因如下:
- 简化重写逻辑:如果复用原 AOF 日志,需要对原日志进行解析和合并,逻辑复杂,容易出错。而直接从内存中获取数据并生成新的 AOF 文件,逻辑简单,易于实现。
- 避免数据不一致:原 AOF 日志中可能包含一些无效的命令,如重复的命令、过期的命令等。重写时直接从内存中获取数据,可以保证新的 AOF 文件只包含最终的数据状态,避免数据不一致的问题。
- 提高重写效率:直接从内存中获取数据并生成新的 AOF 文件,比解析和合并原 AOF 日志的效率更高。
Redis 过期键的删除策略有哪些?
- 定时删除:在设置键的过期时间时,同时创建一个定时器,当过期时间到达时,立即删除该键。这种策略对内存友好,但会占用大量的 CPU 资源,影响 Redis 的性能。
- 惰性删除:在访问键时,检查键是否过期,如果过期则删除该键。这种策略对 CPU 友好,但会导致过期键占用内存,浪费内存资源。
- 定期删除:每隔一段时间,随机检查一部分键,删除其中过期的键。这种策略是定时删除和惰性删除的折中方案,既可以保证一定的内存利用率,又不会对 CPU 造成太大的压力。
Redis 内存淘汰算法有哪些?
- noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
- allkeys - lru:从所有键中删除最近最少使用(LRU)的键。
- allkeys - random:从所有键中随机删除键。
- volatile - lru:从设置了过期时间的键中删除最近最少使用(LRU)的键。
- volatile - random:从设置了过期时间的键中随机删除键。
- volatile - ttl:从设置了过期时间的键中删除剩余时间最短的键。
- allkeys - lfu:从所有键中删除最不经常使用(LFU)的键。
- volatile - lfu:从设置了过期时间的键中删除最不经常使用(LFU)的键。
Redis 的内存用完了会发生什么?
如果 Redis 的内存用完了,会根据配置的内存淘汰策略进行处理:
- 如果配置为 noeviction,新写入操作会报错。
- 如果配置为其他内存淘汰策略,Redis 会根据策略删除一些键,以释放内存空间。
Redis 如何做内存优化?
- 选择合适的数据类型:根据业务需求选择合适的数据类型,如使用哈希类型存储对象信息,使用有序集合实现排行榜等。
- 控制键的数量:避免创建过多的键,尽量将相关的数据存储在一个键中。
- 使用压缩列表和整数集合:当数据量较小时,Redis 会自动使用压缩列表和整数集合来存储数据,减少内存占用。
- 设置合理的过期时间:对于一些临时数据,设置合理的过期时间,让 Redis 自动删除过期键。
- 使用内存淘汰策略:根据业务需求配置合适的内存淘汰策略,当内存不足时自动删除一些键。
Redis key 的过期时间和永久有效分别怎么设置?
设置过期时间
- EXPIRE key seconds:为键设置指定的过期时间(秒)。
- PEXPIRE key milliseconds:为键设置指定的过期时间(毫秒)。
- EXPIREAT key timestamp:为键设置指定的过期时间戳(秒)。
- PEXPIREAT key milliseconds - timestamp:为键设置指定的过期时间戳(毫秒)。
设置永久有效
默认情况下,Redis 的键是永久有效的。如果已经为键设置了过期时间,可以使用 PERSIST key 命令移除键的过期时间,使其变为永久有效。
Redis 中的管道有什么用?
Redis 管道(Pipeline)是一种批量执行命令的机制,它可以将多个命令一次性发送给 Redis 服务器,然后一次性接收服务器的响应。使用管道的好处如下:
- 减少网络开销:通过将多个命令打包发送,可以减少客户端和服务器之间的网络往返次数,提高性能。
- 提高执行效率:服务器可以一次性处理多个命令,减少了命令执行的时间开销。
什么是 redis 事务?Redis 事务相关命令?Redis 事务的三个阶段?
Redis 事务
Redis 事务是一组命令的集合,这些命令会被一次性、按顺序执行,并且在执行过程中不会被其他客户端的命令打断。
Redis 事务相关命令
- MULTI:开启一个事务。
- EXEC:执行事务中的所有命令。
- DISCARD:取消事务,放弃执行事务中的所有命令。
- WATCH key [key ...]:监视一个或多个键,如果在事务执行之前这些键被其他客户端修改,则事务会被取消。
Redis 事务的三个阶段
- 事务开启:使用 MULTI 命令开启一个事务,后续的命令会被放入事务队列中。
- 命令入队:在事务开启后,执行的所有命令都会被放入事务队列中,而不会立即执行。
- 事务执行:使用 EXEC 命令执行事务队列中的所有命令,或者使用 DISCARD 命令取消事务。
Redis 事务其它实现?
除了使用 MULTI、EXEC 等命令实现事务外,还可以使用 Lua 脚本实现类似事务的功能。Lua 脚本在 Redis 中是原子执行的,即脚本中的所有命令会被一次性执行,不会被其他客户端的命令打断。可以将一组相关的命令封装在 Lua 脚本中,实现类似事务的效果。
Redis 事务中出现错误的处理?
- 入队时错误:如果在命令入队时出现错误,如命令语法错误,Redis 会拒绝执行该命令,并将错误信息返回给客户端。客户端可以选择取消事务或修正错误后重新执行事务。
- 执行时错误:如果在事务执行过程中出现错误,如对不存在的键进行操作,Redis 会继续执行事务中的其他命令,并且不会回滚已经执行的命令。客户端需要根据返回的错误信息进行相应的处理。
Redis 事务中 watch 是如何监视实现的呢?
Redis 使用 WATCH 命令来监视一个或多个键。当执行 WATCH 命令后,Redis 会为每个被监视的键维护一个引用计数器,记录有多少个客户端在监视该键。当一个键被修改时,Redis 会将该键的引用计数器加 1,并标记该键为被修改状态。
在执行 EXEC 命令时,Redis 会检查所有被监视的键是否被修改。如果有任何一个被监视的键被修改,则事务会被取消,EXEC 命令返回空列表。如果所有被监视的键都没有被修改,则事务会正常执行。
为什么 Redis 不支持回滚?
- 简单高效:Redis 的设计目标是简单高效,回滚机制会增加系统的复杂度和性能开销。Redis 认为在大多数情况下,开发者可以通过代码逻辑来处理错误,而不需要复杂的回滚机制。
- 事务错误通常是编程错误:Redis 事务中出现的错误通常是由于开发者的编程错误导致的,如对不存在的键进行操作。这些错误应该在开发阶段被发现和修正,而不是依赖回滚机制来处理。
Redis 对 ACID 的支持性理解?
- 原子性(Atomicity):Redis 事务保证了一组命令的原子性,即要么所有命令都执行,要么所有命令都不执行。但需要注意的是,Redis 事务在执行过程中如果出现错误,不会回滚已经执行的命令。
- 一致性(Consistency):Redis 事务可以保证在事务执行过程中,不会被其他客户端的命令打断,从而保证了数据的一致性。但由于 Redis 不支持回滚,在事务执行过程中出现错误时,可能会导致数据不一致。
- 隔离性(Isolation):Redis 事务是按顺序执行的,在事务执行过程中不会被其他客户端的命令打断,因此具有一定的隔离性。
- 持久性(Durability):Redis 的持久性取决于使用的持久化机制。如果使用 RDB 持久化机制,可能会丢失最后一次快照之后的数据;如果使用 AOF 持久化机制,并且配置为每次写操作都同步到磁盘,则可以保证事务的持久性。
Redis 集群的主从复制模型是怎样的?
Redis 集群的主从复制模型是一种数据复制机制,用于实现数据的备份和读写分离。在主从复制模型中,有一个主节点(Master)和多个从节点(Slave)。主节点负责处理写操作,从节点负责处理读操作。主节点将写操作的命令发送给从节点,从节点执行这些命令,从而保证主从节点的数据一致性。
主从复制的过程如下:
- 建立连接:从节点向主节点发送 SYNC 或 PSYNC 命令,请求进行数据同步。
- 全量复制:如果是第一次同步或无法进行增量复制,主节点会进行全量复制。主节点会创建一个 RDB 文件,并将其发送给从节点,从节点将 RDB 文件加载到内存中。
- 增量复制:在全量复制完成后,主节点会将新的写操作命令发送给从节点,从节点执行这些命令,实现数据的增量同步。
Redis 全量复制的三个阶段?
- 主节点创建 RDB 文件:主节点接收到从节点的 SYNC 命令后,会 fork 子进程创建 RDB 文件。在创建 RDB 文件的过程中,主节点会将新的写操作命令记录在缓冲区中。
- 主节点发送 RDB 文件:主节点将创建好的 RDB 文件发送给从节点,从节点接收并加载 RDB 文件到内存中。
- 主节点发送缓冲区中的命令:主节点将创建 RDB 文件期间记录在缓冲区中的命令发送给从节点,从节点执行这些命令,保证主从节点的数据一致性。
Redis 为什么会设计增量复制?
全量复制需要将主节点的所有数据复制到从节点,当数据量较大时,会占用大量的网络带宽和时间。增量复制的设计旨在减少全量复制的开销,提高复制效率。增量复制只复制主从节点之间的差异数据,即主节点在从节点同步之后新产生的写操作命令,从而减少了网络传输和数据加载的时间。
Redis 增量复制的流程?
- 建立连接:从节点向主节点发送 PSYNC 命令,携带自己的复制偏移量(replication offset)和主节点的运行 ID(run ID)。
- 判断是否可以进行增量复制:主节点根据从节点发送的复制偏移量和运行 ID,判断是否可以进行增量复制。如果可以,主节点会返回 CONTINUE 响应;如果不可以,主节点会返回 FULLRESYNC 响应,要求从节点进行全量复制。
- 增量复制:如果主节点返回 CONTINUE 响应,主节点会将从节点复制偏移量之后的写操作命令发送给从节点,从节点执行这些命令,实现数据的增量同步。
增量复制如果在网络断开期间,repl_backlog_size 环形缓冲区写满之后,从库是会丢失掉那部分被覆盖掉的数据,还是直接进行全量复制呢?
如果在网络断开期间,repl_backlog_size 环形缓冲区写满之后,从库会丢失掉那部分被覆盖掉的数据,此时从库会向主库发送 PSYNC 命令请求进行全量复制。因为从库无法通过增量复制来恢复丢失的数据,只能通过全量复制来重新同步主库的数据。
Redis 为什么不持久化的主服务器自动重启非常危险呢?
如果主服务器没有开启持久化机制,当主服务器自动重启后,内存中的数据会全部丢失。由于主从复制是从主节点向从节点复制数据,主节点数据丢失后,从节点会从主节点重新同步数据,导致从节点的数据也会丢失。此外,在主服务器重启期间,客户端的写操作会失败,影响系统的正常运行。
Redis 为什么主从全量复制使用 RDB 而不使用 AOF?
- 文件大小:RDB 文件是内存数据的快照,文件紧凑,传输速度快;而 AOF 文件是追加写命令的日志文件,通常比 RDB 文件大,传输和加载时间长。
- 恢复速度:RDB 文件的恢复速度比 AOF 文件快,因为只需要将 RDB 文件加载到内存中,而 AOF 文件需要重新执行所有的写命令。
- 性能影响:创建 RDB 文件的过程对主节点的性能影响较小,因为是通过 fork 子进程来完成的;而 AOF 文件的重写过程可能会对主节点的性能产生一定的影响。
Redis 为什么还有无磁盘复制模式?
在传统的主从全量复制中,主节点需要将 RDB 文件保存到磁盘上,然后再将 RDB 文件发送给从节点。当数据量较大时,磁盘 I/O 会成为性能瓶颈。无磁盘复制模式的设计旨在避免磁盘 I/O 的开销,提高复制效率。在无磁盘复制模式下,主节点直接将 RDB 文件通过网络发送给从节点,而不需要将 RDB 文件保存到磁盘上。
Redis 为什么还会有从库的从库的设计?
- 减轻主库压力:在大规模的 Redis 集群中,主库可能需要同时向多个从库复制数据,会占用大量的网络带宽和 CPU 资源。通过设置从库的从库,可以将复制任务分散到多个节点上,减轻主库的压力。
- 提高读性能:从库的从库可以提供额外的读节点,增加系统的读能力,提高读性能。
- 增强数据冗余:增加从库的从库可以提高数据的冗余度,增强系统的可靠性和容错能力。
Redis 哨兵机制?哨兵实现了什么功能呢?
Redis 哨兵(Sentinel)是 Redis 的高可用性解决方案,它是一个分布式系统,可以监控 Redis 主从节点的状态,并在主节点出现故障时自动进行故障转移。
哨兵实现的功能如下:
- 监控:哨兵会定期检查 Redis 主从节点的状态,包括节点是否存活、节点的角色等。
- 通知:当哨兵发现主节点或从节点出现故障时,会向管理员或其他客户端发送通知。
- 自动故障转移:当主节点出现故障时,哨兵会从从节点中选择一个新的主节点,并将其他从节点指向新的主节点,实现自动故障转移。
- 配置管理:哨兵会管理 Redis 节点的配置信息,如主节点的地址、从节点的地址等。
Redis 哨兵集群是通过什么方式组建的?
Redis 哨兵集群可以通过以下方式组建:
- 配置文件:在每个哨兵节点的配置文件中,指定要监控的 Redis 主节点的地址和端口,以及其他哨兵节点的地址和端口。
- 自动发现:哨兵节点之间可以通过互相发送 PING 命令来发现彼此,并自动组建集群。
Redis 哨兵是如何监控 Redis 集群的?
Redis 哨兵通过定期向 Redis 节点发送 PING 命令来监控节点的状态。如果在一定时间内没有收到节点的响应,哨兵会认为该节点出现故障。哨兵还会监控节点的角色信息,如主节点、从节点等。当节点的角色发生变化时,哨兵会更新自己的状态信息。
Redis 哨兵如何判断主库已经下线了呢?
Redis 哨兵判断主库下线分为主观下线和客观下线:
- 主观下线:当一个哨兵在一定时间内没有收到主库的响应时,会认为主库主观下线。主观下线只是单个哨兵的判断,可能存在误判。
- 客观下线:当多个哨兵都认为主库主观下线时,会进行投票。如果投票结果超过配置的 quorum 值,则认为主库客观下线。客观下线表示主库确实出现了故障,需要进行故障转移。
Redis 哨兵的选举机制是什么样的?
当主库被判断为客观下线后,哨兵集群需要选举一个领导者来进行故障转移。选举机制如下:
- 每个哨兵都可以成为领导者:每个哨兵都可以向其他哨兵发送竞选领导者的请求。
- 投票机制:哨兵之间通过投票来选举领导者。每个哨兵会根据自己的优先级和运行 ID 来投票,优先级高的哨兵更容易被选为领导者。
- 多数原则:当一个哨兵获得了超过半数的选票时,它就会被选为领导者。
Redis 1 主 4 从,5 个哨兵,哨兵配置 quorum 为 2,如果 3 个哨兵故障,当主库宕机时,哨兵能否判断主库 “客观下线”?能否自动切换?
- 能否判断主库 “客观下线”:可以。
quorum
设置为 2 意味着只要有 2 个哨兵认为主库下线,就可以判定其客观下线。此时虽有 3 个哨兵故障,但剩下的 2 个哨兵若都认为主库主观下线,就满足条件判定主库客观下线。 - 能否自动切换:不能。进行故障转移需要选举出一个哨兵领导者来执行操作,而选举领导者需要多数哨兵投票同意。原本 5 个哨兵时,多数为 3 个,现在只剩 2 个哨兵,无法满足多数原则,所以不能选出领导者,也就无法自动切换。
主库判定客观下线了,那么如何从剩余的从库中选择一个新的主库呢?
- 过滤不健康的从库:剔除掉处于主观下线状态、与主库断开连接时间过长、网络连接不稳定或者响应超时的从库。
- 选择优先级最高的从库:在
redis.conf
里可对每个从库设置优先级,优先级数值越小则优先级越高。若某从库优先级最高,就优先选择它。 - 选择复制偏移量最大的从库:若从库优先级相同,会选择复制偏移量最大的从库。复制偏移量体现了从库与原主库数据的同步程度,偏移量越大,数据越新。
- 选择运行 ID 最小的从库:若前面条件都相同,会选择运行 ID 最小的从库。运行 ID 是 Redis 实例启动时生成的唯一标识符。
新的主库选择出来后,如何进行故障的转移?
- 提升从库为主库:被选中的从库会收到
SLAVEOF NO ONE
命令,使其转变为主库。 - 更新其他从库配置:向其余从库发送
SLAVEOF
命令,让它们将新主库当作主节点,开始从新主库进行数据复制。 - 更新客户端配置:通知客户端新主库的地址,使客户端能够连接到新主库继续进行读写操作。
- 哨兵集群更新状态:哨兵集群会更新自身的状态信息,记录新主库的信息,并且持续监控集群状态。
Redis 事件机制?
Redis 事件机制用于处理文件事件(如客户端连接、读写操作)和时间事件(如定时任务)。它采用 I/O 多路复用技术,能同时监听多个文件描述符,当有事件发生时,会调用相应的事件处理器进行处理。
Redis 文件事件的模型?
Redis 文件事件模型基于 I/O 多路复用技术,主要包含以下几个部分:
- 文件描述符:每个客户端连接、监听端口等都会对应一个文件描述符,用于表示 I/O 操作的对象。
- I/O 多路复用程序:负责监听多个文件描述符的状态变化,如可读、可写等。Redis 支持多种 I/O 多路复用实现,像
select
、poll
、epoll
和kqueue
等,会依据不同的操作系统选择最优的实现。 - 事件分派器:当 I/O 多路复用程序检测到某个文件描述符有事件发生时,会将事件传递给事件分派器,由其根据事件类型调用相应的事件处理器。
- 事件处理器:针对不同类型的文件事件(如连接事件、读事件、写事件),有不同的事件处理器进行处理。
什么是 Redis 发布订阅?
Redis 发布订阅(Pub/Sub)是一种消息通信模式,允许消息的发布者(Publisher)向指定的频道(Channel)发布消息,而订阅者(Subscriber)可以订阅一个或多个频道,接收该频道上发布的消息。这种模式实现了消息的生产者和消费者之间的解耦,适用于实时消息系统、聊天应用等场景。
Redis 发布订阅有哪两种方式?
- 频道订阅:客户端使用
SUBSCRIBE
命令订阅一个或多个频道,使用PUBLISH
命令向指定频道发布消息。订阅者会接收到该频道上发布的所有消息。 - 模式订阅:客户端使用
PSUBSCRIBE
命令订阅一个或多个模式,模式可以使用通配符(如*
)。当有消息发布到匹配该模式的频道时,订阅者会接收到消息。
什么是 Redis Cluster?
Redis Cluster 是 Redis 的分布式解决方案,用于实现 Redis 的水平扩展和高可用性。它将数据分散存储在多个节点上,每个节点负责一部分数据的存储和处理。节点之间通过 Gossip 协议进行通信,自动发现和维护集群状态。当某个节点出现故障时,集群会自动进行故障转移,保证服务的可用性。
说说 Redis 哈希槽的概念?为什么是 16384 个?
- 哈希槽概念:Redis Cluster 把整个键空间划分成 16384 个哈希槽(Hash Slot),每个键都会通过哈希算法映射到其中一个哈希槽上。每个节点负责处理一部分哈希槽,通过这种方式实现数据的分布式存储。
- 选择 16384 个的原因:
- 消息交换效率:在集群节点间进行信息交换时,需要携带节点负责的哈希槽信息。如果哈希槽数量过多,会增加消息的大小,降低信息交换效率;如果数量过少,会导致数据分布不够均匀。16384 个哈希槽在消息大小和数据分布均匀性之间取得了较好的平衡。
- 节点数量限制:在实际应用中,一个 Redis Cluster 集群的节点数量通常不会超过 1000 个。16384 个哈希槽足够满足大多数集群的需求,并且可以保证每个节点负责的哈希槽数量相对均衡。
Redis 集群会有写操作丢失吗?为什么?
Redis 集群在某些情况下可能会出现写操作丢失的情况,原因如下:
- 网络分区:当集群发生网络分区时,部分节点可能会与其他节点失去连接,形成独立的子网。此时,如果客户端向处于孤立子网的主节点写入数据,而该子网最终未能成为集群的一部分,那么这些写入的数据就会丢失。
- 异步复制:Redis 集群采用异步复制的方式将主节点的数据复制到从节点。在主节点将数据写入内存后,会立即返回给客户端写操作成功的响应,而不会等待从节点复制完成。如果主节点在数据复制到从节点之前发生故障,那么这些未复制的数据就会丢失。
Redis 客户端有哪些?
- Jedis:Java 语言编写的 Redis 客户端,功能丰富,使用简单,支持 Redis 的各种数据类型和命令。
- Lettuce:同样是 Java 语言的 Redis 客户端,基于 Netty 框架,支持异步、响应式编程,性能较高。
- StackExchange.Redis:.NET 平台下的 Redis 客户端,支持同步、异步和多路复用操作,使用方便。
- redis - py:Python 语言的 Redis 客户端,提供了简单易用的 API,支持 Redis 的各种功能。
- Node - Redis:Node.js 平台下的 Redis 客户端,支持回调、Promise 和 Async/Await 等多种编程方式。
Redis 如何做大量数据插入?
- 使用管道(Pipeline):将多个命令打包一次性发送给 Redis 服务器,减少网络往返次数,提高插入效率。
- 使用批量插入命令:如
MSET
用于批量设置字符串键值对,RPUSH
用于批量向列表尾部插入元素等。 - 使用 Lua 脚本:将多个插入命令封装在 Lua 脚本中,在 Redis 服务器端原子执行,减少网络开销。
- 使用 Redis 协议:直接按照 Redis 协议格式构造命令,批量发送给 Redis 服务器,这种方式可以进一步提高性能。
Redis 实现分布式锁实现?
使用 Redis 实现分布式锁的基本思路是利用 Redis 的原子操作来保证在分布式环境下同一时间只有一个客户端可以获取锁。以下是一个简单的实现示例:
import redis
import time# 连接 Redis
r = redis.Redis(host='localhost', port=6379, db=0)def acquire_lock(lock_name, acquire_timeout=10, lock_timeout=10):end_time = time.time() + acquire_timeoutwhile time.time() < end_time:# 使用 SETNX 命令尝试获取锁if r.setnx(lock_name, 'locked'):# 设置锁的过期时间,防止死锁r.expire(lock_name, lock_timeout)return Truetime.sleep(0.1)return Falsedef release_lock(lock_name):# 释放锁r.delete(lock_name)# 使用示例
if acquire_lock('my_lock'):try:# 执行需要加锁的操作print("获取到锁,执行操作...")finally:release_lock('my_lock')
else:print("未能获取到锁")
在上述代码中,acquire_lock
函数用于尝试获取锁,release_lock
函数用于释放锁。通过 setnx
命令保证了只有一个客户端可以成功获取锁,同时设置了锁的过期时间,避免死锁。
什么是 RedLock?
RedLock 是 Redis 官方提出的一种分布式锁算法,用于在多个 Redis 节点上实现分布式锁,以提高锁的可靠性。RedLock 的实现步骤如下:
- 获取当前时间:客户端记录获取锁开始的时间。
- 依次尝试获取锁:客户端依次向多个独立的 Redis 节点发送获取锁的请求,每个节点的锁过期时间相同。
- 判断是否获取到锁:如果客户端在大多数(超过半数)的 Redis 节点上成功获取到锁,并且从开始获取锁到获取到大多数锁的时间小于锁的过期时间,则认为客户端成功获取到锁。
- 释放锁:客户端在完成操作后,需要向所有参与的 Redis 节点发送释放锁的请求。
Redis 缓存有哪些问题,如何解决?
- 缓存穿透:指查询一个不存在的数据,导致请求直接穿透缓存访问数据库。解决方法有:
- 布隆过滤器:在缓存之前使用布隆过滤器过滤掉一定不存在的数据。
- 缓存空值:当查询到的数据为空时,也将空值存入缓存,并设置较短的过期时间。
- 缓存击穿:指一个热点 key 在缓存过期的瞬间,大量请求同时访问该 key,导致请求直接访问数据库。解决方法有:
- 设置热点 key 永不过期:对于一些热点 key,不设置过期时间,而是通过后台任务定期更新缓存。
- 加互斥锁:在缓存过期时,只允许一个请求去更新缓存,其他请求等待缓存更新完成后再访问。
- 缓存雪崩:指大量的缓存 key 在同一时间过期,导致大量请求直接访问数据库,造成数据库压力过大。解决方法有:
- 分散过期时间:为不同的 key 设置不同的过期时间,避免大量 key 同时过期。
- 多级缓存:使用多级缓存,如本地缓存和分布式缓存结合,减轻数据库压力。
- 缓存预热:在系统启动时,将一些热点数据提前加载到缓存中。
Redis 性能问题有哪些,如何分析定位解决?
- 性能问题:
- 慢查询:某些复杂的命令执行时间过长,影响 Redis 的整体性能。
- 内存不足:内存使用达到上限,触发内存淘汰策略,导致性能下降。
- 网络问题:网络延迟、带宽不足等问题会影响客户端与 Redis 服务器之间的通信。
- CPU 瓶颈:Redis 单线程模型下,CPU 成为性能瓶颈。
- 分析定位:
- 慢查询日志:通过配置 Redis 的慢查询日志,记录执行时间超过指定阈值的命令,分析慢查询的原因。
- 内存监控:使用
INFO memory
命令查看 Redis 的内存使用情况,监控内存使用率和内存碎片率。 - 网络监控:使用网络监控工具(如
ping
、traceroute
)检查网络延迟和带宽情况。 - CPU 监控:使用系统监控工具(如
top
、htop
)查看 Redis 进程的 CPU 使用率。
- 解决方法:
- 优化命令:避免使用复杂的命令,尽量使用简单高效的命令组合。
- 内存优化:合理设置内存淘汰策略,定期清理过期键,减少内存碎片。
- 网络优化:优化网络配置,增加带宽,减少网络延迟。
- 多线程优化:在 Redis 6.0 及以上版本中,可以开启多线程功能,提高 CPU 利用率。
Redis 单线程模型? 在 6.0 之前如何提高多核 CPU 的利用率?
- 单线程模型:Redis 在 6.0 之前采用单线程模型,即只有一个主线程负责处理客户端的请求和执行命令。主线程通过 I/O 多路复用技术同时监听多个客户端连接,当有请求到达时,依次处理这些请求。
- 提高多核 CPU 利用率的方法:
- 多实例部署:在一台服务器上部署多个 Redis 实例,每个实例使用一个 CPU 核心,通过负载均衡器将客户端请求分发到不同的实例上。
- 分片集群:使用 Redis Cluster 或其他分片方案,将数据分散存储在多个节点上,每个节点使用一个 CPU 核心,提高整体的处理能力。
Redis6.0 之前的版本真的是单线程的吗?
严格来说,Redis 6.0 之前的版本并非完全单线程。虽然处理客户端请求和执行命令的主线程是单线程的,但 Redis 还存在一些后台线程,用于处理一些异步操作,如 AOF 重写、RDB 文件生成等。不过,这些后台线程主要用于处理 I/O 密集型任务,对客户端请求的处理仍然是由主线程单线程完成的。
Redis6.0 之前为什么一致不用多线程?
- 简单易维护:单线程模型避免了多线程带来的锁竞争、上下文切换等复杂问题,代码逻辑简单,易于开发和维护。
- 内存操作为主:Redis 的主要操作是基于内存的,单线程执行操作时不会因为锁竞争而降低性能,因此单线程模型足以满足大多数场景的需求。
- I/O 多路复用优化:通过 I/O 多路复用技术,Redis 可以同时监听多个客户端连接,提高了并发处理能力,减少了对多线程的需求。
Redis6.0 为什么要引入多线程呢?
- 提高网络 I/O 性能:随着网络带宽的不断增加,单线程的网络 I/O 处理能力成为瓶颈。引入多线程可以并行处理网络 I/O 操作,提高网络 I/O 性能。
- 充分利用多核 CPU:现代服务器通常配备多核 CPU,单线程模型无法充分利用多核 CPU 的计算能力。引入多线程可以将部分操作分配到多个线程中并行执行,提高 CPU 利用率。
Redis6.0 默认是否开启了多线程?
Redis 6.0 默认没有开启多线程。需要在配置文件中手动配置 io - threads
和 io - threads - do - reads
参数来开启多线程功能。
Redis6.0 多线程开启时,线程数如何设置?
线程数的设置需要根据服务器的 CPU 核心数和实际业务场景进行调整。一般来说,可以将 io - threads
参数设置为 CPU 核心数减 1,例如,如果服务器有 8 个 CPU 核心,可以将 io - threads
设置为 7。同时,需要根据实际测试结果进行优化,以达到最佳性能。
Redis6.0 多线程的实现机制?
Redis 6.0 的多线程主要用于处理网络 I/O 操作,具体实现机制如下:
- 主线程负责监听和分发:主线程通过 I/O 多路复用技术监听客户端连接,当有请求到达时,将请求分发给多个 I/O 线程处理。
- I/O 线程负责读写操作:多个 I/O 线程并行处理网络读写操作,提高网络 I/O 性能。
- 主线程负责命令执行:I/O 线程将读取到的命令传递给主线程,主线程负责执行命令,并将结果返回给客户端。
开启多线程后,是否会存在线程并发安全问题?
在 Redis 6.0 的多线程模型中,只有网络 I/O 操作是多线程处理的,命令执行仍然由主线程单线程完成,因此不会存在线程并发安全问题。多个 I/O 线程之间通过无锁数据结构进行通信,避免了锁竞争带来的性能开销。同时,Redis 的数据操作是原子性的,主线程在执行命令时不会被其他线程打断,保证了数据的一致性。