redis 面试题
# 1、使用 redis 有哪些好处
速度快,因为数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是 O(1)
支持丰富数据类型,支持 string,list,set,sorted set,hash
支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
丰富的特性:可用于缓存,消息,按 key 设置过期时间,过期后将会自动删除
# 2、redis 相比 memcached 有哪些优势
memcached 所有的值均是简单的字符串,redis 作为其替代者,支持更为丰富的数据类型
redis 的速度比 memcached 快很多
redis 可以持久化其数据
# 3、redis 常见性能问题和解决方案
Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件
如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次
为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个局域网内
尽量避免在压力很大的主库上增加从库
主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...
这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变。
# 4、redis 最适合的场景
Redis 最适合所有数据 in-momory 的场景,虽然 Redis 也提供持久化功能,但实际更多的是一个 disk-backed 的功能,跟传统意义上的持久化有比较大的差别,那么可能大家就会有疑问,似乎 Redis 更像一个加强版的 Memcached,那么何时使用 Memcached,何时使用 Redis 呢?
如果简单地比较 Redis 与 Memcached 的区别,大多数都会得到以下观点:
Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。
Redis 支持数据的备份,即 master-slave 模式的数据备份。
Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
(1)会话缓存(Session Cache) 最常用的一种使用 Redis 的情景是会话缓存(session cache)。用 Redis 缓存会话比其他存储(如 Memcached)的优势在于:Redis 提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗? 幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用 Redis 来缓存会话的文档。甚至广为人知的商业平台 Magento 也提供 Redis 的插件。
(2)全页缓存(FPC) 除基本的会话 token 之外,Redis 还提供很简便的 FPC 平台。回到一致性问题,即使重启了 Redis 实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似 PHP 本地 FPC。 再次以 Magento 为例,Magento 提供一个插件来使用 Redis 作为全页缓存后端。 此外,对 WordPress 的用户来说,Pantheon 有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。
(3)队列 Reids 在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得 Redis 能作为一个很好的消息队列平台来使用。Redis 作为队列使用的操作,就类似于本地程序语言(如 Python)对 list 的 push/pop 操作。 如果你快速的在 Google 中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用 Redis 创建非常好的后端工具,以满足各种队列需求。例如,Celery 有一个后台就是使用 Redis 作为 broker,你可以从这里去查看。
(4)排行榜/计数器 Redis 在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis 只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的 10 个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可: 当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行: ZRANGE user_scores 0 10 WITHSCORES Agora Games 就是一个很好的例子,用 Ruby 实现的,它的排行榜就是使用 Redis 来存储数据的,你可以在这里看到。
(5)发布/订阅 最后(但肯定不是最不重要的)是 Redis 的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用 Redis 的发布/订阅功能来建立聊天系统!(不,这是真的,你可以去核实)。
Redis 提供的所有特性中,我感觉这个是喜欢的人最少的一个,虽然它为用户提供如果此多功能。
# 5、redis 的一些其他特点
(1)Redis 是单进程单线程的 redis 利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销
(2)读写分离模型 通过增加 Slave DB 的数量,读的性能可以线性增长。为了避免 Master DB 的单点故障,集群一般都会采用两台 Master DB 做双机热备,所以整个集群的读和写的可用性都非常高。 读写分离架构的缺陷在于,不管是 Master 还是 Slave,每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限于单个节点的存储能力,而且对于 Write-intensive 类型的应用,读写分离架构并不适合。
(3)数据分片模型 为了解决读写分离模型的缺陷,可以将数据分片模型应用进来。 可以将每个节点看成都是独立的 master,然后通过业务实现数据分片。 结合上面两种模型,可以将每个 master 设计成由一个 master 和多个 slave 组成的模型。
(4)Redis 的回收策略
volatile-lru:从已设置过期时间的数据集(server.db\[i\].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db\[i\].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db\[i\].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db\[i\].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db\[i\].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
2
3
4
5
6
注意这里的 6 种机制,volatile 和 allkeys 规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的 lru、ttl 以及 random 是三种不同的淘汰策略,再加上一种 no-enviction 永不回收的策略。
使用策略规则:
如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用 allkeys-lru
如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用 allkeys-random
# 6、MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证 redis 中的数据都是热点数据
相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6 种数据淘汰策略见上面一条
# 7、假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来
使用 keys 指令可以扫出指定模式的 key 列表。
对方接着追问:如果这个 redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问题?
这个时候你要回答 redis 关键的一个特性:redis 的单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。
# 8、Redis 常见的性能问题都有哪些?如何解决
Master 写内存快照,save 命令调度 rdbSave 函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以 Master 最好不要写内存快照。
Master AOF 持久化,如果不重写 AOF 文件,这个持久化方式对性能的影响是最小的,但是 AOF 文件会不断增大,AOF 文件过大会影响 Master 重启的恢复速度。Master 最好不要做任何持久化工作,包括内存快照和 AOF 日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个 Slave 开启 AOF 备份数据,策略为每秒同步一次。
Master 调用 BGREWRITEAOF 重写 AOF 文件,AOF 在重写的时候会占大量的 CPU 和内存资源,导致服务 load 过高,出现短暂服务暂停现象。
Redis 主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave 和 Master 最好在同一个局域网内
# 9、Redis 有哪些数据结构
字符串 String、字典 Hash、列表 List、集合 Set、有序集合 SortedSet。
如果你是 Redis 中高级用户,还需要加上下面几种数据结构 HyperLogLog、Geo、Pub/Sub。
如果你说还玩过 Redis Module,像 BloomFilter,RedisSearch,Redis-ML,面试官得眼睛就开始发亮了。
# 10、使用过 Redis 分布式锁么,它是什么回事
Redis 分布式锁是一种基于 Redis 实现的分布式锁机制。在分布式系统中,多个节点同时访问共享资源时,为了保证数据的一致性和避免冲突,需要使用分布式锁来控制并发访问。
Redis 分布式锁的实现原理通常是通过在 Redis 中设置一个特定的键值对来实现。当一个节点需要获取锁时,它尝试在 Redis 中设置一个指定的键值对,如果设置成功,则表示获取到了锁;如果设置失败,则表示锁已被其他节点占用。
为了避免死锁和解决锁的争抢问题,Redis 分布式锁通常会设置一个过期时间,节点在获取到锁后需要在一定时间内完成操作,并在操作完成后释放锁。同时,为了保证锁的可靠性,还需要考虑锁的可重入性、防止误删锁等问题。
使用 Redis 分布式锁可以有效地解决分布式系统中的并发访问问题,保证数据的一致性和避免冲突。但需要注意的是,Redis 分布式锁并不能解决所有的并发访问问题,如网络延迟、节点宕机等情况仍然需要额外的处理机制。
- 如果在 setnx 之后执行 expire 之前进程意外 crash 或者要重启维护了,那会怎么样?
如果在执行SETNX
(尝试获取锁)之后、执行EXPIRE
(设置锁的过期时间)之前进程意外崩溃或需要重启维护,可能会导致以下情况之一:
锁无法过期:如果进程崩溃或重启维护之前没有设置锁的过期时间,那么锁可能会一直存在于 Redis 中,导致其他节点无法获取到该锁。
锁过早释放:如果进程崩溃或重启维护之前已经执行了
SETNX
成功,并且在EXPIRE
之前崩溃,那么锁可能会在进程重启后自动释放,导致其他节点错误地获取到了该锁。
为了解决这些问题,可以采取以下措施:
设置一个合理的过期时间:在执行
SETNX
成功后,立即执行EXPIRE
设置锁的过期时间。这样即使进程崩溃或重启维护,锁也会在一定时间后自动过期,避免长时间占用锁资源。使用续约机制:为了避免在进程意外崩溃或重启维护时导致锁永远得不到释放,可以考虑使用锁的续租(Renewal)机制。续租机制可以在锁的过期时间即将到达时,通过重置锁的过期时间来延长锁的有效期。
具体的实现方式如下:
- 在执行
SETNX
成功后,立即执行EXPIRE
设置锁的过期时间。 - 启动一个后台的定时任务,定期检查锁的剩余过期时间。
- 当检测到锁的剩余过期时间接近设定的阈值时,重新设置锁的过期时间,延长锁的有效期。
这样,即使进程意外崩溃或重启维护,定时任务会在锁的过期时间接近时继续延长锁的有效期,确保锁不会无法释放。
需要注意的是,续租机制并不能完全解决所有情况下的锁释放问题,例如节点宕机等极端情况。在分布式系统中,通常需要综合考虑多种因素,如心跳检测、选举机制等来保证锁的可靠性和一致性。
- 在执行
使用分布式锁框架:可以使用已经实现了锁续约、自动释放锁等功能的分布式锁框架,如 Redlock、Redisson 等。这些框架提供了更完善的分布式锁解决方案,可以避免一些常见的问题。
需要根据具体的业务场景和需求来选择合适的处理方式,以确保分布式锁的可靠性和一致性。
# 11、使用过 Redis 做异步队列么,你是怎么用的
在 Redis 中实现异步队列可以使用 Redis 的列表数据结构和发布/订阅功能。
使用 Redis 的列表数据结构:将任务添加到一个 Redis 列表中,即作为队列。生产者将任务添加到队列的尾部,消费者从队列的头部获取任务进行处理。
生产者将任务添加到队列:
LPUSH queue_name task_data
1消费者从队列获取任务:
BRPOP queue_name timeout
1BRPOP
命令会从队列的头部获取任务,如果队列为空,则会等待一段时间(timeout)直到有任务可获取。使用 Redis 的发布/订阅功能:可以使用 Redis 的发布/订阅功能实现生产者和消费者之间的消息传递。
生产者发布任务:
PUBLISH channel_name task_data
1消费者订阅任务:
SUBSCRIBE channel_name
1消费者通过订阅指定的频道(channel)来接收生产者发布的任务。
需要注意的是,Redis 是一个内存数据库,所以异步队列中的任务数据可能会占用较多的内存。可以通过设置合适的队列长度限制和定期清理过期任务来控制内存使用情况。
此外,还可以使用 Redis 的其他功能来增强异步队列的功能,例如使用 Redis 的持久化功能来保证任务的可靠性,使用 Redis 的集群功能来提高系统的可扩展性等。
- 发布/订阅模式的缺点
发布/订阅模式的缺点
发布/订阅模式在某些场景下可能存在一些缺点,包括:
丢失消息:发布/订阅模式是一种异步通信方式,发布者发布消息后,不会知道有多少订阅者接收到了消息。如果订阅者在消息发布之前或期间断开连接,那么它将无法接收到该消息,导致消息丢失。
消息顺序性:在发布/订阅模式中,消息的接收顺序是不确定的。多个订阅者可能以不同的顺序接收到消息,这可能会导致一些应用场景中的问题。
系统复杂性:使用发布/订阅模式需要管理订阅者和频道的关系,以及处理订阅者的连接和断开等。这增加了系统的复杂性,并且可能需要额外的开发和维护工作。
可靠性:发布/订阅模式中的消息传递是不可靠的,因为发布者发送消息后,不会得到确认或反馈。如果需要确保消息的可靠性传递,需要额外的机制来处理消息丢失或重复等问题。
扩展性:在大规模的发布/订阅系统中,可能需要考虑系统的扩展性问题。如果订阅者数量过多或消息频率过高,可能会导致系统负载过高,需要采取一些措施来提高系统的性能和扩展性。
总的来说,发布/订阅模式在某些场景下可能不适合或需要额外的机制来解决一些问题。在选择使用发布/订阅模式时,需要根据具体的需求和系统特点进行综合考虑。
- redis 如何实现延时队列
Redis 可以通过使用有序集合(Sorted Set)和定时任务来实现延时队列。
以下是一种基本的实现方式:
将任务数据作为有序集合中的成员,成员的分值(score)为任务的执行时间戳。
ZADD delay_queue <timestamp> <task_data>
1这样,任务会按照执行时间戳排序,成为一个有序的延时队列。
使用一个定时任务(例如每秒钟或每隔一段时间执行一次)来检查有序集合中是否有已经到期的任务。
ZRANGEBYSCORE delay_queue 0 <current_timestamp> WITHSCORES
1这个命令会返回所有执行时间戳小于当前时间戳的任务。然后可以将这些任务从有序集合中移除,并将它们添加到待处理队列中。
ZREMRANGEBYSCORE delay_queue 0 <current_timestamp>
1在待处理队列中处理任务。
RPOP processing_queue
1这个命令会从待处理队列中弹出一个任务进行处理。
如果任务需要延时执行,可以将任务重新添加到有序集合中,设置新的执行时间戳。
ZADD delay_queue <new_timestamp> <task_data>
1这样任务就会在新的执行时间到达时被重新加入到延时队列中。
需要注意的是,以上的实现方式是一种基本的思路,具体的实现可能还需要考虑一些细节问题,例如多个消费者之间的并发处理、任务的重试机制、任务的持久化等。同时,定时任务的频率也需要根据实际情况进行调整,以平衡系统的性能和实时性。
# 12、如果有大量的 key 需要设置同一时间过期,一般需要注意什么
当有大量的 key 需要设置同一时间过期时,需要注意以下几点:
性能影响:设置大量的 key 同一时间过期会对系统性能产生影响。一般需要在时间上加一个随机值,使得过期时间分散一些。过期键的处理可能会导致数据库的负载增加,因此需要评估系统的性能和资源消耗。
内存消耗:过期键需要占用内存来维护过期时间和相关数据结构。如果过期键的数量很大,会增加系统的内存消耗,需要确保系统有足够的可用内存。
过期策略:选择合适的过期策略是重要的。可以使用主动过期或惰性过期策略。主动过期是在设置 key 的同时设置过期时间,而惰性过期是在访问 key 时检查是否过期。根据实际需求选择合适的策略。
过期时间设置:需要根据业务需求合理设置过期时间。过期时间过短可能导致频繁的 key 过期和重新加载的操作,增加系统负载;过期时间过长可能导致过期键占用过多内存资源。
容错处理:在大量 key 同一时间过期的情况下,需要考虑容错处理。例如,设置备份机制或灾难恢复策略,以防止数据丢失或系统故障。
监控和调优:对于大量 key 同一时间过期的场景,需要进行监控和调优。监控系统的性能指标,如内存使用率、过期键的数量等,及时发现和解决问题。根据实际情况,进行性能调优,优化过期键的处理方式和过期时间的设置。
总之,在大量 key 同一时间过期的情况下,需要综合考虑性能、内存消耗、过期策略、过期时间设置、容错处理和监控调优等方面,以确保系统的稳定性和性能。
# 13、为什么 Redis 需要把所有数据放到内存中
Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 redis 具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘 I/O 速度为严重影响 redis 的性能。在内存越来越便宜的今天,redis 将会越来越受欢迎。 如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。
# 14、Redis 持久化机制
Redis 提供了两种持久化机制,分别是 RDB(Redis Database)和 AOF(Append Only File)。
RDB 是 Redis 默认的持久化方式。它通过将 Redis 数据库的快照保存到磁盘上的一个二进制文件中,实现数据的持久化。RDB 方式的优点是备份速度快,文件体积小,适合用于备份和灾难恢复。缺点是在发生故障时,可能会丢失最后一次快照后的数据。
AOF 是 Redis 的另一种持久化方式,它通过将 Redis 的写操作追加到一个文件中,实现数据的持久化。AOF 方式的优点是可以保证数据的完整性,即使发生故障也可以通过重放日志文件恢复数据。缺点是备份速度较慢,文件体积较大。Redis 还能对 AOF 文件进行后台重写,使得 AOF 文件的体积不至于过大。
如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式。
你也可以同时开启两种持久化方式,在这种情况下,当 redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。
在 Redis 的配置文件中,可以通过设置 save
参数来控制 RDB 的触发条件和频率,通过设置 appendonly
参数来启用 AOF 持久化方式。
- bgsave 的原理是什么?
bgsave 是 Redis 中执行后台持久化操作的命令,它用于生成 RDB 文件。
当执行 bgsave 命令时,Redis 会 fork
创建一个子进程来将当前数据集的快照以 RDB 的格式写入到磁盘上的一个临时 RDB 文件中。这个子进程使用 Copy-on-Write 机制,即在执行持久化期间,子进程会复制 Redis 主进程的内存数据,并在复制过程中对数据进行读取,而不会对主进程的数据进行写操作。
当程序调用了 fork 方法之后,我们就可以通过 fork 的返回值确定父子进程,以此来执行不同的操作:
- fork 函数返回 0 时,意味着当前进程是子进程;
- fork 函数返回非 0 时,意味着当前进程是父进程,返回值是子进程的 pid;
int main() {
if (fork() == 0) {
// child process
} else {
// parent process
}
}
2
3
4
5
6
7
在 fork 的 手册 (opens new window) 中,我们会发现调用 fork 后的父子进程会运行在不同的内存空间中,当 fork 发生时两者的内存空间有着完全相同的内容,对内存的写入和修改、文件的映射都是独立的,两个进程不会相互影响。
除此之外,子进程几乎是父进程的完整副本(Exact duplicate),然而这两个进程在以下的一些方面会有较小的区别:
- 子进程用于独立且唯一的进程 ID;
- 子进程的父进程 ID 与父进程 ID 完全相同;
- 子进程不会继承父进程的内存锁;
- 子进程会重新设置进程资源利用率和 CPU 计时器;
- ……
最关键的点在于父子进程的内存在 fork 时是完全相同的,在 fork 之后进行写入和修改也不会相互影响,这其实就完美的解决了快照这个场景的问题 —— 只需要某个时间点下内存中的数据,而父进程可以继续对自己的内存进行修改,这既不会被阻塞,也不会影响生成的快照。
# 写时拷贝
既然父进程和子进程拥有完全相同的内存空间并且两者对内存的写入都不会相互影响,那么是否意味着子进程在 fork
时需要对父进程的内存进行全量的拷贝呢?假设子进程需要对父进程的内存进行拷贝,这对于 Redis 服务来说基本都是灾难性的,尤其是在以下的两个场景中:
- 内存中存储大量的数据,
fork
时拷贝内存空间会消耗大量的时间和资源,会导致程序一段时间的不可用; - Redis 占用了 10G 的内存,而物理机或者虚拟机的资源上限只有 16G,在这时我们就无法对 Redis 中的数据进行持久化,也就是说 Redis 对机器上内存资源的最大利用率不能超过 50%;
如果无法解决上面的两个问题,使用 fork
来生成内存镜像的方式也无法真正落地,不是一个工程中真正可以使用的方法。
就算脱离了 Redis 的场景,
fork
时全量拷贝内存也是难以接受的,假设我们需要在命令行中执行一个命令,我们需要先通过fork
创建一个新的进程再通过exec
来执行程序,fork
拷贝的大量内存空间对于子进程来说可能完全没有任何作用的,但是却引入了巨大的额外开销。
写时拷贝(Copy-on-Write)的出现就是为了解决这一问题,就像我们在这一节开头介绍的,写时拷贝的主要作用就是将拷贝推迟到写操作真正发生时,这也就避免了大量无意义的拷贝操作。在一些早期的 *nix 系统上,系统调用 fork
确实会立刻对父进程的内存空间进行复制,但是在今天的多数系统中,fork
并不会立刻触发这一过程:
在 fork
函数调用时,父进程和子进程会被 Kernel 分配到不同的虚拟内存空间中,所以在两个进程看来它们访问的是不同的内存:
- 在真正访问虚拟内存空间时,Kernel 会将虚拟内存映射到物理内存上,所以父子进程共享了物理上的内存空间;
- 当父进程或者子进程对共享的内存进行修改时,共享的内存才会以页为单位进行拷贝,父进程会保留原有的物理空间,而子进程会使用拷贝后的新物理空间;
在 Redis 服务中,子进程只会读取共享内存中的数据,它并不会执行任何写操作,只有父进程会在写入时才会触发这一机制,而对于大多数的 Redis 服务或者数据库,写请求往往都是远小于读请求的,所以使用 fork
加上写时拷贝这一机制能够带来非常好的性能,也让 BGSAVE
这一操作的实现变得非常简单。
# 总结
Redis 实现后台快照的方式非常巧妙,通过操作系统提供的 fork
和写时拷贝的特性轻而易举的就实现了这个功能,从这里我们就能看出作者对于操作系统知识的掌握还是非常扎实的,大多人在面对类似的场景时,想到的方法可能就是手动实现类似『写时拷贝』的特性,然而这不仅增加了工作量,还增加了程序出现问题的可能性。
到这里,我们简单总结一下 Redis 为什么在使用 RDB 进行快照时会通过子进程的方式进行实现:
- 通过
fork
创建的子进程能够获得和父进程完全相同的内存空间,父进程对内存的修改对于子进程是不可见的,两者不会相互影响; - 通过
fork
创建子进程时不会立刻触发大量内存的拷贝,内存在被修改时会以页为单位进行拷贝,这也就避免了大量拷贝内存而带来的性能问题;
具体的执行过程如下:
- 主进程执行 bgsave 命令后,会创建一个子进程。
- 子进程会复制主进程的内存数据,并在复制过程中对数据进行读取。
- 子进程将复制的数据写入到一个临时 RDB 文件中。
- 当子进程完成 RDB 文件的写入后,会用该文件替换原有的 RDB 文件,完成持久化操作。
- 主进程继续处理其他命令请求,而不会被持久化过程所阻塞。
通过使用子进程进行持久化操作,可以避免主进程在持久化过程中被阻塞,从而保持 Redis 的高性能和低延迟。此外,子进程和主进程之间共享文件描述符,因此不需要进行数据复制,也减少了内存的使用。
需要注意的是,bgsave 命令是一个异步操作,它不会阻塞主进程的执行。因此,在执行 bgsave 命令后,可以继续执行其他命令,而不需要等待持久化操作完成。
为什么 Redis 快照使用子进程 - 面向信仰编程 (opens new window)
# 16、如何选择合适的持久化方式
一般来说,如果想达到足以媲美 PostgreSQL 的数据安全性,你应该同时使用两种持久化功能。如果你非常关心你的数据,但仍然可以承受数分钟以内的数据丢失,那么你可以只使用 RDB 持久化。
有很多用户都只使用 AOF 持久化,但并不推荐这种方式:因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份,并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快,除此之外,使用 RDB 还可以避免之前提到的 AOF 程序的 bug。
# 17、Pipeline 有什么好处,为什么要用 pipeline
Redis 中的 Pipeline 是一种批量执行多个 Redis 命令的机制,它可以将多个命令一次性发送给 Redis 服务器执行,而不需要等待每个命令的响应结果。使用 Pipeline 可以提高 Redis 的性能和效率。
以下是使用 Redis Pipeline 的好处:
- 减少网络延迟:由于 Pipeline 可以将多个命令一次性发送给 Redis 服务器执行,减少了每个命令的网络通信时间,从而减少了整体的网络延迟。
- 减少服务器负载:使用 Pipeline 可以将多个命令打包发送给 Redis 服务器,减少了服务器的负载。相比于逐个发送命令,使用 Pipeline 可以减少服务器的 CPU 和内存消耗。
原子性操作:Pipeline 中的命令是原子性执行的,要么全部执行成功,要么全部执行失败。这对于需要保持数据一致性的操作非常重要。- 提高吞吐量:由于 Pipeline 可以批量执行多个命令,可以在单位时间内处理更多的命令请求,从而提高 Redis 的吞吐量。
总结来说,Pipeline 可以通过减少网络延迟、减少服务器负载、提高原子性操作和提高吞吐量等方面来提高 Redis 的性能和效率。因此,在需要批量执行多个 Redis 命令的场景下,使用 Pipeline 是非常有益的。
# 18、Redis 的同步机制了解么
Redis 可以使用主从同步,从从同步。第一次同步时,主节点做一次 bgsave,并同时将后续修改操作记录到内存 buffer,待完成后将 rdb 文件全量同步到复制节点,复制节点接受完成后将 rdb 镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。
Redis 集群是一种分布式解决方案,用于在多个 Redis 节点之间分布和复制数据,以提高系统的性能和可用性。Redis 集群采用分片(sharding)的方式将数据分布到多个节点上,并使用内置的数据复制机制来保持节点之间的数据一致性。
Redis 集群方案的实现有以下几个步骤:
分片策略:确定如何将数据分布到多个节点上。Redis 集群使用哈希槽(hash slot)的方式进行数据分片,将所有可能的键值对分配到 16384 个哈希槽中。
节点配置:配置多个 Redis 节点,每个节点都运行在不同的端口上,并在配置文件中指定节点的角色(主节点或从节点)和集群信息。
节点启动:启动所有的 Redis 节点,并通过配置文件中的集群信息将它们连接起来。节点会自动进行握手和数据同步,以建立集群的拓扑结构。
节点握手:当一个节点加入到集群中时,它会发送一个握手请求给其他节点,以建立与其他节点的通信。其他节点会验证握手请求,并将新节点添加到集群中。
数据迁移:当一个节点加入或离开集群时,会触发数据迁移过程。数据迁移将哈希槽从一个节点移动到另一个节点,以保持数据的分片一致性。
故障转移:当一个主节点失效时,集群会自动进行故障转移,将一个从节点升级为新的主节点。故障转移过程中会进行投票和选举,以保证新的主节点的可用性和数据一致性。
客户端访问:客户端可以通过连接任意一个节点来访问整个集群。当客户端发送命令时,节点会根据命令的键值对确定数据所在的哈希槽,并将命令转发到负责该哈希槽的节点上。
通过以上步骤,Redis 集群可以实现数据的分布和复制,提高系统的性能和可用性。同时,Redis 集群还提供了自动分片和故障转移的机制,使得集群的管理和维护变得更加简单和可靠。
# 19、Redis 集群方案与实现
Redis 的同步机制有两种:主从复制和哨兵模式。
主从复制:Redis 的主从复制机制通过将一个 Redis 实例的数据复制到其他实例来实现数据同步。其中一个实例为主节点(Master),负责写操作和处理客户端请求;其他实例为从节点(Slave),负责复制主节点的数据,并提供读操作。主节点将写操作记录在内存中的数据变化,然后将这些变化发送给从节点进行复制。从节点将主节点的数据复制到自己的内存中,保持与主节点的数据一致性。如果主节点失效,可以将一个从节点升级为新的主节点,保证系统的高可用性。
哨兵模式:Redis 的哨兵模式通过监控 Redis 实例的状态来实现高可用性。哨兵可以监控多个 Redis 实例,并在主节点失效时自动将一个从节点升级为新的主节点。哨兵通过心跳机制监控主节点的状态,如果主节点失效,则会选举一个新的主节点,并通知其他从节点切换到新的主节点。哨兵还可以监控从节点的状态,如果从节点失效,则会从其他从节点中选举一个新的从节点来替代。
这两种同步机制都可以实现 Redis 的数据同步和高可用性,但是主从复制适用于多个从节点的场景,可以提供更好的读性能;而哨兵模式适用于更高的可用性要求,可以自动切换主节点。
Redis Sentinal 着眼于高可用,在 master 宕机时会自动将 slave 提升为 master,继续提供服务。
Redis Cluster 着眼于扩展性,在单个 redis 内存不足时,使用 Cluster 进行分片存储。
# 20、一个 Redis 实例最多能存放多少的 keys?List、Set、Sorted Set 他们最多能存放多少元素
一个 Redis 实例最多可以存放约 2^32 - 1 个 keys。
List、Set、Sorted Set 分别可以存放的最大元素数量如下:
- List:最多可以存放约 2^32 - 1 个元素。
- Set:最多可以存放约 2^32 - 1 个元素。
- Sorted Set:最多可以存放约 2^32 - 1 个元素。
换句话说,Redis 的存储极限是系统中的可用内存值。
# 21、Redis 持久化数据和缓存怎么做扩容
如果 Redis 被当做缓存使用,使用一致性哈希实现动态扩容缩容。
如果 Redis 被当做一个持久化存储使用,必须使用固定的 keys-to-nodes 映射关系,节点的数量一旦确定不能变化。否则的话(即 Redis 节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有 Redis 集群可以做到这样。
在 Redis 中,可以通过以下方式对持久化数据和缓存进行扩容:
持久化数据的扩容:
- 使用 Redis 的 RDB 持久化方式:在 RDB 持久化方式下,可以通过增加更多的 Redis 实例来扩容数据。可以使用主从复制或者集群模式来实现数据的复制和分片,从而实现数据的扩容。当需要扩容时,可以添加新的 Redis 实例并将数据进行分配和复制。
- 使用 Redis 的 AOF 持久化方式:在 AOF 持久化方式下,可以通过增加更多的 Redis 实例来扩容数据。同样可以使用主从复制或者集群模式来实现数据的复制和分片,从而实现数据的扩容。当需要扩容时,可以添加新的 Redis 实例并将数据进行分配和复制。
缓存的扩容:
- 使用 Redis 的主从复制:可以通过增加更多的 Redis 实例作为从节点来扩容缓存。将新的 Redis 实例配置为从节点,并将数据进行复制。这样可以增加缓存的读取能力和容量。
- 使用 Redis 的集群模式:可以通过将 Redis 实例组成集群来扩容缓存。将数据进行分片和复制,从而实现缓存的扩容。集群模式可以提供更高的读写性能和容量。
无论是对持久化数据还是缓存的扩容,需要注意以下几点:
- 在扩容过程中,需要确保数据的一致性。可以使用 Redis 的复制机制来实现数据的同步和复制。
- 在扩容过程中,需要考虑数据的迁移和重新分配。可以使用工具来帮助实现数据的迁移和分配。
- 在扩容过程中,需要考虑系统的可用性。可以使用负载均衡器来实现请求的分发和高可用性。
# redis 支持事务吗
是的,Redis 支持事务。Redis 的事务是通过 MULTI、EXEC、WATCH 和 UNWATCH、DISCARD 等命令实现的。
- MULTI 命令用于开启一个事务
- WATCH 和 UNWATCH 命令用于对事务进行监视和取消监视。参阅Redis 之 watch key 和 unwatch (opens new window)。在事务中,所有的命令都会被放入一个队列中,直到执行 EXEC 命令时才会一起执行。如果在执行 EXEC 命令之前,有其他客户端对被监视的键进行了修改,那么事务将会被取消,不会执行任何命令。
- DISCARD:取消事务并清空事务队列。当执行 DISCARD 命令后,Redis 会取消当前客户端的事务,并清空事务队列中的所有命令。这意味着事务中的所有命令都不会被执行。DISCARD 命令可以用于放弃之前的事务,重新开始一个新的事务。
- EXEC 命令用于执行事务中的命令
是否满足 ACID
Redis 具备了一定的原子性,但不支持回滚。 Redis 具备 ACID 中一致性的概念。 Redis 具备隔离性。 Redis 无法保证持久性。
Redis 事务支持 ACID 么? - 知乎 (opens new window)
# Redis 如何做内存优化
尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。
比如你的 web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key,而是应该把这个用户的所有信息存储到一张散列表里面。
# Redis 回收进程如何工作的
在 Redis 中,每个键值对都是一个对象,包含了键、值以及一些额外的元数据。这些对象是通过 Redis 的内存分配器进行分配和管理的。Redis 的内存分配器会根据需要动态分配和释放内存,以适应数据的增长和减少。
Redis 通过使用一些其他的技术来管理内存,例如内存池和写时复制(Copy-on-Write)。内存池是一种预分配内存的机制,可以减少内存分配和释放的开销。写时复制是一种在对象被修改时,先复制一个新的对象,然后再进行修改的技术,可以避免修改原始对象时的并发冲突。
另外,Redis 还通过设置最大内存限制来控制内存的使用情况。当内存使用超过限制时,Redis 会根据配置的策略进行内存回收,例如删除一些键值对或者使用 LRU(最近最少使用)算法进行淘汰。
# 大 key 与热 key 问题
热点 key
热点 key 导致单机 redis 压力陡增,通过 key hash 分散热点或者使用本地缓存的方式(多级缓存),减小 redis 压力
可以将对应热 Key 进行复制并迁移至其他数据分片,例如将热 Key foo 复制出 3 个内容完全一样的 Key 并名为 foo2、foo3、foo4,将这三个 Key 迁移到其他数据分片来解决单个数据分片的热 Key 压力。 :::说明 该方案的缺点在于需要联动修改代码,同时带来了数据一致性的挑战(由原来更新一个 Key 演变为需要更新多个 Key),仅建议该方案用来解决临时棘手的问题。 :::
大 key
对大 Key 进行拆分 例如将含有数万成员的一个 HASH Key 拆分为多个 HASH Key,并确保每个 Key 的成员数量在合理范围。在 Redis 集群架构中,拆分大 Key 能对数据分片间的内存平衡起到显著作用。
对大 Key 进行清理 将不适用 Redis 能力的数据存至其它存储,并在 Redis 中删除此类数据。 :::说明 Redis 4.0 及之后版本:您可以通过 UNLINK 命令安全地删除大 Key 甚至特大 Key,该命令能够以非阻塞的方式,逐步地清理传入的 Key。 Redis 4.0 之前的版本:建议先通过 SCAN 命令读取部分数据,然后进行删除,避免一次性删除大量 key 导致 Redis 阻塞。 :::
监控 Redis 的内存水位 您可以通过监控系统设置合理的 Redis 内存报警阈值进行提醒,例如 Redis 内存使用率超过 70%、Redis 的内存在 1 小时内增长率超过 20%等。通过此类监控手段,可以提前规避许多问题,例如 LIST 数据类型的消费程序故障造成对应 Key 的列表数量持续增长,将告警转变为预警从而避免故障的发生,更多信息,请参见报警设置。
对过期数据进行定期清理 堆积大量过期数据会造成大 Key 的产生,例如在 HASH 数据类型中以增量的形式不断写入大量数据而忽略了数据的时效性。可以通过定时任务的方式对失效数据进行清理。 :::说明 在清理 HASH 数据时,建议通过 HSCAN 命令配合 HDEL 命令对失效数据进行清理,避免清理大量数据造成 Redis 阻塞。 :::
如何找出优化大 Key 与热 Key,产生的原因和问题_云数据库 Redis-阿里云帮助中心 (opens new window)