golang网络框架netpoll(Multi-Reactor模型)核心源码分析

Posted 半路出家的后台技术人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang网络框架netpoll(Multi-Reactor模型)核心源码分析相关的知识,希望对你有一定的参考价值。


现如今提起网络大家的第一反应就是epoll,而实际工程开发中绝大部分的情况都会优先考虑采用已有的一些开源网络框架来做功能的开发。网络框架不同的语言有不同的实现,例如java中大名鼎鼎的netty,再比如c++中的libevent、boost::asio、muduo等,golang中目前在开源社区比较有影响力的网络框架有gnet、evio、netpoll(字节开源)这几个。在之前研究完gnet后差不多一年多的时间了,近期机缘巧合又抽空研究了下netpoll的源码实现。在此总结一篇源码分析的文章,方便日后回顾。

netpoll是字节不久前开源的一款golang编写的高性能网络框架(基于Multi-Reactor模型),旨在用于处理rpc场景,详细的介绍可参见下图介绍。

下面将为大家详细分析其内部的源码实现逻辑。其他系列文章参见如下:
1. 网络IO演变过程
2. gnet网络框架源码剖析

1. Reactor模型简介

我们在开始netpoll框架的源码分析前,方便大家阅读源码有一个更好的体验,先简单的回顾下网络编程中的Reacor模型吧。目前很多主流的网络框架都会采用经典的Reactor模型来进行框架内部的实现。而Reactor模型中用的最频繁的就属Multi-Reactor了,最基本的Multi-Reactor模型框架如下图所示。

 

Multi-Reactor模型中根据角色的不同,可以将Reactor分类两类:mainRactorsubReactor。一般mainReactor是一个,而subReactor会有多个。
Multi-Reactor模型的原理如下:
1. mainReactor主要负责接收客户端的连接请求,建立新连接,接收完连接后mainReactor就会按照一定的负载均衡策略分发给其中一个subReactor进行管理。
2. subReactor会将新的客户端连接进行管理,负责后续该客户端的请求处理。
3. 通常Reactor线程主要负责IO的操作(数据读写)、而业务逻辑的处理会由专门的工作线程来执行。

备注:此处所指的Reactor,以epoll为例可以简单理解成一个Reactor对应于一个epoll对象,由一个线程进行处理,Reactor线程又称为IO线程。

简单回顾完Multi-Reactor模型的原理后,下面我们进入正式的主题:netpoll网络框架的源码分析。

2. netpoll整体框架2.1 netpoll client和server端的交互过程

netpoll中对client和server都进行了封装,通过netpoll可以快速的创建一个server端程序。同时可以采用其提供的client方法可以和server进行交互。下面是client和server的一个完整的交互过程。

在netpoll中针对server端,它提供了以下几个方法和回调接口它们的功能分别如下:

Serve():启动服务端,监听等待客户端的请求 OnPrepare():主要做一些初始化、准备的工作,创建连接前回调
OnConnect():在连接创建后回调
OnRequest():业务逻辑方法回调,实现业务逻辑异步处理 

2.2 netpoll server端内部结构

下面这张图侧重于介绍netpoll中server端的核心逻辑,其实现原理和前面介绍的Multi-Reactor模型基本一致,其中Listener、loadbalance、pollmanager、EventLoop等都是netpoll中核心的概念,下面我们再对其做一一介绍。

 

Listener:主要用来初始化Listener,内部调用标准库的net.Listen(),然后再封装了一层。具体的实现则是调用socket()、bind()、listen()等系统调用。
EventLoop:框架对外提供的接口,对外暴露Serve()方法来创建server端程序。
Poll: 是抽象出的一套接口,屏蔽底层不同操作系统平台接口的差异,linux下采用epoll来实现、bsd平台下则采用kqueue来实现。
pollmanager:Poll的管理器,可以理解成一个Poll池,也就是一组epoll或者kqueue集合。
loadbalance:负责均衡封装,主要用来从pollmanager按照一定的策略(随机、轮询、最小连接等)选择出来一个Poll实例,一般在客户端初始化完成后,server会调用该接口拿到一个Poll实例,并将新建立的客户端加入到Poll管理。 

3. netpoll Server端源码分析3.1 server 使用示例

下面是netpoll的一个简单使用示例,从中我们可以看到它对外暴露的api的使用姿势。

      listeneraddress      eventLoopconnection Connection       timetime       l connectionl            dataerr connection           err                err
fmt eventLoopEventLoop OnRequest connection Connectionops opts onRequestonRequest do ops do optsopts stop eventLoop sync svr opts stop nplnerr err err
evl evlevlevl evl evl err evl runtime err
opts onQuit ln ln opts opts onQuitonQuit server operator FDOperator
ln Listener
opts onQuit connections sync sFDOperator FD s OnReads OnHup s spollmanager err s err s err
connerr s err strings s s err
logerr err
conn connection connections fd conn connection s sconnection connection spollmanager err s connectionsopts c c ccblock1kpagesize
cc ccbarrierPoolbarrierPool c c c syscall c defaultZeroCopyTimeoutSec c c nfdok connok c cnetFD fd conn localAddr conn remoteAddrconn op opc opopopc opopcc opopcc cc opc cop
opts c c c c opts copts c ccontext c c c err c cpollmanager err c err logerr c err pollmanager pollmanagerpollmanager pollmanager pollmanager pollmanager procs runtime loops procs loops procs
loops
manager NumLoops balance loadbalance polls numLoops fmtnumLoops numLoops m mnumLoops
m mnumLoops
m mmlb mm idx idx midx poll mpoll poll m Poll mPoll event PollEventPollEvent PollReadable PollEvent PollWritable PollEvent PollDetach PollEvent PollModReadable PollEvent PollR2RW PollEvent PollRW2R PollEvent EPOLLET epollevent events data op fd event err syscall err syscall err err
events msec err r0 _p0 unsafe msec r0err syscall r0err syscall err syscall err err
Poll poll defaultPoll poll perr syscall err pollp
r0e0 syscall e0 syscall pollpoll pollpoll poll pollPollReadable defaultPoll pollArgs
fd wop buf trigger Reset caps Handler pollArgs size caps events barriers caps aasizecaps
aasizesize i a aa aa capsmsecn barriercap pcaps n pp pcaps nerr pmsec err err syscall err
n msec runtime msec p hups i events operator operatorp syscallp atomic p syscall syscall evt events evt hups operator evt err syscallsyscallerr syscall hups operator evt operator operator bs operator nerr bsp operator err err syscallerr syscall logoperatorerr hups operator evt operator operator bssupportZeroCopy operator nerr bspsupportZeroCopy operator err err syscall logoperatorerr hups operator operator p err syscall err
atomic err syscall err
event PollEvent op evt epollevent
operator
event PollReadable operator opevtsyscallsyscall PollModReadable operator opevtsyscallsyscall PollDetach operator opevtsyscallsyscall PollWritable operator opevtsyscallEPOLLET PollR2RW opevtsyscallsyscall PollRW2R opevtsyscallsyscall opoperator onhups i hups onhupshups pPollDetach i onhups onhups onhups n n n ccmallocMax c lengthc clength clength
cmallocMax cmallocMax
needTrigger length n needTrigger c needTrigger length c onRequestok c processed c cc c process process task START c c c START
task runTask gopool c c cerr c dstc n p c err c nerr
c bs c nerr bscc err err syscall n err c c err c err c err err err
Dialer address timeout timeerr address timeout timeerr address timeout timeerr defaultDialeraddresstimeoutDialer defaultDialer dialer address timeout time connerr daddresstimeout connerr
address timeout timeerr ctx context timeout subCtxcancel contexttimeout ctx subCtx
network raddr raddrerr address err err
connectionerr networkraddr raddr raddrerr address err err
connectionerr raddr net connectionerr
sysDialer net networkaddress TCPConnection connection
err connection err connection err err
connectionnetwork laddrraddr network NetnetworkSourceladdrAddrraddrErrnet raddr NetnetworkSourceladdrAddrErrerrMissingAddress ctx ctx context sd networkaddressraddr cerr sdladdrraddr err NetnetworkSourceladdrAddrraddrErrerr claddrraddr connerr sdladdrraddrsyscall i i laddrerri err conn connerr sdladdrraddrsyscall err err
net laddrraddr sockaddrsotypeproto mode err runtimeruntimeruntimeraddr raddr raddr familyipv6only laddrraddr netfamilysotypeprotoipv6onlyladdrraddrnet familysotypeproto ipv6only laddrraddr sockaddrerr fd fderr sotypeproto err err
err familysotypeipv6only err syscall err
netfd familysotypenet err netfdladdrraddr err netfd err
netfdsotypeproto syscall serr syscallsotypeproto err syscall syscall err oserr err syscallerr syscall oserr sfamilysotype net ret retfd
retnet
retfamily
retsotype
retsotype syscall retsotype syscallsotype syscall ret
laddrraddr sockaddr lsa syscall laddr lsaerr laddrerr err
lsa err syscalllsaerr oserr rsa syscall crsa syscall raddr rsaerr raddrerr err
crsaerr clsarsaerr err
c lsasyscall c crsa c crsasyscallcrsa c c lara syscallret err syscallraerr syscallsyscallsyscall syscall syscall runtime oserr ctx context done interruptRes ctxErr ctxErr ret ret c c interruptRes ctx interruptRes c err cerr err
nerrerr syscallsyscallsyscall err oserr err syscallerr syscallsyscallsyscall syscall syscall rsaerr syscallerr rsa oserr

上面是创建链接的核心实现逻辑,从中我们可以看到,最关键的也就是两步:
1. 调用sysSocket()方法,该方法内部通过syscall.Socket()系统调用初始化一个socket描述。
2. 调用netFD的dial()方法,该方法内部主要调用syscall.Connect()方法进行建立链接
3. 获得netFD后,最后再通过newTCPConnection()方法初始化connection信息,这部分逻辑也就和server端建立连接后调用init()初始化的过程是相同的。 

5. 总结

本文主要回顾了网络编程中经典的Multi-Reactor模型,并在此基础上分析了golang网络框架netpoll的server和client核心源码实现逻辑。再源码中主要关注了网络处理的核心逻辑实现。此外由于篇幅有限,本文并未对netpoll中采用的零拷贝、输出输出缓冲区等内容进行分析。读者感兴趣的话可以自行查看源码进行阅读。文章有理解不恰当地方还请大家指正。

6. 参考资料
  1. https://github.com/cloudwego/netpoll.git

  2. https://github.com/cloudwego/kitex.git

  3. https://github.com/panjf2000/gnet.git

  4. 网络IO演变过程

  5. gnet网络框架源码剖析

Golang 网络爬虫框架gocolly/colly 一

Golang 网络爬虫框架gocolly/colly

gocolly是用go实现的网络爬虫框架,目前在github上具有3400+星,名列go版爬虫程序榜首。gocolly快速优雅,在单核上每秒可以发起1K以上请求;以回调函数的形式提供了一组接口,可以实现任意类型的爬虫;依赖goquery库可以像jquery一样选择web元素。

gocolly的官方网站是http://go-colly.org/,提供了详细的文档和示例代码。安装colly:

 

go get -u github.com/gocolly/colly/...

  

 

在代码中导入包:

import "github.com/gocolly/colly"

  

colly的主体是Collector对象,管理网络通信和负责在作业运行时执行附加的回掉函数。使用colly需要先初始化Collector

c := colly.NewCollector()

   

可以向colly附加各种不同类型的回掉函数,来控制收集作业或获取信息。增加回掉函数:

 

c.OnRequest(func(r *colly.Request) {

    fmt.Println("Visiting", r.URL)

})

c.OnError(func(_ *colly.Response, err error) {

    log.Println("Something went wrong:", err)

})

c.OnResponse(func(r *colly.Response) {

    fmt.Println("Visited", r.URL)

})

c.OnHTML("a[href]", func(e *colly.HTMLElement) {

    e.Request.Visit(e.Attr("href"))

})

c.OnHTML("tr td:nth-of-type(1)", func(e *colly.HTMLElement) {

    fmt.Println("First column of a table row:", e.Text)

})

c.OnScraped(func(r *colly.Response) {

    fmt.Println("Finished", r.URL)

})

  

 

回掉函数的调用顺序如下:

1. OnRequest

在发起请求前被调用

2. OnError

请求过程中如果发生错误被调用

3. OnResponse

收到回复后被调用

4. OnHTML

OnResponse之后被调用,如果收到的内容是HTML

5. OnScraped

OnHTML之后被调用

 

官方提供的Basic示例代码:

 

package main

 

import (

    "fmt"

 

    "github.com/gocolly/colly"

)

 

func main() {

    // Instantiate default collector

    c := colly.NewCollector()

 

    // Visit only domains: hackerspaces.org, wiki.hackerspaces.org

    c.AllowedDomains = []string{"hackerspaces.org", "wiki.hackerspaces.org"}

 

    // On every a element which has href attribute call callback

    c.OnHTML("a[href]", func(e *colly.HTMLElement) {

        link := e.Attr("href")

        // Print link

        fmt.Printf("Link found: %q -> %s\n", e.Text, link)

        // Visit link found on page

        // Only those links are visited which are in AllowedDomains

        c.Visit(e.Request.AbsoluteURL(link))

    })

 

    // Before making a request print "Visiting ..."

    c.OnRequest(func(r *colly.Request) {

        fmt.Println("Visiting", r.URL.String())

    })

 

    // Start scraping on https://hackerspaces.org

    c.Visit("https://hackerspaces.org/")

}

  

 

 

该实例程序仅访问hackerspaces.org域内的链接,OnHTML回掉函数的选择器为a[href],选择页面内具有href属性的a类型元素,找到链接后继续抓取。 运行的部分结果如下:

PS E:\mygo\src\github.com\gocolly\colly\_examples\basic> .\basic.exe

Visiting https://hackerspaces.org/

Link found: "navigation" -> #column-one

Link found: "search" -> #searchInput

Link found: "" -> /File:Cbase07.jpg

Visiting https://hackerspaces.org/File:Cbase07.jpg

Link found: "navigation" -> #column-one

Link found: "search" -> #searchInput

Link found: "File" -> #file

Link found: "File history" -> #filehistory

Link found: "File usage" -> #filelinks

Link found: "" -> /images/e/ec/Cbase07.jpg

Visiting https://hackerspaces.org/images/e/ec/Cbase07.jpg

Link found: "800 × 600 pixels" -> /images/thumb/e/ec/Cbase07.jpg/800px-Cbase07.jpg

Visiting https://hackerspaces.org/images/thumb/e/ec/Cbase07.jpg/800px-Cbase07.jpg

 

以上是关于golang网络框架netpoll(Multi-Reactor模型)核心源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Golang 网络爬虫框架gocolly/colly 一

Golang 网络爬虫框架gocolly/colly 二 jQuery selector

试验一下Golang 网络爬虫框架gocolly/colly

字节跳动自研的 Go RPC 框架 KiteX 核心技术讲解

Golang学习-第三篇 认识Web框架

分布式事务框架 seata-golang 通信模型详解