77百科网
当前位置: 首页 生活百科

redis 多线程(Redis多线程到底有多快)

时间:2023-06-13 作者: 小编 阅读量: 1 栏目名: 生活百科

总之单线程,定时事件和网络事件还是会互相影响的,正在处理定时事件网络任务来了,正在处理网络事件定时任务的时间到了。上面说了多线程的地方都是互相独立互不影响的。

0:Redis单线程的实现流程是怎样的?

Redis一开始是单线程模型,在一个线程中要同时处理两种事件:文件事件和时间事件

文件事件主要是网络I/O的读写,请求的接收和回复

时间事件就是单次/多次执行的定时器,如主从复制、定时删除过期数据、字典rehash等

redis所有核心功能都是跑在主线程中的,像aof文件落盘操作是在子线程中执行的,那么在高并发情况下它是怎么做到高性能的呢?

由于这两种事件在同一个线程中执行,就会出现互相影响的问题,如时间事件到了还在等待/执行文件事件,或者文件事件已经就绪却在执行时间事件,这就是单线程的缺点,所以在实现上要将这些影响降到最低。那么redis是怎么实现的呢?

定时执行的时间事件保存在一个链表中,由于链表中任务没有按照执行时间排序,所以每次需要扫描单链表,找到最近需要执行的任务,时间复杂度是O(N),redis敢这么实现就是因为这个链表很短,大部分定时任务都是在serverCron方法中被调用。从现在开始到最近需要执行的任务的开始时间,时长定位T,这段时间就是属于文件事件的处理时间,以epoll为例,执行epoll_wait最多等待的时长为T,如果有就绪任务epoll会返回所有就绪的网络任务,存在一个数组中,这时我们知道了所有就绪的socket和对应的事件(读、写、错误、挂断),然后就可以接收数据,解析,执行对应的命令函数。

如果最近要执行的定时任务时间已经过了,那么epoll就不会阻塞,直接返回已经就绪的网络事件,即不等待。

总之单线程,定时事件和网络事件还是会互相影响的,正在处理定时事件网络任务来了,正在处理网络事件定时任务的时间到了。所以redis必须保证每个任务的处理时间不能太长。

redis处理流程如下:

1:服务启动,开始网络端口监听,等待客户端请求

2:客户端想服务端发起连接请求,创建客户端连接对象,完成连接

3:将socket信息注册到epoll,设置超时时间为时间事件的周期时长,等待客户端发起请求

4:客户端发起操作数据库请求(如GET)

5:epoll收到客户端的请求,可能多个,按照顺序处理请求

6:接收请求参数,接收完成后解析请求协议,得到请求命令

7:执行请求命令,即操作redis数据库

8:将结果返回给客户端

1:redis哪些地方用到了多线程,哪些地方是单线程?

Redis多线程和单线程模型对比如下图:

从上图中可以看出只有以下3个地方用的是多线程,其他地方都是单线程:

1:接收请求参数

2:解析请求参数

3:请求响应,即将结果返回给client

很明显以上3点各个请求都是互相独立互不影响的,很适合用多线程,特别是请求体/响应体很大的时候,更能体现多线程的威力。而操作数据库是请求之间共享的,如果使用多线程的话适合读写锁。而操作数据库本身是很快的(就是对map的增删改查),单线程不一定就比多线程慢,当然也有可能是作者偷懒,懒得实现罢了,但这次的多线程模型还是值得我们学习一下的。

2:redis多线程是怎么实现的?

先大致说一下多线程的流程:

1:服务器启动时启动一定数量线程,服务启动的时候可以指定线程数,每个线程对应一个队列(list *io_threads_list[128]),最多128个线程。

2:服务器收到的每个请求都会放入全局读队列clients_pending_read,同时将队列中的元素分发到每个线程对应的队列io_threads_list中,这些工作都是在主线程中执行的。

3:每个线程(包括主线程和子线程)接收请求参数并做解析,完事后在client中设置一个标记CLIENT_PENDING_READ,标识参数解析完成,可以操作数据库了。(主线程和子线程都会执行这个步骤)

4:主线程遍历队列clients_pending_read,发现设有CLIENT_PENDING_READ标记的,就操作数据库

5:操作完数据库就是响应client了,响应是一组函数addReplyXXX,在client中设置标记CLIENT_PENDING_WRITE,同时将client加入全局写队列clients_pending_write

6:主线程将全局队列clients_pending_write以轮训的方式将任务分发到每个线程对应的队列io_threads_list

7:所有线程将遍历自己的队列io_threads_list,将结果发送给client

3:redis多线程是怎么做到无锁的?

上面说了多线程的地方都是互相独立互不影响的。但是每个线程的队列就存在两个两个线程访问的情况:主线程向队列中写数据,子线程消费,redis的实现有点反直觉。按正常思路来说,主线程在往队列中写数据的时候加锁;子线程复制队列&并将队列清空,这个两个动作是加锁的,子线程消费复制后的队列,这个过程是不需要加锁的,按理来说主线程和子线程的加锁动作都是非常快的。但是redis并没有这么实现,那么他是怎么实现的呢?

redis多线程的模型是主线程负责搜集任务,放入全局读队列clients_pending_read和全局写队列clients_pending_write,主线程在将队列中的任务以轮训的方式分发到每个线程对应的队列(list *io_threads_list[128])

1:一开始子线程的队列都是空,主线程将全对队列中的任务分发到每个线程的队列,并设置一个队列有数据的标记(_Atomic unsigned long io_threads_pending[128]),io_threads_pending[1]=5表示第一个线程的队列中有5个元素

2:子线程死循环轮训检查io_threads_pending[index] > 0,有数据就开始处理,处理完成之后将io_threads_pending[index] = 0,没数据继续检查

3:主线程将任务分发到子线程的队列中,自己处理自己队列中的任务,处理完成后,等待所有子线程处理完所有任务,继续收集任务到全局队列,在将任务分发给子线程,这样就避免了主线程和子线程同时访问队列的情况,主线程向队列写的时候子线程还没开始消费,子线程在消费的时候主线程在等待子线程消费完,子线程消费完后主线程才会往队列中继续写,就必须加锁了。因为任务是平均分配到每个队列的,所以每个队列的处理时间是接近的,等待的时间会很短。

4:源码执行流程

为了方便你看源码,这里加上一些代码的执行流程

启动socket监听,注册连接处理函数,连接成功后创建连接对象connection,创建client对象,通过aeCreateFileEvent注册client的读事件

main -> initServer -> acceptTcpHandler -> anetTcpAccept -> anetGenericAccept -> accept(获取到socket连接句柄)connCreateAcceptedSocket -> connCreateSocket -> 创建一个connection对象acceptCommonHandler -> createClient创建client连接对象 -> connSetReadHandler -> aeCreateFileEvent -> readQueryFromClientmain -> aeMain -> aeProcessEvents -> aeApiPoll(获取可读写的socket) -> readQueryFromClient(如果可读) -> processInputBuffer -> processCommandAndResetClient(多线程下这个方法在当前流程下不会执行,而由主线程执行)

在多线程模式下,readQueryFromClient会将client信息加入server.clients_pending_read队列,listAddNodeHead(server.clients_pending_read,c);

主线程会将server.clients_pending_read中的数据分发到子线程的队列(io_threads_list)中,子线程会调用readQueryFromClient就行参数解析,主线程分发完任务后,会执行具体的操作数据库的命令,这块是单线程

如果参数解析完成会在client->flags中加一个标记CLIENT_PENDING_COMMAND,在主线程中先判断client->flags & CLIENT_PENDING_COMMAND > 0,说明参数解析完成,才会调用processCommandAndResetClient,之前还担心如果子线程还在做参数解析,主线程就开始执行命令难道不会有问题吗?现在一切都清楚了

main -> aeMain -> aeProcessEvents -> beforeSleep -> handleClientsWithPendingReadsUsingThreads -> processCommandAndResetClient -> processCommand -> call

读是多次读:socket读缓冲区有数据,epoll就会一直触发读事件,所以读可能是多次的

写是一次写:往socket写数据是在子线程中执行的,直接循环直到数据写完位置,就算某个线程阻塞了,也不会像单线程那样导致所有任务都阻塞

执行完相关命令后,就是将结果返回给client,回复client是一组函数,我们以addReply为例,说一下执行流程,执行addReply还是单线程的,将client信息插入全局队列server.clients_pending_write。addReply -> prepareClientToWrite -> clientInstallWriteHandler -> listAddNodeHead(server.clients_pending_write,c)在主线程中将server.clients_pending_write中的数据以轮训的方式分发到多个子线程中beforeSleep -> handleClientsWithPendingWritesUsingThreads -> 将server.clients_pending_write中的数据以轮训的方式分发到多个线程的队列中io_threads_listlist *io_threads_list[IO_THREADS_MAX_NUM];是数组双向链表,一个线程对应其中一个队列子线程将client中的数据发给客户端,所以是多线程server.c -> main -> initThreadedIO(启动一定数量的线程) -> IOThreadMain(线程执行的方法) -> writeToClient -> connWrite -> connSocketWrite

网络操作对应的一些方法,所有connection对象的type字段都是指向CT_Socket

ConnectionType CT_Socket = {.ae_handler = connSocketEventHandler,.close = connSocketClose,.write = connSocketWrite,.read = connSocketRead,.accept = connSocketAccept,.connect = connSocketConnect,.set_write_handler = connSocketSetWriteHandler,.set_read_handler = connSocketSetReadHandler,.get_last_error = connSocketGetLastError,.blocking_connect = connSocketBlockingConnect,.sync_write = connSocketSyncWrite,.sync_read = connSocketSyncRead,.sync_readline = connSocketSyncReadLine};

作者:陈太汉出处:http://hlxs.cnblogs.com/

    推荐阅读
  • 白夜行一样好看的小说(白夜行终于读完了)

    警方在她家发现了吃过之后丢弃的包装盒,于是警方断定桐原洋介那天来过雪穗家。西本母女两人居住,雪穗的父亲七年前在工地上发生意外去世了。警方猜测桐原洋介和西本文代的关系不一般,不巧的是西本文代却有完美的不在场的证明。寺崎忠夫承认了他和西本文代确实在交往。这时的警方,推测桐原洋介可能是胁迫西本文代,而寺崎忠夫无法忍受,便将其杀害的,又不巧的是,这个时候,寺崎忠夫发生了意外,疲劳驾驶意外死亡。

  • 昀怎么读什么意思(昀读音和解释)

    跟着小编一起来看一看吧!昀怎么读什么意思昀是一个汉语汉字,读音为yún。释义为日光,出自于《玉篇》――昀,日光也。多用于人名,纪昀,即纪晓岚。《玉篇》日光也。昀部首:日昀笔画:8

  • 屋顶被火球砸死(神秘火球坠落挪威首都)

    NMN当地时间7月25日午夜,挪威首都奥斯陆的一些市民被空中传来的巨响惊醒。研究人员推测,这个火球是一颗火流星。根据监控,研究人员推断这颗火流星最终落入了距奥斯陆市区约60公里的Finnemarka森林中,并在流星坠落后进行了搜寻。这颗火流星的本体有卡车般大,释放出的能量相当于广岛原子弹爆炸的30倍左右。它坠落后给当地的建筑造成了一定程度的破坏,并使大约1200人不同程度地受伤神秘火球午夜坠落挪威的森林。

  • 帝豪s和帝豪gs有什么区别(帝豪s跟帝豪gs)

    近日,小编经常收到小伙伴们关于“帝豪S和帝豪GS有什么区别”的相关留言,现在为大家讲解。帝豪s和帝豪gs都是吉列汽车旗下的SUV车型。帝豪s和帝豪gs两者的主要区别在于:1,产品售价对比,帝豪s的定价比帝豪gs稍微高一些;2,产品定位对比,帝豪s虽然跟帝豪gs在车型还有很多方面很类似,但是帝豪s更注重在年轻运动、时尚感、科技感方面的品牌升级。买车究竟是买轿车好呢?其实,这个关键还是要看买车的目的是什么。

  • 普通翡翠怎么让它水头好点(如果你不懂翡翠的)

    当欣赏翡翠的水头时,联想起女人如水这句话,都有那么一种类似的感觉,清新雅致,显得是那样的纯净而动人、单纯、和平之美别具韵味。水头是翡翠业内的行家通过长时期的观察总结出来的一种比拟性的表述。说到翡翠的水头,大家都知道透明度好的翡翠,水头就好,水头就是翡翠的透明度,这种说法不完全正确。水头的好坏直接关系到成品的质量,是评价翡翠的重要因素之一。

  • 鲜红的太阳在徐徐上升改成比喻句(什么是比喻句)

    我们一起去了解并探讨一下这个问题吧!鲜红的太阳在徐徐上升改成比喻句徐徐上升的太阳像鲜红的一团火。鲜红的太阳在徐徐上升,像一个硕大的火球。比喻句,是一种常用修辞手法,意思是打比方,用浅显、具体、生动的事物来代替抽象、难理解的事物。

  • 金乡定亲风俗(文化费县之婚嫁习俗篇)

    费县民间关于婚嫁的习俗讲究也特别多,操办起来也是力求热闹。不过了解费县这些习俗还是很有意义的,既是对传统文化的传承,很多礼节的设定本来也具有实用价值。费县的很多婚嫁习俗也是这样,看似落后的无价值的仪式,有一部分因为生活习惯变化太大没有操作必要了,但大部分还是有价值的。今天首先介绍费县婚嫁习俗当中的定亲仪式,明天介绍送日子习俗,欢迎继续关注。

  • 隔离乳可以直接涂吗(怎么使用)

    隔离乳的主要作用是隔绝皮肤和彩妆,以及空气中的污染物,虽然它里面也含有一定物质,可以滋养肌肤,但单靠这些是不能够满足肌肤要求的,所以在使用隔离乳之前要做好基础护肤工作。一般情况下最简单的步骤是在使用完爽肤水以及乳液之后涂抹隔离乳,如果是本身肌肤比较容易出油,或是夏天容易出汗的情况下,可以选择拍完水之后使用隔离乳。隔离乳的主要作用是保护肌肤,使用完隔离乳之后,就可以使用其他彩妆产品了。

  • 等一个人的说说(20条等一个人的说说心情短语)

    死缠烂打的样子特别丑,所以我选择自己走。如果有一天,我走进你的心里,我也会哭,因为那里没有我。

  • 要请病假的证明怎么开?(请病假需要怎么开证明)

    第三条企业职工因患病或非因工负伤,需要停止工作医疗时,根据本人实际参加工作年限和在本单位工作年限,给予三个月到二十四个月的医疗期:(一)实际工作年限十年以下的,在本单位工作年限五年以下的为三个月;五年以上的为六个月。