Q&A

  • 为什么创建另一个 http 包,而不是优化 net/http

因为 net/http 的 API 限制了许多可选的优化,例如

  • net/http 请求对象的生命周期被请求 handler 的执行时间限制了,所以 server 每次请求都必须创建一个新的 request 对象,而 fasthttp 选择的重用已经存在的对象
  • net/http 的头部被存储在 map[string][string] 中,所以 server 必须解析所有的头部,将他们从 []byte 转换成 string,然后在调用用户的请求 handler 之前将他们放在 map 中。在 fasthttp 中将所有不必须的内存分配避免掉了。
  • 每次 net/http 每个请求都需要创建一个新的 response 对象
    • 为什么 fasthttp 的 API 与 net/http 不兼容?
  • Comparenet/http connection hijackingtofasthttp connection hijacking.
  • Compare net/http Request.Body reading to fasthttp request body reading.

    Server

    fasthttp 的核心是一个 workerPool,这个 Pool 最多会启动 Server.Concurrency 个 goroutine 去处理连接,相比于 net/http 每一次都使用新的 goroutine 去处理 conn 肯定是高效很多了。
    fasthttp - 图1
    图一:server serve

workerPool 中,最重要的是两个字段,分别是 ready 和 workerChanPool,他们都是存储 workerChan 类型的,这里可以简单的将其看作是一个 channel。每次一个新的请求,都会先从 ready 中尝试获取 channel,如果获取不到,看看 workerCount 是不是有没有超过 Concurrency,然后选择是否从 workerChanPool 中获取新的 workerChan。每次从 workerChanPool 中获取 workerChan,会启动一个 goroutine,处理 channel 中传过来的 net.Conn,每次处理完成之后,都会将 workerChan 放入 ready 中,等待下一个 net.Conn。

到这里能够创建新的 goroutine,但是旧的 goroutine 无法回收,所以需要一个 goroutine 定时清理很久没有使用的 goroutine。这个 goroutine 会在 Start() 中启动。可以看到它会没经过 MaxIdleWorkerDuration 执行一次 clean。

  1. go func() {
  2. var scratch []*workerChan
  3. for {
  4. wp.clean(&scratch)
  5. select {
  6. case <-stopCh:
  7. return
  8. default:
  9. time.Sleep(wp.getMaxIdleWorkerDuration())
  10. }
  11. }
  12. }()

fasthttp - 图2
图二:clean

clean 清理的是 ready 中的 workerChan,这里 ready 本身就是按照 lastUsedTime 排序的,因为每次将 workerChan 放回时都是放在最后,这里直接将前面的元素去除,然后给这些 goroutine 发送关闭信号。