欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺析Go中fasthttp與net/http的性能對比及應(yīng)用

 更新時(shí)間:2024年03月25日 11:16:02   作者:小許code  
這篇文章主要為大家詳細(xì)介紹了Golang中fasthttp的底層實(shí)現(xiàn)以及與net/http的區(qū)別,下面就跟隨小編一起來看看fasthttp到底是如何做到性能如此之快的吧

處理流程對比

在進(jìn)行了解fasthttp底層代碼實(shí)現(xiàn)之前,我們先對兩者處理請求的方式進(jìn)行一個(gè)回顧和對比,了解完兩者的基本的情況之后,再對fasthttp的實(shí)現(xiàn)最進(jìn)一步分析。

net/http處理流程

在小許文章《圖文講透Golang標(biāo)準(zhǔn)庫 net/http實(shí)現(xiàn)原理 -- 服務(wù)端》中講的比較詳細(xì)了,這里再把大致流程整理以下,整體流程如下:

1. 將路由和對應(yīng)的handler注冊到一個(gè) map 中,用做后續(xù)鍵值路由匹配

2. 注冊完之后就是開啟循環(huán)監(jiān)聽連接,每獲取到一個(gè)連接就會創(chuàng)建一個(gè) Goroutine進(jìn)行處理

3. 在創(chuàng)建好的 Goroutine 里面會循環(huán)的等待接收請求數(shù)據(jù),然后根據(jù)請求的地址去鍵值路由map中匹配對應(yīng)的handler

4. 執(zhí)行匹配到的處理器handler

net/http 的實(shí)現(xiàn)是一個(gè)連接新建一個(gè) goroutine,如果在連接數(shù)非常多的時(shí)候,,每個(gè)連接都會創(chuàng)建一個(gè) Goroutine 就會給系統(tǒng)帶來一定的壓力。這也就造成了 net/http在處理高并發(fā)時(shí)的瓶頸。

每次來了一個(gè)連接,都要實(shí)例化一個(gè)連接對象,這誰受得了,哈哈

fasthttp處理流程

再看看fasthttp處理請求的流程:

1. 啟動監(jiān)聽

2. 循環(huán)監(jiān)聽端口獲取連接,建立workerPool

3. 循環(huán)嘗試獲取連接 net.Conn,先會去 ready 隊(duì)列里獲取 workerChan,獲取不到就會去對象池獲取

4. 將獲取到的的連接net.Conn 發(fā)送到 workerChan 的 channel 中

5. 開啟一個(gè) Goroutine 一直循環(huán)獲取 workerChan 這個(gè) channel 中的數(shù)據(jù)

6. 獲取到channel中的net.Conn之后就會對請求進(jìn)行處理

workerChan 其實(shí)就是一個(gè)連接處理對象,這個(gè)對象里面有一個(gè) channel 用來傳遞連接;每個(gè) workerChan 在后臺都會有一個(gè) Goroutine 循環(huán)獲取 channel 中的連接,然后進(jìn)行處理。

workerChan是在workerPool臨時(shí)對象分別存取

fasthttp為什么快

fasthttp的優(yōu)化主要有以下幾個(gè)點(diǎn):

• 連接復(fù)用,如slice中有可復(fù)用的workerChan就從ready這個(gè)slice中獲取,沒有可復(fù)用的就在workerChanPool創(chuàng)建一個(gè),萬一池子滿了(默認(rèn)是 256 * 1024個(gè))就報(bào)錯(cuò)。

• 對于內(nèi)存復(fù)用,就是大量使用了sync.Pool(你知道的,sync.Pool復(fù)用對象有啥好處),有人統(tǒng)計(jì)過,用了整整30個(gè)sync.Pool,context、request對象、header、response對象都用了sync.Pool ....

• 利用unsafe.Pointer指針進(jìn)行[]byte 和 string 轉(zhuǎn)換,避免[]byte到string轉(zhuǎn)換時(shí)帶來的內(nèi)存分配和拷貝帶來的消耗 。

知道了fasthttp為什么快,接下來我們看下它是如何處理監(jiān)聽處理請求的,在哪些地方用到了這些特性。

底層實(shí)現(xiàn)

簡單案例

import (
    "github.com/buaazp/fasthttprouter"
    "github.com/valyala/fasthttp"
    "log"
)

func main() {
    //創(chuàng)建路由
    r := fasthttprouter.New()
    r.GET("/", Index)
    if err := fasthttp.ListenAndServe(":8083", r.Handler); err != nil {
        log.Fatalf("ListenAndServe fatal: %s", err)
    }

}
func Index(ctx *fasthttp.RequestCtx) {
    ctx.WriteString("hello xiaou code!")
}

這個(gè)案例同樣是幾樣代碼就啟動了一個(gè)服務(wù)。

創(chuàng)建路由、為不同的路由執(zhí)行關(guān)聯(lián)不同的處理函數(shù)handler,接著跟net/http一樣調(diào)用 ListenAndServe 函數(shù)進(jìn)行啟動服務(wù)監(jiān)聽,等待請求進(jìn)行處理。

workerPool結(jié)構(gòu)

workerpool 對象表示 連接處理 工作池,這樣可以控制連接建立后的處理方式,而不是像標(biāo)準(zhǔn)庫 net/http 一樣,對每個(gè)請求連接都啟動一個(gè) goroutine 處理, 內(nèi)部的 ready 字段存儲空閑的 workerChan 對象,workerChanPool 字段表示管理 workerChan 的對象池。

workerPool結(jié)構(gòu)體如下:

type workerPool struct {
    //匹配請求對應(yīng)的handler
    WorkerFunc ServeHandler
    //最大同時(shí)處理的請求數(shù)
    MaxWorkersCount int
    
    LogAllErrors bool
    //最大空閑工作時(shí)間
    MaxIdleWorkerDuration time.Duration

    Logger Logger
    //互斥鎖
    lock         sync.Mutex
    //work數(shù)量
    workersCount int
    mustStop     bool
    // 空閑的 workerChan
    ready []*workerChan
    //是否關(guān)閉workerPool
    stopCh chan struct{}
    //sync.Pool  workerChan 的對象池
    workerChanPool sync.Pool

    connState func(net.Conn, ConnState)
}

WorkerFunc :這個(gè)屬性挺重要的,因?yàn)榻o它賦值的是Server.serveConn

ready:存儲了空閑的workerChan

workerChanPool:是workerChan 的對象池,在sync.Pool中存取臨時(shí)對象,可減少內(nèi)存分配

啟動服務(wù)

ListenAndServe是啟動服務(wù)監(jiān)聽的入口,內(nèi)部的調(diào)用過程如下:

Server.Serve

Serve方法為來自給監(jiān)聽到的連接提供處理服務(wù),直到超過了最大限制(256 * 1024)才會報(bào)錯(cuò)。

func (s *Server) Serve(ln net.Listener) error {
    //最大連接處理數(shù)
    maxWorkersCount := s.getConcurrency()

    s.mu.Lock()
    s.ln = append(s.ln, ln)
    if s.done == nil {
        s.done = make(chan struct{})
    }
    if s.concurrencyCh == nil {
        s.concurrencyCh = make(chan struct{}, maxWorkersCount)
    }
    s.mu.Unlock()
    //workerPool進(jìn)行初始化
    wp := &workerPool{
        WorkerFunc:            s.serveConn,
        MaxWorkersCount:       maxWorkersCount,
        LogAllErrors:          s.LogAllErrors,
        MaxIdleWorkerDuration: s.MaxIdleWorkerDuration,
        Logger:                s.logger(),
        connState:             s.setState,
    }
    //開啟協(xié)程,處理協(xié)程池的清理工作
    wp.Start()
    atomic.AddInt32(&s.open, 1)
    defer atomic.AddInt32(&s.open, -1)

    for {
        // 阻塞等待,獲取連接net.Conn
        if c, err = acceptConn(s, ln, &lastPerIPErrorTime); err != nil {
            ...
            return err
        }
        s.setState(c, StateNew)
        atomic.AddInt32(&s.open, 1)
        //處理獲取到的連接net.Conn
        if !wp.Serve(c) {
            //未能處理,說明已達(dá)到最大worker限制
            ...
        }
        c = nil
    }
}

從上面的注釋中我們可以看出 Server 方法主要做了以下幾件事:

1. 初始化 worker Pool,并啟動

2. net.Listener循環(huán)接收請求

3. 將接收到的請求交給workerChan 處理

注意:這里如果超過了設(shè)定的最大連接數(shù)(默認(rèn)是 256 * 1024個(gè))就直接報(bào)錯(cuò)了

Start開啟協(xié)程池

workerPool進(jìn)行初始化之后接著就調(diào)用Start開啟,這里主要是指定sync.Pool變量workerChanPool的創(chuàng)建函數(shù)。

接著開啟一個(gè)協(xié)程,該Goroutine的目的是進(jìn)行定時(shí)清理 workerPool 中的 ready 中保存的空閑 workerChan,清理頻率為每 10s 啟動一次。

清理規(guī)則是使用二進(jìn)制搜索算法找出最近可以清理的工作者的索引

func (wp *workerPool) Start() {
    //wp的關(guān)閉channel是否為空
    if wp.stopCh != nil {
        return
    }
    wp.stopCh = make(chan struct{})
    stopCh := wp.stopCh
    //指定workerChanPool的創(chuàng)建函數(shù)
    wp.workerChanPool.New = func() interface{} {
        return &workerChan{
            ch: make(chan net.Conn, workerChanCap),
        }
    }
    //開啟協(xié)程
    go func() {
        var scratch []*workerChan
        for {
            //清理空閑超時(shí)的 workerChan
            wp.clean(&scratch)
            select {
            case <-stopCh:
                return
            default:
                // 間隔10 s
                time.Sleep(wp.getMaxIdleWorkerDuration())
            }
        }
    }()
}

開啟一個(gè)清理Goroutine的目的是為了避免在流量高峰創(chuàng)建了大量協(xié)程,之后不再使用,造成協(xié)程浪費(fèi)。

清理流程是在wp.clean()方法中實(shí)現(xiàn)的。

接收連接

acceptConn函數(shù)通過調(diào)用net.Listener的accept方法去接受連接,這里獲取連接的方式跟net/http調(diào)用的其實(shí)都是一樣的。

func acceptConn(s *Server, ln net.Listener, lastPerIPErrorTime *time.Time) (net.Conn, error) {
    for {
        c, err := ln.Accept()
        if err != nil {
            //err判斷
            ...
        }
        //校驗(yàn)是否net.TCPConn連接
       // 校驗(yàn)每個(gè)ip對應(yīng)的連接數(shù)
        if s.MaxConnsPerIP > 0 {
            pic := wrapPerIPConn(s, c)
            if pic == nil {
                ...
                continue
            }
            c = pic
        }
        return c, nil
    }
}

獲取 workerChan

func (wp *workerPool) Serve(c net.Conn) bool {
    //獲取 workerChan 
    ch := wp.getCh()
    if ch == nil {
        return false
    }
    //將連接放到channel中
    ch.ch <- c
    //返回true
    return true
}

這里調(diào)用的getCh()函數(shù)實(shí)現(xiàn)了獲取workerChan,獲取到之后將之前接受的連接net.Conn放到workerChan結(jié)構(gòu)體的channel通道中。

我們看下workerChan這個(gè)結(jié)構(gòu)體

type workerChan struct {
    lastUseTime time.Time
    ch          chan net.Conn
}

lastUseTime:最后一次被使用的時(shí)間,這個(gè)值在進(jìn)行清理workerChan的時(shí)候是會用到的

ch:用來傳遞獲取到的連接net.Conn,獲取到連接時(shí)接收,處理請求時(shí)獲取

getCh方法:

func (wp *workerPool) getCh() *workerChan {
    var ch *workerChan
    createWorker := false

    wp.lock.Lock()
    //從ready隊(duì)列中拿workerChan
    ready := wp.ready
    n := len(ready) - 1
    if n < 0 {
        if wp.workersCount < wp.MaxWorkersCount {
            createWorker = true
            wp.workersCount++
        }
    } else {
        //ready隊(duì)列不為空,從隊(duì)尾拿workerChan
        ch = ready[n]
        //隊(duì)尾置為nil
        ready[n] = nil
        //重新將ready賦值給wp.ready
        wp.ready = ready[:n]
    }
    wp.lock.Unlock()
    //ready中獲取不到workerChan,則從對象池中新建一個(gè)
    if ch == nil {
        if !createWorker {
            return nil
        }
        vch := wp.workerChanPool.Get()
        ch = vch.(*workerChan)
        //開啟一個(gè)goroutine執(zhí)行
        go func() {
            //處理ch中channel中的數(shù)據(jù)
            wp.workerFunc(ch)
            //處理完后將workerChan放回對象池
            wp.workerChanPool.Put(vch)
        }()
    }
    return ch
}

getCh()方法的目的就是獲取workerChan,流程如下:

• 先會去 ready 空閑隊(duì)列中獲取 workerChan

• ready 獲取不到則從對象池中創(chuàng)建一個(gè)新的 workerChan

• 并啟動 Goroutine 用來處理 channel 中的數(shù)據(jù)

workPool中的ready是一個(gè)FILO的棧,每次從隊(duì)尾取出workChan

處理連接

func (wp *workerPool) workerFunc(ch *workerChan) {
    var c net.Conn

    var err error
    for c = range ch.ch {
        //channel的值是nil,退出
        if c == nil {
            break
        }
        //執(zhí)行請求,并處理
        if err = wp.WorkerFunc(c); err != nil && err != errHijacked {
            ...
        }
        ...
        //將當(dāng)前workerChan放入ready隊(duì)列
        if !wp.release(ch) {
            break
        }
    }

    wp.lock.Lock()
    wp.workersCount--
    wp.lock.Unlock()
}

執(zhí)行流程

• 先遍歷workerChan的channel,看是否有連接net.Conn

• 獲取到連接之后就執(zhí)行WorkerFunc 函數(shù)處理請求

• 請求處理完之后將當(dāng)前workerChan放入ready隊(duì)列

WorkerFunc 函數(shù)實(shí)際上是 Server 的 serveConn 方法

一開始開代碼的時(shí)候我還沒發(fā)現(xiàn)呢,細(xì)看了之后在Server.Serve()啟動服務(wù)時(shí)將Server.serveConn()方法賦值給了workerPool的WorkerFunc()。

要想了解實(shí)現(xiàn)的朋友可以搜下這方面的代碼

func (s *Server) ServeConn(c net.Conn) error {
    ...
    err := s.serveConn(c)
    ...
}

里面的代碼會比較多,不過里面的流程就是是獲取到請求的參數(shù),找到對應(yīng)的 handler 進(jìn)行請求處理,然后返回 響應(yīng)給客戶端。

這里的實(shí)現(xiàn)代碼可以看到context、request對象的sync.Pool實(shí)現(xiàn),這里就不一一貼出來了。

總結(jié)

fasthttp和net/http在實(shí)現(xiàn)上還是有較大區(qū)別,通過對實(shí)現(xiàn)原理的分析,知道了fasthttp速度快是利用了大量sync.Pool對象復(fù)用 、[]byte 和 string利用萬能指針unsafe.Pointer進(jìn)行轉(zhuǎn)換等優(yōu)化技巧。

如果你的業(yè)務(wù)需要支撐較高的 QPS 并且保持一致的低延遲時(shí)間,那么采用 fasthttp 是一個(gè)較好的選擇。不過net/http兼容性更高,在多數(shù)情況下反而是更好的選擇!

以上就是淺析Go中fasthttp與net/http的性能對比及應(yīng)用的詳細(xì)內(nèi)容,更多關(guān)于Go fasthttp的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 如何在VScode 中編譯多個(gè)Go文件

    如何在VScode 中編譯多個(gè)Go文件

    這篇文章主要介紹了VScode 中編譯多個(gè)Go文件的實(shí)現(xiàn)方法,本文通過實(shí)例圖文并茂的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2021-08-08
  • Go?http.Transport?主要參數(shù)說明

    Go?http.Transport?主要參數(shù)說明

    這篇文章主要為大家介紹了Go?http.Transport主要參數(shù)說明,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • Go?Fiber快速搭建一個(gè)HTTP服務(wù)器

    Go?Fiber快速搭建一個(gè)HTTP服務(wù)器

    Fiber?是一個(gè)?Express?啟發(fā)?web?框架基于?fasthttp?,最快?Go?的?http?引擎,這篇文章主要介紹了Go?Fiber快速搭建一個(gè)HTTP服務(wù)器,需要的朋友可以參考下
    2023-06-06
  • 一個(gè)Pod調(diào)度失敗后重新觸發(fā)調(diào)度的所有情況分析

    一個(gè)Pod調(diào)度失敗后重新觸發(fā)調(diào)度的所有情況分析

    這篇文章主要為大家介紹了一個(gè)Pod調(diào)度失敗后重新觸發(fā)調(diào)度的所有情況分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • Golang中的錯(cuò)誤處理深入分析

    Golang中的錯(cuò)誤處理深入分析

    Go錯(cuò)誤處理類似C語言,沒有提供任何異常,以及類java語言使用的try/catch異常處理機(jī)制。Go異常處理僅簡化為預(yù)定義的Error類型,Go沒有提供異常處理機(jī)制,不能拋出類似許多其他語言的異常。相反,Golang集成了新的錯(cuò)誤處理機(jī)制,如panic和recovery
    2023-01-01
  • GO語言實(shí)現(xiàn)簡單TCP服務(wù)的方法

    GO語言實(shí)現(xiàn)簡單TCP服務(wù)的方法

    這篇文章主要介紹了GO語言實(shí)現(xiàn)簡單TCP服務(wù)的方法,實(shí)例分析了Go語言實(shí)現(xiàn)TCP服務(wù)的技巧,需要的朋友可以參考下
    2015-03-03
  • Go語言編程中判斷文件是否存在是創(chuàng)建目錄的方法

    Go語言編程中判斷文件是否存在是創(chuàng)建目錄的方法

    這篇文章主要介紹了Go語言編程中判斷文件是否存在是創(chuàng)建目錄的方法,示例都是使用os包下的函數(shù),需要的朋友可以參考下
    2015-10-10
  • Go語言的type?func()用法詳解

    Go語言的type?func()用法詳解

    在Go語言中,函數(shù)的基本組成為:關(guān)鍵字func、函數(shù)名、參數(shù)列表、返回值、函數(shù)體和返回語句,這篇文章主要介紹了Go語言的type?func()用法,需要的朋友可以參考下
    2022-03-03
  • GO語言異常處理機(jī)制panic和recover分析

    GO語言異常處理機(jī)制panic和recover分析

    這篇文章主要介紹了GO語言異常處理機(jī)制panic和recover,分析了捕獲運(yùn)行時(shí)發(fā)生錯(cuò)誤的方法,是非常實(shí)用的技巧,需要的朋友可以參考下
    2014-12-12
  • Go實(shí)現(xiàn)mongodb增刪改查工具類的代碼示例

    Go實(shí)現(xiàn)mongodb增刪改查工具類的代碼示例

    這篇文章主要給大家介紹了關(guān)于Go實(shí)現(xiàn)mongodb增刪改查工具類的相關(guān)資料,MongoDB是一個(gè)NoSQL數(shù)據(jù)庫,它提供了靈活的文檔存儲模型以及強(qiáng)大的查詢和操作功能,需要的朋友可以參考下
    2023-10-10

最新評論