golang的net/http包实现了处理连接时比较简单粗暴,相比之下有性能更好的http库,项目里用到过的fasthttp就是一个很好的实现,Go开发HTTP的另一个选择fasthttp中总结了它跟标准库实现的几点不同:
net/http
的实现是一个连接新建一个 goroutine,fasthttp
是利用一个 worker pool做了协程池,复用 goroutine,减轻 runtime 调度 goroutine 的压力net/http
解析的请求数据很多放在http.Header或者http.Request.Form中,数据结构map[string]string
或map[string][]string
涉及不必要的 []byte 到 string 的转换,是可以规避的net/http
解析 HTTP 请求每次生成新的*http.Request
和http.ResponseWriter
,fasthttp
解析 HTTP 数据到*fasthttp.RequestCtx
,然后使用sync.Pool
复用结构实例,减少对象的数量fasthttp
会延迟解析 HTTP 请求中的数据,尤其是 Body 部分。这样节省了很多不直接操作 Body 的情况的消耗
workerpool
net包和fasthttp最大的不同可能就是server在处理连接的时候使用了协程池。在并发量大的时候,goroutine数量巨大,runtime层的上下文切换成本对性能有影响。而fasthttp用协程池规避了这个问题,去年在做AOS的时候,项目中后期也引入了workerpool。
1 | func (s *Server) Serve(ln net.Listener) error { |
workerpool的数据结构中WorkerFunc就是 s.serveConn
,即每条net.conn的处理函数。workerChanPool是个对象池,MaxIdleWorkerDuration是worker的最大空闲时间,ready是可用的worker列表,也就是说所有goroutine worker是存放在一个数组里面的。
这个数组模拟一个类似栈的FILO队列,也就是说我们每次使用的worker都从队列的尾部开始取。
1 | type workerPool struct { |
start & stop
wp.start开启了一个goroutine,定时清理workerpool中未使用时间超过maxIdleWorkerDuration的goroutine。
1 | func (wp *workerPool) Start() { |
stop停止了ready里所有ch清空,并清空ready。资源清理时chan要置nil。
1 | func (wp *workerPool) Stop() { |
serve
实现中还涉及到如果wp已经stop,那worker退出后channel对象通过临时对象池管理等细节,这里就跳过了。总结 wp.serve到s.serveconn的过程大概如下
- 当ready这个可用worker列表中没有ch可用时,创建一个新ch绑定
wp.Workfunc
的goroutine。即新建了一个协程worker,这个协程从绑定的ch中获取待处理net.Conn。 - wp.Serve把accept的conn发到这个ch上,供绑定的协程worker处理。
- worker处理完后
release
这个绑定的ch到ready栈里。下一次有连接来时getCh优先从ready栈里找ch,也就是找worker。对ready的读取FILO,类似栈。
1 | func (wp *workerPool) Serve(c net.Conn) bool { |
getCh的实现可以理解为一个用来执行workFunc的goroutine都绑定了一个workerChan。把要处理的conn发到这个workerChan,这个goroutine就开始执行。没有要执行的conn则goroutine阻塞,直到下次workerChan有连接发来。
1 | func (wp *workerPool) getCh() *workerChan { |
worker处理完一个连接后,将release这个连接到ready这个可用worker栈。即表示这时worker阻塞,可以交给它任务啦。同时处理完net.Conn后要置nil。正常情况下worker是不退出的,除非wp.Stop。
1 | func (wp *workerPool) workerFunc(ch *workerChan) { |
clean
最后看下start中开启的clean定时任务。之所以清理过程只从前遍历清理前面部分,是因为ready是FILO先进后出的,所以ready中越往后的空闲时间最短。
1 | func (wp *workerPool) clean(scratch *[]*workerChan) { |