FastHTTP源碼分析——「百花齊放」的協程池

原文:FastHTTP源碼分析——「百花齊放」的協程池git

聲明

    閱讀本編文章須要go語言基礎和對資源池有一些瞭解。github

    go 版本爲1.11,FastHTTP爲2018-11-23的最新master版本數組

前言

    在開始前咱們先來簡單定義一下協程池:可以達到協程資源複用。在這個定義下協程池的實現能夠說是「百花齊放」了,找一下熱門的go語言開源項目都會有協程池的不一樣實現方式。 有基於鏈表實現的Tidb,有基於環形隊列實現的Jaeger,有基於數組棧實現的FastHTTP等,種類繁多任君選擇。這麼多的協程池實現能夠概括成二種:app

  1. 提早建立協程:JaegerIstioTars等。
  2. 按需建立協程:TidbFastHTTPAnts等。

    這2種實現中,我的比較喜歡第二種按需建立,FastHTTP也是使用第二種方式,因此咱們來看看它是如何實現的。源碼分析

FastHTTP協程池簡介

    在介紹FastHTTP協程池以前先作一下簡單的介紹。workerChan和協程一一對應,相同的生命週期,能夠把workerChan當作是協程的門牌,使用憑證,引路子等。 整個協程池的實現主要由workerPoolworkerChan組成。性能

  1. 請求進來建立協程
  2. 請求處理完成,把協程的workerChan放入workerPool.ready
  3. 再有請求進來,從workerPool.ready獲取workerChan,處理請求。
  4. 從第2步開始不斷重複

協程池用在哪裏

  • go官方原生 http.Server
net/http/server.go #2805
func (srv *Server) Serve(l net.Listener) error {
    ......
    for {
      rw, e := l.Accept()
      ......
      //FastHTTP在這步使用協程池
      go c.serve(ctx)
    }
}
  • FastHTTP的 fasthttp.ListenAndServe
github.com/valyala/fasthttp/server.go 1489
func (s *Server) Serve(ln net.Listener) error {
    ......
    for {
      if c, err = acceptConn(s, ln, &lastPerIPErrorTime); err != nil {
          ......
      }
      //對應go原生的 go c.serve(ctx)
      if !wp.Serve(c) {
          ......
      }
      ......
    }
}
    在go原生的 http.Server包中,當接收到新請求就會啓動一個協程處理,而FastHTTP則使用協程池處理。

獲取workerChan

github.com/valyala/fasthttp/workerpool.go #156
func (wp *workerPool) getCh() *workerChan {
  var ch *workerChan
  createWorker := false

  wp.lock.Lock()
  ready := wp.ready
  n := len(ready) - 1
  if n < 0 {
    if wp.workersCount < wp.MaxWorkersCount {
      createWorker = true
      wp.workersCount++
    }
  } else {
    //從尾部獲取Ch
    ch = ready[n]
    ready[n] = nil
    wp.ready = ready[:n]
  }
  wp.lock.Unlock()

  if ch == nil {
    //若是協程數超過上限,直接拋棄當前請求
    if !createWorker {
      return nil
    }
    vch := wp.workerChanPool.Get()
    if vch == nil {
      vch = &workerChan{
        ch: make(chan chan struct{}, workerChanCap),
      }
    }
    ch = vch.(*workerChan)
    //ch和協程綁定
    go func() {
      wp.workerFunc(ch)
      wp.workerChanPool.Put(vch)
    }()
  }
  return ch
}
    在go語言中不一樣協程之間的通信使用 channel ,在協程池中也不例外,FastHTTP建立了一個協程,就會和一個 workerChan綁定,使用方根據這個 workerChan 就可使用協程池裏的資源。從上面的代碼能夠看出,使用協程池的資源,都是先從Slice的尾部彈出 workerChan ,在把 workerChan 交給使用放,若是Slice沒有 workerChan 就會建立。

把workerChan放入Slice尾部

github.com/valyala/fasthttp/workerpool.go #194
func (wp *workerPool) release(ch *workerChan) bool {
  //用戶清理
  ch.lastUseTime = time.Now()
  wp.lock.Lock()
  if wp.mustStop {
    wp.lock.Unlock()
    return false
  }
  //往尾部追加
  wp.ready = append(wp.ready, ch)
  wp.lock.Unlock()
  return true
}
    當協程完成工做後,就會把 workerChan放回Slice尾部,以待其餘請求使用。

按期清理過時workerChan

github.com/valyala/fasthttp/workerpool.go #98
func (wp *workerPool) clean(scratch *[]*workerChan) {
  ......
  currentTime := time.Now()

  wp.lock.Lock()
  ready := wp.ready
  n := len(ready)
  i := 0
  for i < n && currentTime.Sub(ready[i].lastUseTime) > maxIdleWorkerDuration {
      i++
  }
  *scratch = append((*scratch)[:0], ready[:i]...)
  if i > 0 {
    m := copy(ready, ready[i:])
    for i = m; i < n; i++ {
      ready[i] = nil
    }
    wp.ready = ready[:m]
  }
  wp.lock.Unlock()

  ......
  tmp := *scratch
  for i, ch := range tmp {
    //讓協程中止工做
    ch.ch <- nil
    tmp[i] = nil
  }
}
    按期清理是爲了不在常態下空閒的協程過多,加劇了調度層的負擔。使用按需建立協程池的方式存在這樣一個問題,高峯期的時候建立了不少協程,高峯期事後不少協程處於空閒狀態,這就形成了沒必要要的開銷。因此須要一種過時機制。在這裏數組棧(FILO)的優勢也體現出來了,由於棧的特色不活躍的 workerChan都放在了數組的頭部,因此只須要從數組頭部開始輪詢,一直到找到未過時的 workerChan,再把這部分清理掉,就達到清理的效果,而且不須要輪詢整個數組。

收益有多少

    花了點時間對FastHTTP的協程池進行了壓測 代碼
apple:gopool apple$ go test -bench=. -test.benchmem
goos: darwin
goarch: amd64
pkg: study_go/gopool
BenchmarkNotPool-4                  10    4937881320 ns/op    107818560 B/op      401680 allocs/op
BenchmarkFastHttpPool-4             10     380807481 ns/op    13444607 B/op      169946 allocs/op
BenchmarkAntsPoll-4                 10     429482715 ns/op    20756724 B/op      302093 allocs/op
PASS
ok      study_go/gopool    72.891s
從上面的對比來看使用協程池的收益還很多。

結語

     FastHTTP協程池的實現方式是我所瞭解的幾種實現中,性能是比較突出的,固然其餘協程池的實現方式也頗有學習參考價值,在這個過程當中複習了鏈表,數組棧,環形隊列的使用場景。收穫頗多。
相關文章
相關標籤/搜索