Redis(一):单线程为何还能这么快?

22 篇文章 0 订阅
5 篇文章 0 订阅

         提到redis马上在我们脑海中会浮现出这样一些关键字:单线程、高性能、内存数据库、kv存储......这些关键字都从不同层面描述了redis的一些相关特性和技术实现。那么为什么redis具备这些特性以及是如何实现的,本文将进行一一分析。

一、单线程

1.1 为什么是单线程

  1. 总结Redis的普通KV存储瓶颈不在 CPU,而往往可能受到内存和网络I/O 的制约。

  2. Redis 中有多种类型的数据操作,甚至包括一些事务处理,如果采用多线程,则会被多线程产生的切换问题而困扰,也可能因为加锁导致系统架构变的异常复杂造成性能损耗。

Redis作者咋说的:

        总结来说就是对于redis来说单线程的设计能够保证性能,多线程在设计和实现上会带来更多的复杂度。但是使用单线程的方式确实无法很好发挥多核CPU 的性能,可以通过在单机开多个Redis 实例来完善!

1.2 有多线程的考量吗

        Redis4.0版本对于一些大键值对的删除操作,引入多线程来非阻塞地释放内存空间,能减少对 Redis 主线程阻塞的时间,提高执行的效率。

        Redis6.0 引入多线程来提高网络 IO 读写性能。

        这里要注意的是Redis 的多线程只是在网络数据的读写这类耗时操作上使用了, 执行命令仍然是单线程顺序执行。Redis6中默认是禁用多线程的,可以通过修改redis的配置文件中io-threads-do-reads=true来开启。除此之外还需要设置现场的数量才能正真开启多线程,配置参数为io-threads 3表示开启三个线程。

        线程设置建议:关于线程数的设置,官方的建议是如果为 4 核的 CPU,建议线程数设置为 2 或 3,如果为 8 核 CPU 建议线程数设置为 6,线程数一定要小于机器核数,线程数并不是越大越好。

二、高性能

        通常我们的理解是单线程性能没有多线程好,那redis又是如何做到高性能的了?

2.1 I/O多路复用

        这是我们最多看到的一句解释,redis使用了I/O多路复用的模式,所以性能高,那么到底什么是I/O多路复用模型,以及在redis中怎么实现的。我这里先打几个比方来方便大家理解。

        要过年了,老王去火车站买票回家过年,春运期间票不好买,老王买了三天买到了一张退票。这样一个场景老王有三种方式来完成这次买票:

        方式一:老王去到火车站售票大厅,在长椅上躺了两夜,终于在第三天等到了一张票,兴高采烈的回家了。(老王在火车站待了三天,其他啥事没干,还耗费了6桶泡面一床棉被)

        方式二:老王去到火车站售票大厅买票,没买到,之后每天中午再去一次,终于在第三天买到了票。(老王往返车站6次,路上耗费了3小时,不过这几天其他时间送了三天外卖,又给家里的老婆挣了不少钱)

        方式三:老王去到火车站售票大厅买票,没买到,这时候看到一个黄牛在帮别人买票,老王想着还有外卖要送,就让黄牛帮他买,三天后黄牛买到了票通知他下班后来取。(老王往返车站两次,路上耗费1小时,给了黄牛50块手续费,其他时间送了三天外卖,由于老王临近过年每天都没耽搁的加班送外卖,平台奖励了老王500块)

        第一种方式就是阻塞IO模型,第二种方式就是非阻塞IO模型,第三种方式就是IO复用模型了。除了老王,老张老李......都找了黄牛买票,这样大家都可以不用跑火车站了,等黄牛消息就行。黄牛帮一个人买是买,帮多个人买也是买,反正都要在这里排队,还能多挣几份钱。老王老张老李的请求,都复用这个黄牛搞定了,老王他们节省了时间和精力干了其他事,黄牛一个人花费了近乎一样的时间和精力赚了多份钱。 大概理解了I/O多路复用的概念接下来就看看在redis中是如何实现的。针对IO复用思想前后主要有select, poll, epoll 三种技术实现。

        Select:select是I/O多路复用的第一个实现(1983年),有I/O事件发生了,却并不知道是哪几个流,只能无差别轮询所有流,找出能读出数据,或者写入数据的流,同时处理的流越多,轮询时间就越长。就好比黄牛给多个人买票,但是并不知道买到票是谁的,只能不停的去问所有买票的人是不是你的。这样买票的人越多,黄牛要问的人就越多。

        select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:

  1. 单个进程可监视的fd数量被限制,即能监听端口的大小有限。一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.

  2.  对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低。 

  3. 需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。

        Poll:poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。它没有最大连接数的限制,原因是它是基于链表来存储的。

        Epoll:epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。

        通过以上三种技术实现的分析,epoll无疑是最好的选择,那么redis中是这样选择的吗?先来看下redis在做多路复用函数选择时的代码实现:

ifdef HAVE_EVPORT
include "ae_evport.c"
else
    ifdef HAVE_EPOLL
    include "ae_epoll.c"
    else
        ifdef HAVE_KQUEUE
        include "ae_kqueue.c"
        else
        include "ae_select.c"
        endif
    endif
endif

执行逻辑如下图:

        可以看到redis针对不同的操作系统会选用不同的实现,主流操作系统都有类似epoll的实现作为选择,同时也提供了select方式作为备选。

2.2 Reactor(反应堆模式)

        有了epoll等IO复用技术的支撑,接下来我们看看redis是如何利用IO复用来串连起socket连接请求和具体任务处理的。

        由上图可以看出redis处理并发客户端连接的方式是利用epoll来实现IO多路复用,将连接信息和事件放到队列中,之后依次放到文件事件分派器,事件分派器将事件分发给事件处理器。这种处理方式叫做反应堆模式。Redis是基于Reactor模式(反应堆模式)开发了自己的网络模型,形成了一个完备的基于IO复用的事件驱动服务器。

        上面我们了解到epoll方式的多路复用实现已经是很高性能的了,那么为什么redis在此基础上还要基于Reactor来实现自己的网络模型了?

        epoll将收集到的可读写事件全部放入队列中等待业务线程的处理,此时线程池的工作线程拿到任务进行处理,实际场景中可能有很多种请求类型,工作线程每拿到一种任务就进行相应的处理,处理完成之后继续处理其他类型的任务,工作线程需要关注各种不同类型的请求,对于不同的请求选择不同的处理方法,因此请求类型的增加会让工作线程复杂度增加,维护起来也变得越来越困难。

        如果我们在epoll的基础上进行业务区分,并且对每一种业务设置相应的处理函数,每次来任务之后对任务进行识别和分发,每种处理函数只处理一种业务,这种模型也就是Reactor反应堆模式的设计思路。

        通俗点讲就是黄牛的业务做的很好,找黄牛除了买火车票还有买机票电影票的,那么黄牛每次处理不同的业务的时候就要不断跑来跑去切换业务场景,显然这样业务没法做大做强,黄牛就找了多个业务员,负责专门买火车票,飞机票,电影票,这样黄牛接到不同业务的时候就交给不同的业务员去做,接客能力一下就增强了。

三、告一段落

        到这里我们从redis的线程模型分析了redis为什么使用单线程,以及从单线程性能依旧很出色分析了基于I/O多路复用的反应堆模式请求处理流程。下一篇将从redis的内存模型来解读redisdb的数据结构以及内存回收机制。


文章导航

类别标题发布
RedisRedis(一):单线程为何还能这么快本文章
Redis(二):内存模型及回收算法2021.10.22
Redis(三):持久化2021.10.27
Redis(四):主从同步即将上线
Redis(五):集群搭建即将上线
Redis(六):实战应用即将上线
ElasticsearchElasticsearch:概述2021.10.20
Elasticsearch:核心2021.10.25
Elasticsearch:实战2021.10.29
Elasticsearch写入流程详解即将上线
Elasticsearch查询流程详解即将上线
Elasticsearch集群一致性即将上线
Lucene的基本概念即将上线
Elasticsearch部署架构和容量规划即将上线
RocketMQRocketMQ—NameServer总结及核心源码剖析2021.11.01
RocketMQ—深入剖析Producer即将上线
Broker—启动流程源码解密即将上线
Broker—接受消息处理流程解密即将上线
Tomcat源码分析Tomcat(一):项目结构及架构分析即将上线
Tomcat(二):启动关闭流程分析即将上线
Tomcat(三):应用加载原理分析即将上线
Tomcat(四):网络请求原理分析即将上线
Tomcat(五):嵌入式及性能调优即将上线
NacosNacos项目结构及架构分析即将上线
Nacos服务注册源码解析即将上线
Nacos配置管理源码解析即将上线
Nacos2.0版本优化功能解析即将上线
Netty计算机网络&nio核心原理即将上线
详细解读kafka是如何基于原生nio封装网络通信组件的?即将上线
netty初识之核心组件介绍即将上线
源码解析netty服务端,端口是如何拉起来的?新连接接入又如何处理?即将上线
深入netty中各种事件如何在pipeline中传播的?即将上线
网络编程中拆包粘包是什么?kafka以及netty是如何解决的?即将上线
深入解读netty中最为复杂的缓存分配是如何进行的?即将上线
源码分析netty、kafka、sentinel中不同时间轮实现方式以及细节即将上线
尝试从上帝角度对比kafka&netty中的性能优化,各种设计模式的丰富运用即将上线
Netty在Rocketmq中的实践,RocketMq的消息协议解读即将上线

 

关注IT巅峰技术,私信作者,获取以下2021全球架构师峰会PDF资料。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值