我们平时看到介绍 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 线程、清理回收线程。当然不能忘记的是,想要体验多线程机制,就得通过修改配置文件开启多线程功能。

推荐阅读

原创内容屡屡被盗?从源头对资源盗用说NO

严重危害警告!Log4j 执行漏洞被公开!

面试官问,Redis 是单线程还是多线程?我懵了的更多相关文章

  1. 面试官问我,Redis分布式锁如何续期?懵了。

    前言 上一篇[面试官问我,使用Dubbo有没有遇到一些坑?我笑了.]之后,又有一位粉丝和我说在面试过程中被虐了.鉴于这位粉丝是之前肥朝的粉丝,而且周一又要开启新一轮的面试,为了回馈他长期以来的支持,所 ...

  2. 面试之二:Redis是单线程还是多线程?以及处理模型。

      Redis是单线程还是多线程?以及处理模型. 线程:单线程 处理模型:参考书<Redis 设计与实现>P151-152 ![](https://ws1.sinaimg.cn/large ...

  3. 面试官问我Redis集群,我真的是

    面试官:聊下Redis的分片集群,先聊 Redis Cluster好咯? 面试官:Redis Cluser是Redis 3.x才有的官方集群方案,这块你了解多少? 候选者:嗯,要不还是从基础讲起呗? ...

  4. 面试官问线程安全的List,看完再也不怕了!

    最近在Java技术栈知识星球里面有球友问到了线程安全的 List: 扫码查看答案或加入知识星球 栈长在之前的文章<出场率比较高的一道多线程安全面试题>里面讲过 ArrayList 的不安全 ...

  5. 面试官问:HashMap在并发情况下为什么造成死循环?一脸懵

    这个问题是在面试时常问的几个问题,一般在问这个问题之前会问Hashmap和HashTable的区别?面试者一般会回答:hashtable是线程安全的,hashmap是线程不安全的. 那么面试官就会紧接 ...

  6. [每日一题]面试官问:Async/Await 如何通过同步的方式实现异步?

    关注「松宝写代码」,精选好文,每日一题 ​时间永远是自己的 每分每秒也都是为自己的将来铺垫和增值 作者:saucxs | songEagle 一.前言 2020.12.23 日刚立的 flag,每日一 ...

  7. 面试官:Redis中字符串的内部实现方式是什么?

    在面试间里等候时,感觉这可真暖和呀,我那冰冷的出租屋还得盖两层被子才能睡着.正要把外套脱下来,我突然听到了门外的脚步声,随即门被打开,穿着干净满脸清秀的青年走了进来,一股男士香水的淡香扑面而来. 面试 ...

  8. 面试官:Redis中集合数据类型的内部实现方式是什么?

    虽然已经是阳春三月,但骑着共享单车骑了这么远,还有有点冷的.我搓了搓的被冻的麻木的手,对着前台的小姐姐说:"您好,我是来面试的."小姐姐问:"您好,您叫什么名字?&quo ...

  9. 面试官:Redis如何实现持久化的、主从哨兵又是什么?

    哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 作为一名Java程序员,Redi ...

随机推荐

  1. Linux 编译安装、压缩打包、定时任务

    目录 Linux 编译安装 知识储备: wget命令 编译安装 Linux 压缩打包 gzip压缩 bzip2压缩 tar打包 Linux 定时任务 相关文件及操作 Linux 编译安装 编译安装就是 ...

  2. 资源分配(Project)

    <Project2016 企业项目管理实践>张会斌 董方好 编著 资源设置好以后,不能光摆着看,分配到各任务中去才是正道. 分配资源就需要回到与任务相关的视图了,比如[任务工作表]视图或者 ...

  3. 十年后回到百年前?(Excel技巧集团)

    在单元格里输入日期,有时可以偷懒,比如明年的日期可以输入至少一位的年+横杠(或斜杠)+至少一位的月+横杠(或斜杠)+至少一位的日,也就是"21-1-1",单元格里就会自动显示&qu ...

  4. 【嵌入式AI】全志 XR806 OpenHarmony 鸿蒙系统固件烧录

      欢迎关注我的公众号 [极智视界],回复001获取Google编程规范   O_o   >_<   o_O   O_o   ~_~   o_O   大家好,我是极智视界,本教程详细记录了 ...

  5. java 编程基础:注解(Annotation Processing Tool)注解处理器 利用注解解读类属性生成XML文件

    APT的介绍: APT(Annotation Processing Tool)是一种注解处理工具,它对源代码文件进行检测,并找出源文件所包含的注解信息,然后针对注解信息进行额外的处理. 使用APT工具 ...

  6. Java 中的5个代码性能提升技巧,最高提升近10倍

    文章持续更新,可以关注公众号程序猿阿朗或访问未读代码博客. 本文 Github.com/niumoo/JavaNotes 已经收录,欢迎Star. 这篇文章介绍几个 Java 开发中可以进行性能优化的 ...

  7. Linux宝塔面板部署运行jar包

    登录面板 安装插件 把jar包上传上去 设置jar包 填写项目启动的端口 然后点击确定 会自动启动 然后浏览器打开 ip:端口 即可

  8. 【LeetCode】376. Wiggle Subsequence 解题报告(Python)

    [LeetCode]376. Wiggle Subsequence 解题报告(Python) 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.c ...

  9. 【LeetCode】889. Construct Binary Tree from Preorder and Postorder Traversal 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...

  10. Redis -使用 Bitmap

    redis数据类型 String.Set.Zset.List.hash       Bitmap . 四种统计类型: 二值状态统计: 聚合统计: 排序统计: 基数统计 二值状态统计: 就是集合中的元素 ...