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()
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章