内容简介:运营研发团队程序媛 张晶晶基于以上两个疑问,我进行了事件驱动模型的研究和分析先明确一点:事件驱动模型的本质是单线程的,因为想要同时处理多个请求,我们需要换成事件模型的方式重构代码
运营研发团队程序媛 张晶晶
背景
- 1.最近研究 redis 关于主从复制的功能实现,发现客户端实时响应slaveof的命令后,把主从复制添加到epoll的时间事件中再进行操作。因此有疑问,redis是如何进行文件和时间事件的调度
- 2.go的一大特点就是从语言方面支持协程,提供系统的并发性,那么 go 语言中是否还需要epoll这种事件驱动模型
基于以上两个疑问,我进行了事件驱动模型的研究和分析
分析
先明确一点:事件驱动模型的本质是单线程的,因为想要同时处理多个请求,我们需要换成事件模型的方式重构代码
1.最简单的模型是单线程
bind()
listen()
while(1) {
accept() //接收新连接
handle() //处理消息
}
当多个客户端请求时只能一个个处理,只要等到当前连接结束,才能处理下一个连接。这种方式处理效率低下,怎么提高处理效率呢,有两种方法:多线程处理,或者一个线程处理多个连接
2.多线程
bind()
listen()
while(1) {
accept()
pthread_create()
}
这样就可以同时处理多个请求。但是这种方式有几个不好的地方,
- cpu和内存消耗,上下文切换
- 线程安全问题
- 问题定位难等
对于第一个问题,在程序中进行速率的限制,防止客户端无限链接->线程池
3.事件驱动模型
除了线程池,还可以采用非阻塞式IO,通过fcntl设置套接字。这种方式只能通过不断的轮询来检查是否有请求数据到来。
操作系统应该是知道哪个套接字是准备好了数据的,因此没必要逐个扫描。
select
想想一个线程,有一堆的任务要处理,应该监视哪些东西呢,两种类型的套接字活动:
- 1.accept
- 2.读写事件
尽管这两种活动在本质上有所区别,我们还是要把它们放在一个循环里,因为只能有一个主循环。循环会包含 select 的调用。这个 select 的调用会监视上述的两种活动。
while(1) {
int nready = select(fdset_max + 1, &readfds, &writefds, NULL, NULL); // 获取事件的个数
//遍历fd
for (fd = 0; fd <= fdset_max && nready > 0; fd++) {
// Check if this fd became readable.
if (FD_ISSET(fd, &readfds)) { //是否为读事件
nready--;
if (fd == listener_sockfd) { //是否为请请求到来
fd_status_t status =
on_peer_connected(newsockfd, &peer_addr, peer_addr_len);
if (status.want_read) { //是否有读事件
FD_SET(newsockfd, &readfds_master);
} else {
FD_CLR(newsockfd, &readfds_master);
}
if (status.want_write) { //是否有写事件
FD_SET(newsockfd, &writefds_master);
} else {
FD_CLR(newsockfd, &writefds_master);
}
}
} else {
fd_status_t status = on_peer_ready_recv(fd);//接收数据
if (status.want_read) {
FD_SET(fd, &readfds_master);
} else {
FD_CLR(fd, &readfds_master);
}
if (status.want_write) {
FD_SET(fd, &writefds_master);
} else {
FD_CLR(fd, &writefds_master);
}
}
}
if (FD_ISSET(fd, &writefds)){ //是否有写事件
//
write()// 发送消息
...
}
}
流程图的处理从图一变成了图二
select 方法会返回需要处理的事件的个数,然后遍历所有的fd去处理。
但是这种方法依然是有缺陷,第一既然知道了事件的个数,可不可以知道事件是发生在哪个fd上。不然每次都需要遍历所有的fd,限制性能。
此外,FD_SETSIZE是一个编译期常数,在如今的操作系统中,它的值通常是 1024。它被硬编码在 glibc 的头文件里,并且不容易修改。它把 select 能够监视的文件描述符的数量限制在 1024 以内。曾有些人想要写出能够处理上万个并发访问的客户端请求的服务器,所以这个问题很有现实意义。
epoll
epoll 高效的关键之处在于它与内核更好的协作。epoll_wait 用当前准备好的事件填满一个缓冲区。只有准备好的事件添加到了缓冲区,因此没有必要遍历客户端中当前所有 监视的文件描述符。这简化了查找就绪的描述符的过程,把空间复杂度从 select 中的 O(N) 变为了 O(1)。
while(1){
int nready = epoll_wait(epollfd, events, MAXFDS, -1);
for ( int i = 0; i < nready; i++) {
...
}
}
要在 select 里面重新遍历,有明显的差异:如果在监视着 1000 个描述符,只有两个就绪, epoll_waits 返回的是 nready=2,然后修改 events 缓冲区最前面的两个元素,因此我们只需要“遍历”两个描述符。用 select 我们就需要遍历 1000 个描述符,找出哪个是就绪的。因此,在繁忙的服务器上,有许多活跃的套接字时 epoll 比 select 更加容易扩展。
redis为什么要实现自己的事件库?
Redis 并没有使用 libuv,或者任何类似的事件库,而是它去实现自己的事件库 —— ae,用 ae 来封装 epoll、kqueue 和 select。事实上,Antirez(Redis 的创建者)恰好在 2011 年的一篇文章 http://oldblog.antirez.com/po... 中回答了这个问题。他的回答的要点是:ae 只有大约 770 行他理解的非常透彻的代码;而 libuv 代码量非常巨大,也没有提供 Redis 所需的额外功能。
epoll 实现
https://www.cnblogs.com/appre... 这篇文章分析了epoll的实现
一颗红黑树,一张准备就绪fd链表,少量的内核cache,就帮我们解决了大并发下的fd(socket)处理问题。
- 1.执行epoll_create时,创建了红黑树和就绪list链表。
- 2.执行epoll_ctl时,如果增加fd(socket),则检查在红黑树中是否存在,存在立即返回,不存在则添加到红黑树上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪list链表中插入数据。
- 3.执行epoll_wait时立刻返回准备就绪链表里的数据即可
又来一个问题,为什么epoll选择红黑树问不是其它avl树,mysql 选择b+树
为什么不用 AVL 树作为底层实现, 那是因为 AVL 树是高度平衡的树, 而每一次对树的修改, 都要 rebalance, 这里的开销会比红黑树大. 红黑树插入只要两次旋转, 删除至多三次旋转. 但不可否认的是, AVL 树搜索的效率是非常稳定的. 选取红黑树, 是一种折中的方案
B+树是为磁盘或其他直接存取的辅助存储设备而设计的一种数据结构。mysql为什么选取B+树,本质上是因为 mysql 数据是存放在外部存储的
go是否需要epoll
因为协程的高效,在go中处理多客户端请求,只需要如下这样写即可
for{
accept()
go handle()
}
是否需要epoll的编程模型呢,在一篇帖子中写到
go中怎么找不到像epoll或者iocp这种编程模型
答案很简单:goroutine底层用的非阻塞+epoll,所以你可以用同步的方式写出异
步的程序。
链接: https://grokbase.com/p/gg/gol...
验证需要查看goroutine的底层实现。(大概率是正确的,这就是go语言的优势所在)
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- SpringBoot | :异步开发之异步调用
- 改进异步封装:处理带返回值的异步调用
- 异步发展流程 —— Generators + co 让异步更优雅
- 文件系统与异步操作——异步IO那些破事
- 浅析“远程对象调用”
- 浅析“远程对象调用”
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Programming in Haskell
Graham Hutton / Cambridge University Press / 2007-1-18 / GBP 34.99
Haskell is one of the leading languages for teaching functional programming, enabling students to write simpler and cleaner code, and to learn how to structure and reason about programs. This introduc......一起来看看 《Programming in Haskell》 这本书的介绍吧!