• 应对百万访问量的epoll模式


    写在前面

    上一篇文章并发模型:Actors与CSP简单介绍了Actors和CSP两种并发模型。如果认真推敲会发现,无论是Actors还是CSP,直观上来说其实都是内存模型,那么高并发的CPU模型是怎么样的呢?

    或者说:只有8颗CPU核的一台主机,同一时间至多运行8个线程,如何实现一秒时间内对上万个请求的响应?

    select/poll与epoll

    select/poll模型工作机理

    在epoll之前,存在两种高并发模型:select和poll,大体的步骤是:

    # select和poll模式会专门对下面的连接链表进行轮询,查看那个连接上有请求
    head->connetion1->connection2->connection3->connection4->...
    
    1. 创建连接链表。简单讲,同一时间来了一万连接请求,给用户A发来的连接请求创建一个专门的connection1,用户B发来的连接请求创建一个专门的connection2;给用户C发来的……
    2. 遍历步骤1生成的连接链表,从connection1一个个地看直到connection10000,查看哪个连接(connection)上面用户发了新的请求,如果有发现新的请求,则想办法通知负责当前连接的进程(比如我们自己的服务)去响应,然后继续遍历。
    3. 终于遍历到了最后一个连接,继续从头开始遍历。

    select/poll模型的局限

    从上面的描述可以知道,select/poll模型里面存在一个遍历查找过程。当链表的长度较短,且每个连接(connection)上的请求很频繁时,select/poll的模型工作的很好;但是一旦连接数增加,select/poll模式遍历查找的过程会消耗大量的CPU时间,而且连接数越多情况越恶化,因此限制了这种模式在高访问量场景下的使用。

    epoll模型工作机理

    既然遍历连接(可以看做一小块内存,是文件描述符的一种)限制了select/poll模型的天花板,那么能不能不要再让CPU遍历那么多连接了。

    linux说:可以。

    # 连接的列表,每个连接存在一个唯一的id
    [0]connection0 | [1]connection1 | [2]connection2 | ...
    
    # 发现connection1和connection10有请求
    # 把它们加入到一个特殊的链表
    head->connection1->connection10
    

    大体的步骤如下:

    1. 创建连接数组列表。同一时间来了一万连接请求,给用户A发来的连接请求创建一个专门的connection0,ID为0;用户B发来的连接请求创建一个专门的connection1,ID为1;给用户C发来的……
    2. linux内核和网卡驱动的约定:当某个连接上有新的请求时,网卡驱动把请求的内容和对应的连接ID一起发给内核。
    3. linux内核拿到了带连接ID的请求,找到对应的connectionID并把它加入到一个特殊链表。
    4. 遍历这个特殊的链表,想办法通知负责当前连接的进程(比如我们自己的服务)去响应,然后继续遍历

    注意第4步,因为linux内核已经把存在实际请求的连接拣出来了,因此不存在徒劳功,老老实实处理请求就好了。

    epoll的局限

    像上面所描述的,epoll杜绝了无意义的遍历,因此在高访问量场景中有很大的发挥空间。但是不能不说,一切都是基于web请求计算量低请求低频的场景。

    试想,对于epoll中的connection,如果网卡突然对linux内核说:哥,现在所有的连接都有请求。那么特殊链表里其实就是所有的连接实例了,这种场景下epoll反而不如select/poll模式,毕竟后者步骤少啊。

    幸运的是,我们所说的百万访问量,都是人发起的,很契合epoll的使用场景。

    golang中的epoll

    参见 大专栏  应对百万访问量的epoll模式golang源码src/runtime/proc.go文件,其在main函数启动时,既开始在系统栈开始运行sysmon函数。

    func main() {
    //...
    	systemstack(func() {
    		newm(sysmon, nil)
    	})
    //...
    }
    

    golang源码中的sysmon函数

    通过查看sysmon函数可以知道,这个函数主要的是一个无穷的for循环,负责调整时序、GC(垃圾回收)以及epoll检查等。

    // sysmon
    // Go runtime启动时创建的,负责监控所有goroutine的状态判断是否需要GC,
    // 进行netpoll等操作。sysmon函数中会调用retake函数进行抢占式调度
    func sysmon() {
    //...
      for {
    		if netpollinited() && lastpoll != 0 && lastpoll+10*1000*1000 < now {
    			//更新最后一次查询G时间,为了下一次做判断
    			atomic.Cas64(&sched.lastpoll, uint64(lastpoll), uint64(now))
    			// 从网络I/O查找已经就绪的G,不阻塞
    			gp := netpoll(false) // non-blocking - returns list of goroutines
    			//...
    		}
      }
    }
    

    进一步查看netpoll函数,能发现主要有下面几个函数:

    1. epollcreate/epollcreate1 创建epoll
    2. epollctl 设置epoll事件
    3. epollwait等待epoll事件

    到这里,golang与epoll就算对接上了。因为时间问题,细节暂时就不展开了,大家感兴趣可以自己探索。

    小结

    本文简单介绍了epoll模型。直观上来讲,并发模型中的Actors模型、CSP模型等,侧重的是内存的分配与信号的管理;但是,如何能充分发挥这些并发模型的优势,满足高并发的真实场景呢?

    答案就是epoll模型。相比较于传统的select/poll模型,epoll能更充分地利用cpu的时间,把性能投入到有效的运算中去。

    参考

  • 相关阅读:
    comet技术
    OCP-1Z0-052-V8.02-120题
    OCP-1Z0-052-V8.02-121题
    OCP-1Z0-052-V8.02-122题
    OCP-1Z0-052-V8.02-124题
    OCP-1Z0-052-V8.02-125题
    OCP-1Z0-052-V8.02-126题
    OCP-1Z0-052-V8.02-127题
    RMAN 备份脚本
    Oracle DB 性能视图和数据字典
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12037680.html
Copyright © 2020-2023  润新知