fasthttp 是如何啓動 goroutine 來處理請求的

首先,在 fasthttp 中大量的使用了 sync.Pool 來緩存某些對象,已達到對象複用、減小 GC 壓力的目的。 fasthttp 並不像官方 net/http 一樣,創建一個 gorountine 來處理一個 http 請求,而是創建可複用的 gorountine ,並且數量會隨着負載的大小伸縮。

查看自動伸縮如何實現的

fasthttp.ListenAndServe 入手:

func (s *Server) ListenAndServe(addr string) error {
    ln, err := net.Listen("tcp4", addr)  // 創建 Listen 對象用於監聽端口創建tcp連接
    ...
    return s.Serve(ln)
}
...

func (s *Server) serveConn(c net.Conn) (err error) {
    // 初始化 ctx(RequestCtx)
    // 執行請求處理器
}

func (s *Server) Serve(ln net.Listener) error {
    ....
    var c net.Conn
    var err error

    maxWorkersCount := s.getConcurrency()

    ....

    wp := &workerPool{         // 這個就是實現 自動伸縮功能的主體
        WorkerFunc:      s.serveConn,
        MaxWorkersCount: maxWorkersCount,
        LogAllErrors:    s.LogAllErrors,
        Logger:          s.logger(),
        connState:       s.setState,
    }
    wp.Start()   // 初始化 workerPool 並開始 循環清理 workerPool
    ...

    for {
        if c, err = acceptConn(s, ln, &lastPerIPErrorTime); err != nil {   // 監聽 並得到一個TCP鏈接
           ...
        }
        ...

        if !wp.Serve(c) {    // 交由 wp 對象處理
            // 無法處理請求(併發數達到上限)的一些處理代碼
            ...
        }
        c = nil
    }
}

// ---------------------------------------
//                workerpool.go
// ---------------------------------------
...
func (wp *workerPool) Start() {
    if wp.stopCh != nil {
        panic("BUG: workerPool already started")
    }
    wp.stopCh = make(chan struct{})
    stopCh := wp.stopCh
    wp.workerChanPool.New = func() interface{} {
        return &workerChan{
            ch: make(chan net.Conn, workerChanCap),
        }
    }
    // 開啓一個 goroutine 來清理一些空閒的 worker gorountine 和 通道, 對應伸縮中的 縮
    go func() {
        var scratch []*workerChan
        for {
            wp.clean(&scratch)
            select {
            case <-stopCh:
                return
            default:
                time.Sleep(wp.getMaxIdleWorkerDuration())
            }
        }
    }()
}
...
func (wp *workerPool) clean(scratch *[]*workerChan) {
    maxIdleWorkerDuration := wp.getMaxIdleWorkerDuration()

    // Clean least recently used workers if they didn't serve connections
    // for more than maxIdleWorkerDuration.
    criticalTime := time.Now().Add(-maxIdleWorkerDuration)

    wp.lock.Lock()
    ready := wp.ready
    n := len(ready)

    // Use binary-search algorithm to find out the index of the least recently worker which can be cleaned up.
    l, r, mid := 0, n-1, 0
    for l <= r {
        mid = (l + r) / 2
        if criticalTime.After(wp.ready[mid].lastUseTime) {
            l = mid + 1
        } else {
            r = mid - 1
        }
    }
    i := r
    if i == -1 {
        wp.lock.Unlock()
        return
    }

    *scratch = append((*scratch)[:0], ready[:i+1]...)
    m := copy(ready, ready[i+1:])
    for i = m; i < n; i++ {
        ready[i] = nil
    }
    wp.ready = ready[:m]
    wp.lock.Unlock()

    tmp := *scratch
    for i := range tmp {
        tmp[i].ch <- nil                            // 通知 worker goroutine 停止
        tmp[i] = nil
    }
}
...
func (wp *workerPool) Serve(c net.Conn) bool {
    ch := wp.getCh()        
    if ch == nil {
        return false
    }
    ch.ch <- c
    return true
}
...
// 返回一個/創建一個通道(用於向請求處理器 goroutine 傳輸連接對象)對應伸縮中的 伸
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++                                                   // 增加 workers 
        }
    } else {
        ch = ready[n]
        ready[n] = nil
        wp.ready = ready[:n]
    }
    wp.lock.Unlock()

    if ch == nil {
        if !createWorker {                                                               // 超出了最大併發數量
            return nil
        }
        vch := wp.workerChanPool.Get()
        ch = vch.(*workerChan)
        go func() {                                               // 啓動一個 worker goroutine
            wp.workerFunc(ch)                          // 循環處理 通道中的 tcp 連接
            wp.workerChanPool.Put(vch)      // worker goroutine 被清理,回收通道對象方便複用
        }()
    }
    return ch
}

// 記錄當前正在運行的 worker goroutine 的 通道
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
}

// 從通道中獲取 tcp 連接 然後執行請求處理器
func (wp *workerPool) workerFunc(ch *workerChan) {
    var c net.Conn

    var err error
    // 若從通道中取出的對象是 nil 就結束 goroutine,是 TCP 連接就交給 wp.WorkerFunc 處理 (Server.serveConn)
    for c = range ch.ch {
        if c == nil {
            break
        }
        // wp.WorkerFunc 就是 處理器
        if err = wp.WorkerFunc(c); err != nil && err != errHijacked {
            errStr := err.Error()
            if wp.LogAllErrors || !(strings.Contains(errStr, "broken pipe") ||
                strings.Contains(errStr, "reset by peer") ||
                strings.Contains(errStr, "request headers: small read buffer") ||
                strings.Contains(errStr, "unexpected EOF") ||
                strings.Contains(errStr, "i/o timeout")) {
                wp.Logger.Printf("error when serving connection %q<->%q: %s", c.LocalAddr(), c.RemoteAddr(), err)
            }
        }
        if err == errHijacked {
            wp.connState(c, StateHijacked)
        } else {
            _ = c.Close()
            wp.connState(c, StateClosed)
        }
        c = nil

        if !wp.release(ch) {
            break
        }
    }

    wp.lock.Lock()
    wp.workersCount--
    wp.lock.Unlock()
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章