我们经常听到说Redis是单线程的,也会有疑问:为什么单线程的Redis能那么快?

这里要明白一点:Redis是单线程,主要是指Redis的网络IO和键值对读写是由一个线程来完成的,这也是Redis对外提供键值存储服务的主要流程。但Redis的其他功能,比如持久化、异步删除、集群数据同步等,都是由额外的线程执行的。

我们知道多线程能够提升并发性能,那为什么Redis会采用单线程,而非多线程?为什么单线程能那么快?

下面我们就来学习一下Redis采用单线程的原因。

为什么采用单线程?

使用多线程,虽然可以增加系统吞吐率,或是增加系统扩展性,但同样会产生开销。

Redis的数据是在内存里的,是共享的,如果使用多线程就会引发共享资源的竞争,需要引入互斥锁来解决,使得并行变串行。最终系统吞吐率并没有随着线程的增加而增加。

另外,多线程开发需要精细的设计,会增加系统的复杂度,降低代码的易调试性和可维护性。为了避免这些问题,Redis采用单线程模式。

单线程Redis为什么那么快?

通常来说,单线程的处理能力比多线程要差很多,那Redis却能使用单线程模型达到每秒数十万级别的处理能力,这是为什么呢?

一方面,Redis大多数操作是在内存上完成的,并且采用高效的数据结构,例如哈希表和跳表。另一方面,Redis采用了多路复用机制,使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐率。

在学习多路复用机制前,我们要弄明白网络操作的基于IO模型和潜在的阻塞点。

基本IO模型与阻塞点

以Get请求为例,为了处理一个Get请求:

  1. 需要监听客户端请求(bind/listen)
  2. 和客户端建立连接(accept)
  3. 从socket中读取请求(recv)
  4. 解析客户端发送请求(parse)
  5. 根据请求类型读取键值数据(get)
  6. 最后给客户端返回结果,即向socket中写回数据(send)。

下图显示了这一过程,其中,bind/listen、accept、recv、parse和send属于网络IO处理,而get属性键值数据操作。

但是在这里的网络IO操作中,有潜在的阻塞点,分别是accept()和recv()。

  • 当Redis监听到一个客户端有连接请求,但一直未能成功建立起连接时,会阻塞在accept()
  • 当Redis通过recv()从一个客户端读取数据时,如果数据一直没有到达,Redis也会一直阻塞在recv()

这就导致Redis整个线程阻塞,无法处理其他客户端请求,效率很低。不过,幸运的是,socket网络模型本身支持非阻塞模式。

非阻塞模式

Socket网络模型可以设置非阻塞模式。

这样能保证Redis线程既不会像基本IO模型中一直在阻塞点等待,也不会导致Redis无法处理实际到达的连接请求或数据。

下面就到多路复用机制登场了。

基于多路复用的高性能I/O模型

Linux的IO多路复用机制是指一个线程处理多个IO流,也就是select/epoll机制。

在Redis运行单线程下,该机制允许内核中,同时存在多个监听套接字和已连接套接字。

为了在请求到达时能通知到Redis线程,select/epoll提供了基于事件的回调机制,即针对不同事件的发生,调用相应的处理函数

回调机制的工作流程:

  1. select/epoll一旦临听到FD上有请求到达,就会触发相应的事件,并放进一个事件队列中。
  2. Redis单线程对事件队列进行处理即可,无需一直轮询是否有请求发生,避免CPU资源浪费。

因为Redis一直在对事件队列进行处理,所以能及时响应客户端请求,提升Redis的响应性能。

不过,需要注意的是,在不同的操作系统上,多路复用机制也是适用的。

拓展

在“Redis基本IO模型”图中,有哪些潜在的性能瓶颈?

Redis单线程处理IO请求性能瓶颈主要包括2个方面:

1、任意一个请求在server中一旦发生耗时,都会影响整个server的性能 也就是说后面的请求都要等前面这个耗时请求处理完成,自己才能被处理到。

耗时的操作包括:

  • 操作bigkey:写入一个bigkey在分配内存时需要消耗更多的时间,同样,删除bigkey释放内存同样会产生耗时
  • 使用复杂度过高的命令:例如SORT/SUNION/ZUNIONSTORE,或者O(N)命令,但是N很大,例如lrange key 0 -1一次查询全量数据
  • 大量key集中过期:Redis的过期机制也是在主线程中执行的,大量key集中过期会导致处理一个请求时,耗时都在删除过期key,耗时变长
  • 淘汰策略:溜达策略也是在主线程执行的,当内存超过Redis内存上限后,每次写入都需要淘汰一些key,也会 造成耗时变长。
  • AOF刷盘开启always机制:每次写入都需要把这个操作刷到磁盘,写磁盘的速度远比写内存慢,会拖慢Redis的性能
  • 主从全量同步生成RDB:虽然采用fork子进程生成数据快照,但fork这一瞬间也是会阻塞整个线程的,实例越大,阻塞时间越久

解决办法:

  • 需要业务人员去规避
  • Redis在4.0推出了lazy-free机制,把bigkey释放内存的耗时操作放在了异步线程中执行,降低对主线程的影响

2、并发量非常大时,单线程读写客户端IO数据存在性能瓶颈,虽然采用IO多路复用机制,但是读写客户端数据依旧是同步IO,只能单线程依次读取客户端的数据,无法利用到CPU多核。

解决办法:

  • Redis在6.0推出了多线程,可以在高并发场景下利用CPU多核多线程读写客户端数据,进一步提升server性能
  • 当然,只针对客户端的读写是并行的,每个命令的真正操作依旧是单线程的

参考资料

Redis基础篇(二)高性能IO模型的更多相关文章

  1. 高性能IO模型浅析

    高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking  ...

  2. 高性能IO模型浅析(彩图解释)good

    服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking IO):默认创建的s ...

  3. 高性能IO模型浅析(转)

    转自:http://www.cnblogs.com/fanzhidongyzby/p/4098546.html 是我目前看到的解释IO模型最清晰的文章,当然啦,如果想要详细的进一步了解还是继续啃蓝宝书 ...

  4. 【珍藏】高性能IO模型浅析

    服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking IO):默认创建的s ...

  5. [转载] 高性能IO模型浅析

    转载自http://www.cnblogs.com/fanzhidongyzby/p/4098546.html 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(B ...

  6. 转 高性能IO模型浅析

    高性能IO模型浅析 转自:http://www.cnblogs.com/fanzhidongyzby/p/4098546.html 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: ( ...

  7. 【转载】高性能IO模型浅析

    服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking IO):默认创建的s ...

  8. php基础篇-二维数组排序 array_multisort

    原文:php基础篇-二维数组排序 array_multisort 对2维数组或者多维数组排序是常见的问题,在php中我们有个专门的多维数组排序函数,下面简单介绍下: array_multisort(a ...

  9. docker+k8s基础篇二

    Docker+K8s基础篇(二) docker的资源控制 A:docker的资源限制 Kubernetes的基础篇 A:DevOps的介绍 B:Kubernetes的架构概述 C:Kubernetes ...

  10. NIO相关基础篇二

    转载请注明原创出处,谢谢! 上篇NIO相关基础篇一,主要介绍了一些基本的概念以及缓冲区(Buffer)和通道(Channel),本篇继续NIO相关话题内容,主要就是文件锁.以及比较关键的Selecto ...

随机推荐

  1. New Features In SNMPv3 - SNMP Tutorial

    30.12 New Features In SNMPv3 We said that version 3 of SNMP represents an evolution that follows and ...

  2. Google最新截屏案例详解

    Google从Android 5.0 开始,给出了截屏案例ScreenCapture,在同版本的examples的Media类别中可以找到.给需要开发手机或平板截屏应用的小伙伴提供了非常有意义的参考资 ...

  3. 【HDOJ】3696 Farm Game

    SPFA求最短路径.见图的时候注意逆向建图. /* 3696 */ #include <iostream> #include <queue> #include <vect ...

  4. 如何:控制命名空间前缀 (C#) (LINQ to XML)

    Visual Studio 2010 本主题介绍在序列化 XML 树时如何控制命名空间前缀. 在很多情况下,不需要控制命名空间前缀. 但是,某些 XML 编程工具需要命名空间前缀的特定控制. 例如,您 ...

  5. 使用IntelliJ IDEA的小技巧快乐编程(1)

    前言 我很喜欢和别人讨论一些问题,有时候,在公司里,讨论这样的问题需要演示代码.常常会碰到的一种情况是(根据我的记忆这半年多来至少超过了10次),别人会打断你的演示,抛出一个问题:等等,你刚才的操作是 ...

  6. jquery的$(selector).each(function(index,element))和$.each(dataresource,function(index,element))的区别

    $(selector).each(function(index,element)) 定义和用法 each() 方法规定为每个匹配元素规定运行的函数. $(selector).each(function ...

  7. 面向对象【day07】:类的属性(五)

    本节内容 概述 公有属性 一.概述 前面我们讲了类的私有属性,现在我们来说说类的公有属性,这边很容易被人弄混淆,有人觉的,在__init__()构造方法中,除了私有属性,其他的都是公有属性了,其实这是 ...

  8. git使用遇到的坑

    把一个完整项目提交到github上步骤以及注意事项 Git的安装就不说了. 第一步:mkdir/cd 我们需要先创建一个本地的版本库(其实也就是一个文件夹). 你可以直接右击新建文件夹,也可以右击打开 ...

  9. Mac OSX系统下通过ProxyChains-NG实现终端下的代理

    项目主页:https://github.com/rofl0r/proxychains-ng 官方说明: proxychains ng (new generation) - a preloader wh ...

  10. FPM包定制完成 (等待实现 里程碑 1 和 2) 2018年4月13日 2:18:32

    前期环境准备: 关闭SELINUX  :   setenforce 0 关闭SELINUX  :   sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' / ...