面试官问,Redis 是单线程还是多线程?我懵了

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 面试官问,Redis 是单线程还是多线程?我懵了我们平时看到介绍 Redis 的文章,都会说 Redis 是单线程的。但是我们学习的时候,比如 Redis 的 bgsave 命令,它的作用是在后台异步保存当前数据库的数据到磁盘,那既然是异步了,肯定是由别的线程去完成的,这怎么还能说 Redis 是单线程的呢?其实通常说的 Redis 是单线程,主要是指 Redis 对外提供键值存储服务的主要流程,即网络 IO 和键值对读写是由⼀个线程来完成的。除此外 Redis 的其他功能,比如持久化、 异步删除、集群数据同步等,是由额外的线程执⾏的。

面试官问,Redis 是单线程还是多线程?我懵了

Redisyouhuiquan.jpg

我们平时看到介绍 Redis 的文章,都会说 Redis 是单线程的。但是我们学习的时候,比如 Redis 的 bgsave 命令,它的作用是在后台异步保存当前数据库的数据到磁盘,那既然是异步了,肯定是由别的线程去完成的,这怎么还能说 Redis 是单线程的呢?

其实通常说的 Redis 是单线程,主要是指 Redis 对外提供键值存储服务的主要流程,即网络 IO 和键值对读写是由⼀个线程来完成的。除此外 Redis 的其他功能,比如持久化、 异步删除、集群数据同步等,是由额外的线程执⾏的。在这一点上 Node 也是一样的,一般提到 Node 也是单线程的,但其实 Node 只有一个主线程是单线程,其他异步任务则由其他线程完成。这样做的原因是防止有同步代码阻塞,导致主线程被占用后影响后续的程序代码执行。

因此,严格地说 Redis 并不是单线程。但是我们⼀般把 Redis 称为单线程高性能,这样显得 Redis 更强一些。

Redis 为什么用单线程

Redis 为什么用单线程?在回答这个问题前,先来看大家都很熟悉的数据库 MySQL,它使用的就是多线程。MySQL 不会每有一个连接就创建一个线程,因为线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等,同时也会降低计算机的整体性能。这个正是多线程会遇到的难点。

此外多线程系统中通常会存在被多线程同时访问的共享资源,比如一个共享的数据结构,当有多个进程要修改这个共享资源时,为了保证共享资源的正确性,就需要有额外的机制进行保证,而这个额外的机制,也会带来额外的开销。还是以 MySQL 举例,MySQL 引入了锁机制来解决这个问题。

从上面不难看出,多线程开发中并发访问控制是⼀个难点,需要精细的设计才能处理。如果只是简单地处理,比如简单地采⽤⼀个粗粒度互斥锁,只会出现不理想的结果。即便增加了线程,系统吞吐率也不会随着线程的增加而增加,因为大部分线程还在等待获取访问共享资源的互斥锁。而且,大部分采用多线程开发引入的同步原语保护共享资源的并发访问,也会降低系统代码的易调试性和可维护性。

而正是以上这些问题,才让 Redis 采⽤了单线程模式。

看到这里大家可能有点疑惑,前面说了 Redis 不是单线程,现在我们也说了 Redis 的键值对读写操作使用采用了单线程模式,那么它的其他线程是是什么样的呢?

主进程的其它线程

Redis 3.0 版本后,主进程中除了主线程处理网络 IO 和命令操作外,还有 3 个辅助 BIO 线程。这 3 个 BIO 线程分别负责处理,文件关闭、AOF 缓冲数据刷新到磁盘,以及清理对象这三个任务队列,从而避免这些任务对主 IO 线程的影响。

Redis 在启动时,会同时启动这三个 BIO 线程,但是 BIO 线程只有在需要执行相关类型后台任务时才会唤醒,其他时间会休眠等待任务。

多进程

除了主进程,在以下场景如果需要进行重负荷任务的处理,Redis 会 fork 一个子进程来处理:

  • 收到 bgrewriteaof 命令: Redis fork 一个子进程,然后子进程往临时 AOF文件中写入重建数据库状态的所有命令。写入完毕后,子进程会通知父进程把新增的写操作追加到临时 AOF 文件。最后将临时文件替换旧的 AOF 文件,并重命名。
  • 收到 bgsave 命令: Redis 构建子进程,子进程将内存中的所有数据通过快照做一次持久化落地,写入到 RDB 中。
  • 当需要进行全量复制: master 启动一个子进程,子进程将数据库快照保存到 RDB 文件。在写完 RDB 快照文件后,master 会把 RDB 发给 slave,同时将后续新的写指令都同步给 slave。

Redis6.0 多线程

多线程是 Redis6.0 推出的一个新特性。正如上面所说 Redis 是核心线程负责网络 IO ,命令处理以及写数据到缓冲,而随着网络硬件的性能提升,单个主线程处理⽹络请求的速度跟不上底层⽹络硬件的速度,导致网络 IO 的处理成为了 Redis 的性能瓶颈。

而 Redis6.0 就是从单线程处理网络请求到多线程处理,通过多个 IO 线程并⾏处理网络操作提升实例的整体处理性能。需要注意的是对于读写命令,Redis 仍然使⽤单线程来处理,这是因为继续使⽤单线程执行命令操作,就不⽤为了保证 Lua 脚本、事务的原⼦性,额外开发多线程互斥机制了。

需要注意的是在 Redis6.0 中,多线程机制默认是关闭的,需要在 redis.conf 中完成以下两个设置才能启用多线程。

  • 设置 io-thread-do-reads 配置项为 yes,表示启用多线程。
io-threads-do-reads yes
  • 设置线程个数。⼀般来说,线程个数要小于 Redis 实例所在机器的 CPU 核数, 例如,对于⼀个 8 核的机器来说,Redis 官⽅建议配置 6 个 IO 线程。
io-threads 6

多线程流程

来具体看一下在 Redis6.0 中,主线程和 IO 线程是如何协作完成请求处理的。

全部流程分为以下 4 阶段:

阶段一:服务端和客⼾端建立 Socket 连接,并分配处理线程

当有客⼾端请求和实例建立 Socket 连接时,主线程会创建和客户端的连接,并把 Socket 放入全局等待队列中。然后主线程通过轮询方法把 Socket 连接分配给 IO 线程。

阶段二:IO 线程读取并解析请求

主线程把 Socket 分配给 IO 线程后,会进⼊阻塞状态等待 IO 线程完成客户端请求读取和解析。

阶段三:主线程执⾏请求操作

IO 线程解析完请求后,主线程以单线程的⽅式执⾏这些命令操作。

阶段四:IO 线程回写 Socket 和主线程清空全局队

主线程执行完请求操作后,会把需要返回的结果写入缓冲区。然后,主线程会阻塞等待 IO 线程把这些结果回写到 Socket 中,并返回给客户端。等到 IO 线程回写 Socket 完毕,主线程会清空全局队列,等待客户端的后续请求。

总结

看完了这篇文章,相信大家对 Redis 是单线程的说法已经有了大致概念。我们说它是单线程,主要是因为在以前的版本中网络 IO 和键值对读写是由⼀个线程来完成的。而之所以说 Redis 是多线程,则是因为 Redis6.0 以后的版本里,网络 IO 的部分变为了多线程处理。而且除了主线程,还有 3 个辅助 BIO 线程,分别是 fsync 线程、close 线程、清理回收线程。当然不能忘记的是,想要体验多线程机制,就得通过修改配置文件开启多线程功能。

转载地址https://www.cnblogs.com/upyun/p/15796634.html

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
28天前
|
监控 NoSQL 安全
如何在 Redis 中正确使用多线程?
【10月更文挑战第16天】正确使用 Redis 多线程需要综合考虑多个因素,并且需要在实践中不断摸索和总结经验。通过合理的配置和运用,多线程可以为 Redis 带来性能上的提升,同时也要注意避免可能出现的问题,以保障系统的稳定和可靠运行。
37 2
|
28天前
|
存储 NoSQL Redis
Redis 新版本引入多线程的利弊分析
【10月更文挑战第16天】Redis 新版本引入多线程是一个具有挑战性和机遇的改变。虽然多线程带来了一些潜在的问题和挑战,但也为 Redis 提供了进一步提升性能和扩展能力的可能性。在实际应用中,我们需要根据具体的需求和场景,综合评估多线程的利弊,谨慎地选择和使用 Redis 的新版本。同时,Redis 开发者也需要不断努力,优化和完善多线程机制,以提供更加稳定、高效和可靠的 Redis 服务。
33 1
|
8天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
25天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
19 3
|
25天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
16 2
|
25天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
28 2
|
25天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
28 1
|
25天前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
34 1
|
25天前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
25 1
|
1月前
|
NoSQL Java API
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试一线互联网企业时遇到了关于Redis分布式锁过期及自动续期的问题。尼恩对此进行了系统化的梳理,介绍了两种核心解决方案:一是通过增加版本号实现乐观锁,二是利用watch dog自动续期机制。后者通过后台线程定期检查锁的状态并在必要时延长锁的过期时间,确保锁不会因超时而意外释放。尼恩还分享了详细的代码实现和原理分析,帮助读者深入理解并掌握这些技术点,以便在面试中自信应对相关问题。更多技术细节和面试准备资料可在尼恩的技术文章和《尼恩Java面试宝典》中获取。
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?