首先,在 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()
}