redis网络架构及单线程模型

更新时间:2023-05-24 21:47:43 阅读: 评论:0

redis⽹络架构及单线程模型
⼀、⽹络基础架构
连花清瘟胶囊说明书
这⾥解释下上图涉及的组件,Redis⽹络层基础组件主要包括四个部分:
1)EventLoop事件轮询器,这部分实现在AE⾥⾯。
2)提供Socket句柄事件的多路复⽤器,这部分分别对于不同平台提供了不同的实现,⽐如epoll和lect可以⽤于Linux平台、kqueue可以⽤于苹果平台、evpoll可以⽤于Solaris平台,这⾥并没有看到iocp,也就是Redis对于Windows⽀持并不是很好。
3)包括⽹络事件处理器实现的networking,这部分主要包括两个重要的事件处理器:acceptTcpHandler和acceptCommonHandler。
4)处理⽹络⽐较底层的部分,⽐如⽹络句柄创建、⽹络的读写等。
⼆、单进程单线程模型
要理解Redis的单线程模型,我们先抛出⼀些问题,当我们有多个客户端同时去跟Redis Server建⽴连接,之后⼜同时对某个key进⾏操作,这个过程中发⽣了什么呢?会不会有并发问题?这些问题先丢在这了,我们看看Redis启动初始化的过程中会做什么事情,这⾥尽量省略了与本⽂⽆关的部分:
1)初始化Redis Server参数,这部分代码通过initServerConfig实现。
2)初始化Redis Server,这部分代码在initServer⾥⾯。
3)启动事件轮询器。
对,这⾥我们就把Redis的启动部分简化为三步,跟⽹络操作有关的主要在第⼆步和第三步⾥⾯,来看看initServer⾥⾯发⽣了什么:
initServer流程
initServer⾥⾯⾸先创建了⼀个EventLoop,然后监听Server的IP对应的端⼝号,假设我们监听的是 127.0.0.1:3333 这个IP:端⼝对,我们得到的⼀个Server Socket句柄,最后通过createFileEvent将我们得到的Server Socket句柄和我们关⼼的⽹络事件mask注册到EventLoop上⾯。EventLoop是什么呢,我们看看它的定义:
typedef struct aeEventLoop {
int maxfd;  /* highest file descriptor currently registered */
int tsize; /* max number of file descriptors tracked */
long long timeEventNextId;
time_t lastTime;    /* Ud to detect system clock skew */
aeFileEvent *events; /* Registered events */
黑鱼的功效>太太学堂
aeFiredEvent *fired; /* Fired events */
aeTimeEvent *timeEventHead;
int stop;
void *apidata; /* This is ud for polling API specific data */
aeBeforeSleepProc *beforesleep;
} aeEventLoop;
上⾯我们关注的主要是两个东西:events和fired。他们分别是两个数组,events⽤于存放被注册的事件以及相应的句柄,fired⽤于存放当EventLoop线程从多路复⽤器轮询到有事件的句柄的时候,EventLoop线程会把它放⼊fired数组⾥⾯,然后处理。
我⽤上⾯的⽰意图描述createFileEvent做的事情,就是将Server Socket句柄和关⼼的事件mask以及当事件产⽣的时候的事件处理器accptHandler⽣成⼀个aeFileEvent注册到EventLoop的events的数组
⾥⾯,当然在这之前会⾸先将事件注册到多路复⽤器上,也就是epoll、kqueue等这些组件上。事件注册完之后需要对多路复⽤器进⾏轮询,来分离我们关⼼切发⽣的事件,那就是最后⼀步,启动事件轮询器。
接收⽹络连接
上⾯的步骤完成了服务端的⽹络初始化,⽽且事件轮询器已经开始⼯作了,事件轮询器做什么事情呢,就是不断轮询多路复⽤器,看看之前注册的事件有没有发⽣,如果有发⽣,则将会将事件分离出来,放⼊EventLoop的fired数组中,然后处理这些事件。
很显然,上⾯注册的事件是客户端建⽴连接这个事件,因此当有两个客户端同时连接Redis服务器的时候,事件轮询器会从多路复⽤器上⾯分离出这个事件,同时调⽤acceptHandler来处理。acceptHandler做的事情主要是accept客户端的连接,创建socket句柄,然后将socket句柄和读事件注册到EventLoop的events数组⾥⾯,不⼀样的是对于客户端的事件处理器是readQueryClient。
上⾯⽰意图表⽰了acceptHandler处理客户端连接,得到句柄之后再将这个句柄注册到多路复⽤器以及EventLoop上的⽰意图。之后再同样再处理下⼀个客户端的连接,这些都是串⾏的。
事件轮询
上⾯接收客户端这部分其实都发⽣在事件轮询的主循环⾥⾯:
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
}
Redis会不断的轮询多路复⽤器,将⽹络事件分离出来,如果是accept事件,则新接收客户端连接并将其注册到多路复⽤器以及EventLoop 中,如果是查询事件,则通过读取客户端的命令进⾏相应的处理,这⼀切都是单线程,顺序的执⾏的,因此不会发⽣并发问题。
三、⾼性能单线程模型
孩子快抓紧妈妈的手根据官⽅的测试结果《How fast is Redis?》来看,在操作内存的情况下,CPU 并不能起到决定性的
重庆政治作⽤,反⽽可能带来⼀些其他问题。⽐如锁,CPU 切换带来的性能开销等。这⼀点我们可以根据官⽅的测试报告,提供的数据来证明。⽽且官⽅提供的数据是可以达到100000+的QPS(每秒内查询次数),这个数据并不⽐采⽤单进程多线程 Memcached 差!所以在基于内存的操作,CPU 不是 Redis
瓶颈的情况下,瓶颈在⽹络 I/O 上⾯,我们⼀般提供较好的⽹络环境就可以提升Redis的吞吐量,⽐如提⾼⽹络带宽,除此之外还可以通过合并命令提交批处理请求来代替单条命令⼀次次请求从⽽减少⽹络开销,提⾼吞吐量。
好了,说完单线程设计后,我们再来讨论讨论单线程的设计为什么能⽀持⾼并发?原因基本有以下⼏点:
周涛好紧好爽第⼀,我们请求 Redis 更多的是操作内存。直接操作内存就很快啊,数据存在内存中,类似于 HashMap。HashMap 的优势就是查找和操作的时间复杂度都是 O(1)。
第⼆,单线程,没有 CPU 上下⽂切换带来的开销问题。⽽且上⾯也说了,内存操作和 CPU 的多核影响不⼤。直接采⽤单线程,就不⽤考虑各种锁,与之相关的加锁,解锁,死锁等问题就不复存在了。
第三,多路 IO 复⽤。这个后⾯我会具体的来讲讲它。能谈到这⼀点说明对 Redis 有⼀定的理解。这涉及到基于操作系统的⽹络 IO 模型。Reactor ⽹络模式,epoll,poll,lect,kqueue 等多路复⽤ IO。
第四,依赖第⼆点。由于是单线程的,所以就存在⼀个顺序读写问题。⼤家可以⽐较以下,随机读写和顺序读写的速度。
第五,Redis 的数据结构,是经过专门的研究和设计的。所以操作起来简单且快。
第六,Redis ⾃⼰构建了VM 机制 。因为⼀般的调⽤系统函数,会浪费⼀定的时间。
综合以上内容,Redis 才有单线程,⾼性能的特点。
最后,再说⼀点,Redis 是单进程和单线程的设计,并不是说它不能多进程多线程。⽐如备份时会 fork ⼀个新进程来操作;再⽐如基于COW 原理的 RDB 操作就是多线程的。
多路复⽤IO模型中,会有⼀个线程(Java中的Selector)不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调⽤实际的IO读写操作。因为在多路复⽤IO模型中,只需要使⽤⼀个线程就可以管理多个socket,系统不需要建⽴新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进⾏时,才会使⽤IO资源,所以它⼤⼤减少了资源占⽤。
IO多路复⽤模型使⽤了Reactor设计模式实现了这⼀机制。Reactor模式有三种实现⽅式:
Reactor单线程
每个客户端发起连接请求都会交给acceptor,acceptor根据事件类型交给线程handler处理,注意acceptor 处理和 handler 处理都在⼀个线程中处理,所以其中某个 handler 阻塞时, 会导致其他所有的 client 的 handler 都得不到执⾏, 并且更严重的是, handler 的阻塞也会导致整个服务不能接收新的 client 请求(因为 acceptor 也被阻塞了). 因为有这么多的缺陷, 因此单线程Reactor 模型⽤的⽐较少.
Reactor多线程模式
有专门⼀个线程, 即 Acceptor 线程⽤于监听客户端的TCP连接请求.
客户端连接的 IO 操作都是由⼀个特定的 NIO 线程池负责. 每个客户端连接都与⼀个特定的 NIO 线程绑定, 因此在这个客户端连接中的所有IO 操作都是在同⼀个线程中完成的.
客户端连接有很多, 但是 NIO 线程数是⽐较少的, 因此⼀个 NIO 线程可以同时绑定到多个客户端连接中.
五种I/O模型介绍
IO 多路复⽤是5种I/O模型中的第3种,对各种模型讲个故事,描述下区别:
故事情节为:⽼李去买⽕车票,三天后买到⼀张退票。参演⼈员(⽼李,黄⽜,售票员,快递员),往返车站耗费1⼩时。
1.阻塞I/O模型
我的假期生活作文300字⽼李去⽕车站买票,排队三天买到⼀张退票。
耗费:在车站吃喝拉撒睡 3天,其他事⼀件没⼲。
2.⾮阻塞I/O模型
日本地形特点
⽼李去⽕车站买票,隔12⼩时去⽕车站问有没有退票,三天后买到⼀张票。
耗费:往返车站6次,路上6⼩时,其他时间做了好多事。
3.I/O复⽤模型
1.lect/poll
⽼李去⽕车站买票,委托黄⽜,然后每隔6⼩时电话黄⽜询问,黄⽜三天内买到票,然后⽼李去⽕车站交钱领票。
耗费:往返车站2次,路上2⼩时,黄⽜⼿续费100元,打电话17次
2.epoll
⽼李去⽕车站买票,委托黄⽜,黄⽜买到后即通知⽼李去领,然后⽼李去⽕车站交钱领票。
耗费:往返车站2次,路上2⼩时,黄⽜⼿续费100元,⽆需打电话
4.信号驱动I/O模型
⽼李去⽕车站买票,给售票员留下电话,有票后,售票员电话通知⽼李,然后⽼李去⽕车站交钱领票。
耗费:往返车站2次,路上2⼩时,免黄⽜费100元,⽆需打电话
5.异步I/O模型
⽼李去⽕车站买票,给售票员留下电话,有票后,售票员电话通知⽼李并快递送票上门。
耗费:往返车站1次,路上1⼩时,免黄⽜费100元,⽆需打电话
1同2的区别是:⾃⼰轮询
2同3的区别是:委托黄⽜
3同4的区别是:电话代替黄⽜
4同5的区别是:电话通知是⾃取还是送票上门

本文发布于:2023-05-24 21:47:43,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/927229.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:事件   客户端   连接   操作   车站   线程   问题   轮询
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图