次要针对字节跳动的netpoll网络库进行总结。netpoll网络库相比于go自身的net规范库更适宜高并发场景。
基础知识
netpoll与go.net库一样应用epoll这种IO多路复用机制解决网络申请。
根本了解
咱们晓得linux万物皆文件,每个文件有个文件标识符fd,咱们能够设想linux提供给咱们的socket fd就是操作系统将传输层及以下的协定进行封装抽象化的一个接口。咱们能够简略把socket了解成对应的一次tcp连贯。 那么网络操作基本上也是针对网卡的IO操作,咱们须要读取数据/写入数据,那么如何更加高效地解决数据呢?目前大多数网络库都应用IO多路复用机制,在linux零碎中最先进的io多路复用就是epoll机制。
epoll工作形式
- 事件告诉机制
- epoll_ctl/epoll_wait
- ET(边缘触发)/LT(程度触发)
事件告诉机制
- 注册事件:epoll须要注册一些可读的事件
- 监听事件:监听到可读的数据
- 触发事件:告诉数据可读
次要还是有两个零碎调用:
- epoll_ctl
- epoll_wait
工作模式
epoll有两种触发工作模式:ET和LT
- ET也叫边缘触发,注册的事件满足条件之后,epoll只会触发一次告诉。就算你这一次的读写事件的数据没有解决完,下一次epoll_wait也不会再触发告诉。
- LT也叫程度触发,注册的事件满足条件之后,不论数据是否读写实现,每一次epoll_wait都会告诉以后监听的fd事件。
BIO/NIO
- BIO:blocking I/O,阻塞I/O。就是当咱们向一个socket发动read的时候,数据读取实现之前始终是阻塞的。
- NIO:nonblocking I/O,非阻塞I/O。就是read数据的时候不阻塞,立刻返回。
那么咱们每次发现socket中有可读数据的时候,咱们就会开启一个goroutine读取数据。
Netpoll的优化点
go的net库是BIO的,节约了更多的goroutine在阻塞,并且难以对连接池中的连贯进行探活。 netpoll采纳了LT的触发形式,这种触发形式也就导致编程思路的不同
ET
LT
netpoll采纳LT的编程思路 因为netpoll想在 零碎调用 和 buffer 上做优化,所以采纳LT的模式。
优化零碎调用
syscall这个办法其实有三步:
- enter_runtime
- raw_syscall
- exit_runtime
因为零碎调用是一个耗时的阻塞操作,容易造成goroutine阻塞,所以须要退出一些runtime的调度流程。 然而,epoll_wait触发的事件,保障不会被阻塞,所以netpoll间接采纳RawSyscall办法做零碎调用,跳过了runtime的一些逻辑。
优化调度
应用msec动静调参和runtime.Gosched被动让出P
msec动静调参
epoll_wait的零碎调用有个参数是,等待时间,设置成-1是有限期待事件到来,0是不期待。
这样就有事件到来的时候下次循环的epoll_wait采纳立刻返回,没有事件就始终阻塞,缩小重复无用的调用。
runtime.Gosched被动让出P
如果msec为-1的话会立刻进入下一次循环,开启新的epoll_wait调用,那么调用就阻塞在这里,goroutine阻塞工夫长了之后会被runtime切换掉,只能等到下一次执行这个goroutine才行,导致工夫节约。 netpoll调用runtime.Gosched办法被动将GMP中的P让出,缩小runtime的调度过程。
优化buffer
咱们在读取和写入数据的时候须要应用到buffer。 少数框架应用环形buffer,能够做到流式读写。然而环形buffer容量是死的,须要扩容的话,须要从新copy数组,引入了很多的并发问题。
LinkBuffer
netpoll应用的buffer实现包含:
- 链表解决扩容copy问题
- sync.Pool复用链表节点
- atomic拜访size,躲避data race和锁竞争
还有一些nocopy方面的优化,缩小了write和read的次数,从而进步了读取和发送的时候的编解码效率。
更多信息看:https://www.cloudwego.io/zh/b…