最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang你一定要懂的連接池實(shí)現(xiàn)

 更新時(shí)間:2020年08月19日 14:27:21   作者:源代碼  
這篇文章主要介紹了Golang你一定要懂的連接池實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

問題引入

作為一名Golang開發(fā)者,線上環(huán)境遇到過好幾次連接數(shù)暴增問題(mysql/redis/kafka等)。

糾其原因,Golang作為常駐進(jìn)程,請求第三方服務(wù)或者資源完畢后,需要手動(dòng)關(guān)閉連接,否則連接會一直存在。而很多時(shí)候,開發(fā)者不一定記得關(guān)閉這個(gè)連接。

這樣是不是很麻煩?于是有了連接池。顧名思義,連接池就是管理連接的;我們從連接池獲取連接,請求完畢后再將連接還給連接池;連接池幫我們做了連接的建立、復(fù)用以及回收工作。

在設(shè)計(jì)與實(shí)現(xiàn)連接池時(shí),我們通常需要考慮以下幾個(gè)問題:

  • 連接池的連接數(shù)目是否有限制,最大可以建立多少個(gè)連接?
  • 當(dāng)連接長時(shí)間沒有使用,需要回收該連接嗎?
  • 業(yè)務(wù)請求需要獲取連接時(shí),此時(shí)若連接池?zé)o空閑連接且無法新建連接,業(yè)務(wù)需要排隊(duì)等待嗎?
  • 排隊(duì)的話又存在另外的問題,隊(duì)列長度有無限制,排隊(duì)時(shí)間呢?

Golang連接池實(shí)現(xiàn)原理

我們以Golang HTTP連接池為例,分析連接池的實(shí)現(xiàn)原理。

結(jié)構(gòu)體Transport

Transport結(jié)構(gòu)定義如下:

type Transport struct {
  //操作空閑連接需要獲取鎖
  idleMu    sync.Mutex
  //空閑連接池,key為協(xié)議目標(biāo)地址等組合
  idleConn   map[connectMethodKey][]*persistConn // most recently used at end
  //等待空閑連接的隊(duì)列,基于切片實(shí)現(xiàn),隊(duì)列大小無限制
  idleConnWait map[connectMethodKey]wantConnQueue // waiting getConns
  
  //排隊(duì)等待建立連接需要獲取鎖
  connsPerHostMu  sync.Mutex
  //每個(gè)host建立的連接數(shù)
  connsPerHost   map[connectMethodKey]int
  //等待建立連接的隊(duì)列,同樣基于切片實(shí)現(xiàn),隊(duì)列大小無限制
  connsPerHostWait map[connectMethodKey]wantConnQueue // waiting getConns
  
  //最大空閑連接數(shù)
  MaxIdleConns int
  //每個(gè)目標(biāo)host最大空閑連接數(shù);默認(rèn)為2(注意默認(rèn)值)
  MaxIdleConnsPerHost int
  //每個(gè)host可建立的最大連接數(shù)
  MaxConnsPerHost int
  //連接多少時(shí)間沒有使用則被關(guān)閉
  IdleConnTimeout time.Duration
  
  //禁用長連接,使用短連接
  DisableKeepAlives bool
}

可以看到,連接護(hù)著隊(duì)列,都是一個(gè)map結(jié)構(gòu),而key為協(xié)議目標(biāo)地址等組合,即同一種協(xié)議與同一個(gè)目標(biāo)host可建立的連接或者空閑連接是有限制的。

需要特別注意的是,MaxIdleConnsPerHost默認(rèn)等于2,即與目標(biāo)主機(jī)最多只維護(hù)兩個(gè)空閑連接。這會導(dǎo)致什么呢?

如果遇到突發(fā)流量,瞬間建立大量連接,但是回收連接時(shí),由于最大空閑連接數(shù)的限制,該聯(lián)機(jī)不能進(jìn)入空閑連接池,只能直接關(guān)閉。結(jié)果是,一直新建大量連接,又關(guān)閉大量連,業(yè)務(wù)機(jī)器的TIME_WAIT連接數(shù)隨之突增。

線上有些業(yè)務(wù)架構(gòu)是這樣的:客戶端 ===> LVS ===> Nginx ===> 服務(wù)。LVS負(fù)載均衡方案采用DR模式,LVS與Nginx配置統(tǒng)一VIP。此時(shí)在客戶端看來,只有一個(gè)IP地址,只有一個(gè)Host。上述問題更為明顯。

最后,Transport也提供了配置DisableKeepAlives,禁用長連接,使用短連接訪問第三方資源或者服務(wù)。

連接獲取與回收

Transport結(jié)構(gòu)提供下面兩個(gè)方法實(shí)現(xiàn)連接的獲取與回收操作。

func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persistConn, err error) {}

func (t *Transport) tryPutIdleConn(pconn *persistConn) error {}

連接的獲取主要分為兩步走:1)嘗試獲取空閑連接;2)嘗試新建連接:

//getConn方法內(nèi)部實(shí)現(xiàn)

if delivered := t.queueForIdleConn(w); delivered {
  return pc, nil
}
  
t.queueForDial(w)

當(dāng)然,可能獲取不到連接而需要排隊(duì),此時(shí)怎么辦呢?當(dāng)前會阻塞當(dāng)前協(xié)程了,直到獲取連接為止,或者h(yuǎn)ttpclient超時(shí)取消請求:

select {
  case <-w.ready:
    return w.pc, w.err
    
  //超時(shí)被取消
  case <-req.Cancel:
    return nil, errRequestCanceledConn
  ……
}

var errRequestCanceledConn = errors.New("net/http: request canceled while waiting for connection") // TODO: unify?

排隊(duì)等待空閑連接的邏輯如下:

func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) {
  //如果配置了空閑超時(shí)時(shí)間,獲取到連接需要檢測,超時(shí)則關(guān)閉連接
  if t.IdleConnTimeout > 0 {
    oldTime = time.Now().Add(-t.IdleConnTimeout)
  }
  
  if list, ok := t.idleConn[w.key]; ok {
    for len(list) > 0 && !stop {
      pconn := list[len(list)-1]
      tooOld := !oldTime.IsZero() && pconn.idleAt.Round(0).Before(oldTime)
      //超時(shí)了,關(guān)閉連接
      if tooOld {
        go pconn.closeConnIfStillIdle()
      }
      
      //分發(fā)連接到wantConn
      delivered = w.tryDeliver(pconn, nil)
    }
  }
  
  //排隊(duì)等待空閑連接
  q := t.idleConnWait[w.key]
  q.pushBack(w)
  t.idleConnWait[w.key] = q
}

排隊(duì)等待新建連接的邏輯如下:

func (t *Transport) queueForDial(w *wantConn) {
  //如果沒有限制最大連接數(shù),直接建立連接
  if t.MaxConnsPerHost <= 0 {
    go t.dialConnFor(w)
    return
  }
  
  //如果沒超過連接數(shù)限制,直接建立連接
  if n := t.connsPerHost[w.key]; n < t.MaxConnsPerHost {
    go t.dialConnFor(w)
    return
  }
  
  //排隊(duì)等待連接建立
  q := t.connsPerHostWait[w.key]
  q.pushBack(w)
  t.connsPerHostWait[w.key] = q
}

連接建立完成后,同樣會調(diào)用tryDeliver分發(fā)連接到wantConn,同時(shí)關(guān)閉通道w.ready,這樣主協(xié)程糾接觸阻塞了。

func (w *wantConn) tryDeliver(pc *persistConn, err error) bool {
  w.pc = pc
  close(w.ready)
}

請求處理完成后,通過tryPutIdleConn將連接放回連接池;這時(shí)候如果存在等待空閑連接的協(xié)程,則需要分發(fā)復(fù)用該連接。另外,在回收連接時(shí),還需要校驗(yàn)空閑連接數(shù)目是否超過限制:

func (t *Transport) tryPutIdleConn(pconn *persistConn) error {
  //禁用長連接;或者最大空閑連接數(shù)不合法
  if t.DisableKeepAlives || t.MaxIdleConnsPerHost < 0 {
    return errKeepAlivesDisabled
  }
  
  if q, ok := t.idleConnWait[key]; ok {
    //如果等待隊(duì)列不為空,分發(fā)連接
    for q.len() > 0 {
      w := q.popFront()
      if w.tryDeliver(pconn, nil) {
        done = true
        break
      }
    }
  }
  
  //空閑連接數(shù)目超過限制,默認(rèn)為DefaultMaxIdleConnsPerHost=2
  idles := t.idleConn[key]
  if len(idles) >= t.maxIdleConnsPerHost() {
    return errTooManyIdleHost
  }

}

空閑連接超時(shí)關(guān)閉

Golang HTTP連接池如何實(shí)現(xiàn)空閑連接的超時(shí)關(guān)閉邏輯呢?從上述queueForIdleConn邏輯可以看到,每次在獲取到空閑連接時(shí),都會檢測是否已經(jīng)超時(shí),超時(shí)則關(guān)閉連接。

那如果沒有業(yè)務(wù)請求到達(dá),一直不需要獲取連接,空閑連接就不會超時(shí)關(guān)閉嗎?其實(shí)在將空閑連接添加到連接池時(shí),Golang同時(shí)還設(shè)置了定時(shí)器,定時(shí)器到期后,自然會關(guān)閉該連接。

pconn.idleTimer = time.AfterFunc(t.IdleConnTimeout, pconn.closeConnIfStillIdle)

排隊(duì)隊(duì)列怎么實(shí)現(xiàn)

怎么實(shí)現(xiàn)隊(duì)列模型呢?很簡單,可以基于切片:

queue  []*wantConn

//入隊(duì)
queue = append(queue, w)

//出隊(duì)
v := queue[0]
queue[0] = nil
queue = queue[1:]

這樣有什么問題嗎?隨著頻繁的入隊(duì)與出隊(duì)操作,切片queue的底層數(shù)組,會有大量空間無法復(fù)用而造成浪費(fèi)。除非該切片執(zhí)行了擴(kuò)容操作。

Golang在實(shí)現(xiàn)隊(duì)列時(shí),使用了兩個(gè)切片head和tail;head切片用于出隊(duì)操作,tail切片用于入隊(duì)操作;出隊(duì)時(shí),如果head切片為空,則交換head與tail。通過這種方式,Golang實(shí)現(xiàn)了底層數(shù)組空間的復(fù)用。

func (q *wantConnQueue) pushBack(w *wantConn) {
  q.tail = append(q.tail, w)
}

func (q *wantConnQueue) popFront() *wantConn {
  if q.headPos >= len(q.head) {
    if len(q.tail) == 0 {
      return nil
    }
    // Pick up tail as new head, clear tail.
    q.head, q.headPos, q.tail = q.tail, 0, q.head[:0]
  }
  w := q.head[q.headPos]
  q.head[q.headPos] = nil
  q.headPos++
  return w
}

到此這篇關(guān)于Golang你一定要懂的連接池實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Golang 連接池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • go微服務(wù)PolarisMesh源碼解析服務(wù)端啟動(dòng)流程

    go微服務(wù)PolarisMesh源碼解析服務(wù)端啟動(dòng)流程

    這篇文章主要為大家介紹了go微服務(wù)PolarisMesh源碼解析服務(wù)端啟動(dòng)流程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • Go語言實(shí)現(xiàn)IP段范圍校驗(yàn)示例

    Go語言實(shí)現(xiàn)IP段范圍校驗(yàn)示例

    這篇文章主要介紹了Go語言實(shí)現(xiàn)IP段范圍校驗(yàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • Go實(shí)現(xiàn)并發(fā)的示例代碼

    Go實(shí)現(xiàn)并發(fā)的示例代碼

    Go語言的并發(fā)機(jī)制是其強(qiáng)大和流行的一個(gè)關(guān)鍵特性之一,本文主要介紹了Go實(shí)現(xiàn)并發(fā)的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-11-11
  • Go的gin參數(shù)校驗(yàn)中的validator庫詳解

    Go的gin參數(shù)校驗(yàn)中的validator庫詳解

    這篇文章主要介紹了Go的gin參數(shù)校驗(yàn)之validator庫,使用 validator 以后,只需要在定義結(jié)構(gòu)體時(shí)使用 binding 或 validate tag標(biāo)識相關(guān)校驗(yàn)規(guī)則,就可以進(jìn)行參數(shù)校驗(yàn)了,而不用自己單獨(dú)去寫常見的校驗(yàn)規(guī)則,需要的朋友可以參考下
    2023-08-08
  • Golang圖片驗(yàn)證碼的使用方法

    Golang圖片驗(yàn)證碼的使用方法

    最近在使用到Golang進(jìn)行原生開發(fā),注冊和登錄頁面都涉及到圖片驗(yàn)證碼的功能,支持很多類型的驗(yàn)證方式,例如支持?jǐn)?shù)字類型、字母類型、音頻驗(yàn)證碼、中文驗(yàn)證碼,本文給大家介紹Golang圖片驗(yàn)證碼的使用,感興趣的朋友跟隨小編一起看看吧
    2024-05-05
  • Go并發(fā)的方法之goroutine模型與調(diào)度策略

    Go并發(fā)的方法之goroutine模型與調(diào)度策略

    在go中,協(xié)程co-routine被改為goroutine,一個(gè)goroutine只占幾kb,因此可以有大量的goroutine存在,另一方面goroutine 的調(diào)度器非常靈活,本文給大家介紹下Go并發(fā)的方法之goroutine模型與調(diào)度策略,感興趣的朋友一起看看吧
    2021-11-11
  • 一篇文章學(xué)會GO語言中的變量

    一篇文章學(xué)會GO語言中的變量

    Go語言是靜態(tài)類型語言,因此變量有明確類型的,編譯器也會檢查變量類型的正確性,下面這篇文章主要給大家介紹了關(guān)于GO語言中變量的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07
  • golang實(shí)戰(zhàn)之truncate日志文件詳解

    golang實(shí)戰(zhàn)之truncate日志文件詳解

    這篇文章主要給大家介紹了關(guān)于golang實(shí)戰(zhàn)之truncate日志文件的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07
  • 深入了解Go語言中web框架的中間件運(yùn)行機(jī)制

    深入了解Go語言中web框架的中間件運(yùn)行機(jī)制

    大家在使用iris框架搭建web系統(tǒng)時(shí),一定會用到中間件。那么你了解中間件的運(yùn)行機(jī)制嗎?你知道為什么在iris和gin框架的請求處理函數(shù)中要加c.Next()函數(shù)嗎?本文就和大家一起探究該問題的答案
    2023-02-02
  • Go語言中的變量聲明和賦值

    Go語言中的變量聲明和賦值

    這篇文章主要介紹了Go語言中的變量聲明和賦值的方法,十分的細(xì)致全面,有需要的小伙伴可以參考下。
    2015-04-04

最新評論

苍南县| 张家川| 灵山县| 沙雅县| 洞口县| 高阳县| 阿勒泰市| 当涂县| 丹棱县| 神农架林区| 淮阳县| 新巴尔虎左旗| 叶城县| 秀山| 兰西县| 芒康县| 兰考县| 浠水县| 津市市| 屯留县| 原阳县| 内江市| 和平县| 洛川县| 玛曲县| 顺平县| 昭觉县| 台湾省| 临朐县| 松溪县| 扎鲁特旗| 柘荣县| 开化县| 教育| 澄江县| 北海市| 九台市| 宁远县| 格尔木市| 山丹县| 保亭|