四十九

首页新闻博问专区闪存班级                          我的博客我的园子账号设置退出登录注册登录博客园新随笔订阅管理随笔 - 116  文章 - 0  评论 - 1287 Redis多线程原理详解 本篇文章为你解答以下问题:0:redis单线程的实现流程是怎样的?1:redis哪些地方用到了多线程,哪些地方是单线程?2:redis多线程是怎么实现的?3:redis多线程是怎么做到无锁的? 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_write6:主线程将全局队列clients_pending_write以轮训的方式将任务分发到每个线程对应的队列io_threads_list7:所有线程将遍历自己的队列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 -> readQueryFromClient

main -> 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_list
list *io_threads_list[IO_THREADS_MAX_NUM];是数组双向链表,一个线程对应其中一个队列

子线程将client中的数据发给客户端,所以是多线程
server.c -> main -> initThreadedIO(启动一定数量的线程) -> IOThreadMain(线程执行的方法) -> writeToClient -> connWrite -> connSocketWrite 网络操作对应的一些方法,所有connection对象的type字段都是指向CT_SocketConnectionType 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/
本文版权归作者和博客园共有,欢迎转载 分类: Redis好文要顶 关注我 收藏该文 啊汉
关注 - 9
粉丝 - 776 推荐博客+加关注 0 0

« 上一篇: redis client原理分析 posted @ 2020-11-25 19:23  啊汉  阅读(110)  评论(0)  编辑  收藏

刷新评论刷新页面返回顶部

发表评论 【福利】注册AWS账号,立享12个月免费套餐 编辑预览 7693b08a-a8f6-49f3-f45a-08d88556cc23 Markdown 帮助自动补全 不改了退出 订阅评论 [Ctrl+Enter快捷键提交]

首页 新闻 博问 专区 闪存 班级 【推荐】News: 大型组态、工控、仿真、CADGIS 50万行VC++源码免费下载
【推荐】从零开始的RPG游戏制作教程,来《魔兽争霸III》共同成长
【推荐】了不起的开发者,挡不住的华为,园子里的品牌专区
【推荐】未知数的距离,毫秒间的传递,声网与你实时互动
【福利】AWS携手博客园为开发者送免费套餐与抵扣券
【推荐】阿里云返场期奖励继续,赢5万现金
相关博文:
· Redis-安装redis
· Redis
· Redis
· redis
· redis
» 更多推荐…最新 IT 新闻:
· 辛巴燕窝直播争议背后:主播到底是销售者还是广告代言人?
· 『牵手微软、拿下融资』小冰的商业化探索又释放了哪些信号?
· 摁住巨头:腾讯阿里们要小心了?
· 耽美IP到底能撬动多大市场?
· 租客和房东都要打起来了,蛋壳公寓还在微博阴阳怪气
» 更多新闻…

公告 昵称: 啊汉
园龄: 10年5个月
荣誉: 推荐博客
粉丝: 776
关注: 9 +加关注

最新随笔1.Redis多线程原理详解 2.redis client原理分析 3.分布式系统选主场景分析及实现 4.聊聊redis单线程为什么能做到高性能和io多路复用到底是个什么鬼 5.golang实现常用集合原理介绍 6.删除单链表,你会吗? 7.nsq源码分析 8.Go map实现原理 9.原子操作&普通锁&读写锁 10.Go channel实现源码分析 积分与排名 积分 - 268389 排名 - 1994 随笔分类 (159) .NET(13) C#(23) C/C++(44) Effective C++(3) Golang(10) JS(1) Redis(3) SQL Server(4) Windows编程(4) 读书(8) 阅读排行榜 1. C++网络编程(一)(78367) 2. 《STL系列》之vector原理及实现(38079) 3. C++网络编程(二)–客户端服务器程序(32961) 4. 《STL系列》之map原理及实现(25121) 5. 多线程编程–5种方法实现线程同步(22602) 6. C#转C++的一点分享(20089) 7. 7种方式实现斐波那契数列(19859) 8. 算法–找出数组中出现次数超过一半的数(18387) 9. 算法–两道百度笔试题(17304) 10. 15道简单算法题(16638) 评论排行榜 1. 算法–两道百度笔试题(154) 2. C#类在什么时候分配内存(83) 3. C#成员初始化有点坑爹(82) 4. 第一次负责项目总结(79) 5. C#转C++的一点分享(61) 6. 毕业两年工作三年小结(47) 7. 《12个球问题》分析(35) 8. C#自定义分页控件(29) 9. 常见充值方式介绍及对比(27) 10. C#内存管理与垃圾回收(27) 推荐排行榜 1. C++网络编程(一)(135) 2. 第一次负责项目总结(42) 3. C#转C++的一点分享(31) 4. 一个提高查找速度的小技巧(20) 5. 毕业两年工作三年小结(13)

Copyright © 2020 啊汉
Powered by .NET 5.0.0 on Kubernetes

#MySignature1 {
background: url(“http://www.cnblogs.com/images/cnblogs_com/Terrylee/147338/o_info.png?id=02115344”) no-repeat scroll 1% 50% #FFFEFE;
border: 1px solid #E5E5E5;
padding: 10px 10px 10px 70px;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值