禅道博客

分享专业技术知识,文章内容干货满满

《深入理解Linux网络》读书笔记

2022-10-09 10:00:00
刘永凯
原创 98
摘要:通过对《深入理解Linux网络》一书,了解并掌握对网络开发模型中的阻塞概念、同步阻塞IO需要哪些开销?多路复用epoll为啥能提高网络性能?为啥Redis网络性能突出等问题的解答。

1、阻塞到底是啥?

网络开发模型中,阻塞其实说的就是 进程因为等待某个事件而主动让出CPU挂起的操作 。在网络IO中,当进程等待Socket上的数据时,如果数据还没有到来,那就把当前进程状态从TASK_RUNNKNG修改为TASK_INTERRUPTIPLE,然后主动让出CPU。由调度器来调度下一个就绪状态的进程来执行。

所以在分析某个技术方案是不是阻塞的时候,关键要看进程有没有放弃CPU。如果放弃了,那就是阻塞。如果没放弃,那就是非阻塞。

2、同步阻塞IO需要哪些开销?

  • 进程通过recv系统调用接受一个Socket上的数据时,如果数据没有到达,进程就被从CPU上拿下来,然后换上另一个进程。这导致一次进程上下文切换所产生的开销。
  • 当连接上的数据就绪的时候,睡眠的进程又会被唤醒,又是一次进程切换产生的开销。
  • 一个进程同时只能等待一条连接,如果由很多并发,则需要很多进程。每个进程都会占用几MB的内存。
总体上说,同步阻塞网络IO是高性能网络开发路上的绊脚石

3、多路复用epoll为啥能提高网络性能

  • 最根本原因: epoll极大程度地减少了无用的进程上下文切换,让进程更专注地处理网络请求。
    • 在内核的硬、软中断上下文中,包从网卡接手过来进行处理,然后放到Socket的接收队列。再找到Socket关联的epitem,并把它添加到epoll对象的就序链表中。
    • 在用户进程中,通过调用epoll_wait来查看就绪链表中是否有事件到达,如果有,直接取走进行处理。处理完毕再次调用epoll_wait。在高并发的实践中,只要活够多,epoll_wait根本不会让进程阻塞。用户进程会一直干活,直到epoll_wait中没有就绪进程时才会主动让出CPU。这是epoll高效的 核心原因
    • epoll使用的红黑树,也提高了epoll查找、添加、删除Socket时的效率。

4、epoll是阻塞的?

epoll确实是阻塞的,但是 阻塞不会导致低性能,过多过频繁的阻塞才会导致低性能 。epoll的阻塞和它的高性能并不冲突。

5、为啥Redis网络性能突出?

因为Redis的主要业务逻辑就是在本机内存上的数据结构的读写,几乎没有网络IO和磁盘IO,单个请求处理起来很快。所以它把著服务端程序做成了单进程的,这样省区了多进程之间协作的负担,也更大程度减少了进程切换。进程主要的工作过程就是调用epoll_wait等待事件,有了事件以后处理,处理完之后再调用epoll_wait。一直工作,一直工作,直到没有请求需要处理,或者进程时间片到了时候才让出CPU。工作效率发挥到了极致。

  • 源码分析:
void aeMain(aeEventLoop *eventLoop) {
    while (!eventLoop->stop) {
        //开始处理事件
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}
//file: src/ae.c
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    //等待事件
    numevents = aeApiPoll(eventLoop, tvp);
    for(j = 0; j < numevents; j++) {
        //处理
        aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
        fe->rfileProc()
        fe->wfileProc()
    }
}
/*
aeProcessEvents中通过调用aeApiPoll来等待时间(继续看下去,可以发现aeApiPoll只是一个对epoll_wait的封装) 
*/
//file: src/ae_epoll.c
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    ......
    retval = epoll_wait(state->epfd, state->events, eventLoop->setsize,
            tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
}
//伪码简化
void aeMain(aeEventLoop *eventLoop) {
    job = epoll_wait(...)
    do_job();
}
发表评论
评论通过审核后显示。