这是我参与「第五届青训营 」笔记创作活动的第15天
netpoll
在青训营写项目的时候用到了Hertz和Kitex两个框架,正好昨天也学习到了网络IO相关的内容,结合昨天所学,来看看这两个框架共同用到的网络库Netpoll
官方介绍:
Netpoll 是由 字节跳动 开发的高性能 NIO(Non-blocking I/O) 网络库,专注于 RPC 场景。
RPC 通常有较重的处理逻辑,因此无法串行处理 I/O。而 Go 的标准库 net 设计了 BIO(Blocking I/O) 模式的 API,使得 RPC 框架设计上只能为每个连接都分配一个 goroutine。 这在高并发下,会产生大量的 goroutine,大幅增加调度开销。此外,net.Conn 没有提供检查连接活性的 API,因此 RPC 框架很难设计出高效的连接池,池中的失效连接无法及时清理。
另一方面,开源社区目前缺少专注于 RPC 方案的 Go 网络库。类似的项目如:evio , gnet 等,均面向 Redis, HAProxy 这样的场景。
因此 Netpoll 应运而生,它借鉴了 evio 和 netty 的优秀设计,具有出色的 性能,更适用于微服务架构。 同时,Netpoll 还提供了一些 特性,推荐在 RPC 设计中替代 net 。
基于 Netpoll 开发的 RPC 框架 Kitex 和 HTTP 框架 Hertz,性能均业界领先。
Server
package main
import "github.com/cloudwego/netpoll"
func main() {
listener, err := netpoll.CreateListener(network, address)
if err != nil {
panic("create netpoll listener failed")
}
eventLoop, _ = netpoll.NewEventLoop(
handle,
netpoll.WithOnPrepare(prepare),
netpoll.WithReadTimeout(time.Second),
)
// start listen loop ...
eventLoop.Serve(listener)
}
以上是范例给出的启动一个服务端的简单代码
主要分为三步
- 创建Listener
- 创建EventLoop
- 运行
可以看出,该服务端由Listener和EventLoop组成,我们来看看它们到底是什么,分别起什么样的作用
Listener
我们知道net包中,Listener是一组接口
type Listener interface {
// Accept waits for and returns the next connection to the listener.
Accept() (Conn, error)
// Close closes the listener.
// Any blocked Accept operations will be unblocked and return errors.
Close() error
// Addr returns the listener's network address.
Addr() Addr
}
而netpoll中的listener实现了这些接口,并且支持udp和tcp,我们实际上使用的是这样的结构体:
var _ net.Listener = &listener{}
type listener struct {
fd int
addr net.Addr // listener's local addr
ln net.Listener // tcp|unix listener
pconn net.PacketConn // udp listener
file *os.File
}
具体接口的实现这里不细谈,主要是分别根据udp和tcp的类型还有地址转换为上面的listener结构体,转换的最后一步都是将socket设置为非阻塞模式
syscall.SetNonblock(ln.fd, true)
EventLoop
type eventLoop struct {
sync.Mutex
opts *options
svr *server
stop chan error
}
EventLoop结构体如上 创建EventLoop时还没有创建server
func NewEventLoop(onRequest OnRequest, ops ...Option) (EventLoop, error) {
opts := &options{
onRequest: onRequest,
}
for _, do := range ops {
do.f(opts)
}
return &eventLoop{
opts: opts,
stop: make(chan error, 1),
}, nil
}
其中options主要用于接收参数,包括函数
type options struct {
onPrepare OnPrepare
onConnect OnConnect
onRequest OnRequest
readTimeout time.Duration
writeTimeout time.Duration
idleTimeout time.Duration
}
直到EventLoop调用Serve()时,才创建server结构体,并调用其Run()
server结构体如下,其与IO相关的主要部分为FDOperator
type server struct {
operator FDOperator
ln Listener
opts *options
onQuit func(err error)
connections sync.Map // key=fd, value=connection
}
server调用run时会从多个poll中选取一个
// Run this server.
func (s *server) Run() (err error) {
s.operator = FDOperator{
FD: s.ln.Fd(),
OnRead: s.OnRead,
OnHup: s.OnHup,
}
s.operator.poll = pollmanager.Pick()
err = s.operator.Control(PollReadable)
if err != nil {
s.onQuit(err)
}
return err
}
manager选取时考虑负载均衡,balance提供了Random和RoundRobin两种选取算法
type manager struct {
NumLoops int
balance loadbalance // load balancing method
polls []Poll // all the polls
}
poll的linux下的默认实现结构体如下
type defaultPoll struct {
pollArgs
fd int // epoll fd
wop *FDOperator // eventfd, wake epoll_wait
buf []byte // read wfd trigger msg
trigger uint32 // trigger flag
m sync.Map // only used in go:race
opcache *operatorCache // operator cache
// fns for handle events
Reset func(size, caps int)
Handler func(events []epollevent) (closed bool)
}
获取到可用的poll之后会向系统调用注册epoll事件
// Control implements Poll.
func (p *defaultPoll) Control(operator *FDOperator, event PollEvent) error {
var op int
var evt epollevent
p.setOperator(unsafe.Pointer(&evt.data), operator)
switch event {
case PollReadable: // server accept a new connection and wait read
operator.inuse()
op, evt.events = syscall.EPOLL_CTL_ADD, syscall.EPOLLIN|syscall.EPOLLRDHUP|syscall.EPOLLERR
...
}
return EpollCtl(p.fd, op, operator.FD, &evt)
}
// EpollCtl implements epoll_ctl.
func EpollCtl(epfd int, op int, fd int, event *epollevent) (err error) {
_, _, err = syscall.RawSyscall6(syscall.SYS_EPOLL_CTL, uintptr(epfd), uintptr(op), uintptr(fd), uintptr(unsafe.Pointer(event)), 0, 0)
if err == syscall.Errno(0) {
err = nil
}
return err
}
暂时还没有找到EpollWait在何处被调用,今天的学习笔记暂且记录到这里,明天继续!