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

SingleFlight模式的Go并發(fā)編程學(xué)習(xí)

 更新時(shí)間:2022年04月26日 11:44:50   作者:kevwan  
這篇文章主要為大家介紹了SingleFlight模式的Go并發(fā)編程學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

最近接觸到微服務(wù)框架go-zero,翻看了整個(gè)框架代碼,發(fā)現(xiàn)結(jié)構(gòu)清晰、代碼簡(jiǎn)潔,所以決定閱讀源碼學(xué)習(xí)下,本次閱讀的源碼位于core/syncx/singleflight.go。

go-zeroSingleFlight的作用是:將并發(fā)請(qǐng)求合并成一個(gè)請(qǐng)求,以減少對(duì)下層服務(wù)的壓力。

應(yīng)用場(chǎng)景

  • 查詢緩存時(shí),合并請(qǐng)求,提升服務(wù)性能。 假設(shè)有一個(gè) IP 查詢的服務(wù),每次用戶請(qǐng)求先在緩存中查詢一個(gè) IP 的歸屬地,如果緩存中有結(jié)果則直接返回,不存在則進(jìn)行 IP 解析操作。

如上圖所示,n 個(gè)用戶請(qǐng)求查詢同一個(gè) IP(8.8.8.8)就會(huì)對(duì)應(yīng) n 個(gè) Redis 的查詢,在高并發(fā)場(chǎng)景下,如果能將 n 個(gè) Redis 查詢合并成一個(gè) Redis 查詢,那么性能肯定會(huì)提升很多,而 SingleFlight就是用來(lái)實(shí)現(xiàn)請(qǐng)求合并的,效果如下:

  • 防止緩存擊穿。

緩存擊穿問(wèn)題是指:在高并發(fā)的場(chǎng)景中,大量的請(qǐng)求同時(shí)查詢一個(gè) key ,如果這個(gè) key 正好過(guò)期失效了,就會(huì)導(dǎo)致大量的請(qǐng)求都打到數(shù)據(jù)庫(kù),導(dǎo)致數(shù)據(jù)庫(kù)的連接增多,負(fù)載上升。

通過(guò)SingleFlight可以將對(duì)同一個(gè)Key的并發(fā)請(qǐng)求進(jìn)行合并,只讓其中一個(gè)請(qǐng)求到數(shù)據(jù)庫(kù)進(jìn)行查詢,其他請(qǐng)求共享同一個(gè)結(jié)果,可以很大程度提升并發(fā)能力。

應(yīng)用方式

直接上代碼:

func main() {
  round := 10
  var wg sync.WaitGroup
  barrier := syncx.NewSingleFlight()
  wg.Add(round)
  for i := 0; i < round; i++ {
    go func() {
      defer wg.Done()
      // 啟用10個(gè)協(xié)程模擬獲取緩存操作
      val, err := barrier.Do("get_rand_int", func() (interface{}, error) {
        time.Sleep(time.Second)
        return rand.Int(), nil
      })
      if err != nil {
        fmt.Println(err)
      } else {
        fmt.Println(val)
      }
    }()
  }
  wg.Wait()
}

以上代碼,模擬 10 個(gè)協(xié)程請(qǐng)求 Redis 獲取一個(gè) key 的內(nèi)容,代碼很簡(jiǎn)單,就是執(zhí)行Do()方法。其中,接收兩個(gè)參數(shù),第一個(gè)參數(shù)是獲取資源的標(biāo)識(shí),可以是 redis 中緩存的 key,第二個(gè)參數(shù)就是一個(gè)匿名函數(shù),封裝好要做的業(yè)務(wù)邏輯。最終獲得的結(jié)果如下:

5577006791947779410
5577006791947779410
5577006791947779410
5577006791947779410
5577006791947779410
5577006791947779410
5577006791947779410
5577006791947779410
5577006791947779410
5577006791947779410

從上看出,10個(gè)協(xié)程都獲得了同一個(gè)結(jié)果,也就是只有一個(gè)協(xié)程真正執(zhí)行了rand.Int()獲取了隨機(jī)數(shù),其他的協(xié)程都共享了這個(gè)結(jié)果。

源碼解析

先看代碼結(jié)構(gòu):

type (
  // 定義接口,有2個(gè)方法 Do 和 DoEx,其實(shí)邏輯是一樣的,DoEx 多了一個(gè)標(biāo)識(shí),主要看Do的邏輯就夠了
  SingleFlight interface {
    Do(key string, fn func() (interface{}, error)) (interface{}, error)
    DoEx(key string, fn func() (interface{}, error)) (interface{}, bool, error)
  }
  // 定義 call 的結(jié)構(gòu)
  call struct {
    wg  sync.WaitGroup // 用于實(shí)現(xiàn)通過(guò)1個(gè) call,其他 call 阻塞
    val interface{}    // 表示 call 操作的返回結(jié)果
    err error          // 表示 call 操作發(fā)生的錯(cuò)誤
  }
  // 總控結(jié)構(gòu),實(shí)現(xiàn) SingleFlight 接口
  flightGroup struct {
    calls map[string]*call // 不同的 call 對(duì)應(yīng)不同的 key
    lock  sync.Mutex       // 利用鎖控制請(qǐng)求
  }
)

然后看最核心的Do方法做了什么事情:

func (g *flightGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
  c, done := g.createCall(key)
  if done {
    return c.val, c.err
  }
  g.makeCall(c, key, fn)
  return c.val, c.err
}

代碼很簡(jiǎn)潔,利用g.createCall(key)對(duì) key 發(fā)起 call 請(qǐng)求(其實(shí)就是做一件事情),如果此時(shí)已經(jīng)有其他協(xié)程已經(jīng)在發(fā)起 call 請(qǐng)求就阻塞住(done 為 true 的情況),等待拿到結(jié)果后直接返回。如果 done 是 false,說(shuō)明當(dāng)前協(xié)程是第一個(gè)發(fā)起 call 的協(xié)程,那么就執(zhí)行g.makeCall(c, key, fn)真正地發(fā)起 call 請(qǐng)求(此后的其他協(xié)程就阻塞在了g.createCall(key))。 

從上圖可知,其實(shí)關(guān)鍵就兩步:

  • 判斷是第一個(gè)請(qǐng)求的協(xié)程(利用map)
  • 阻塞住其他所有協(xié)程(利用 sync.WaitGroup)

來(lái)看下g.createCall(key)如何實(shí)現(xiàn)的:

func (g *flightGroup) createCall(key string) (c *call, done bool) {
  g.lock.Lock()
  if c, ok := g.calls[key]; ok {
    g.lock.Unlock()
    c.wg.Wait()
    return c, true
  }
  c = new(call)
  c.wg.Add(1)
  g.calls[key] = c
  g.lock.Unlock()
  return c, false
}

先看第一步:判斷是第一個(gè)請(qǐng)求的協(xié)程(利用map)

g.lock.Lock()
if c, ok := g.calls[key]; ok {
  g.lock.Unlock()
  c.wg.Wait()
  return c, true
}

此處判斷 map 中的 key 是否存在,如果已經(jīng)存在,說(shuō)明已經(jīng)有其他協(xié)程在請(qǐng)求了,當(dāng)前這個(gè)協(xié)程只需要等待,等待是利用了sync.WaitGroupWait()方法實(shí)現(xiàn)的,此處還是很巧妙的。要注意的是,map 在 Go 中是非并發(fā)安全的,所以需要加鎖。

再看第二步:阻塞住其他所有協(xié)程(利用 sync.WaitGroup)

c = new(call)
c.wg.Add(1)
g.calls[key] = c

因?yàn)槭堑谝粋€(gè)發(fā)起 call 的協(xié)程,所以需要 new 這個(gè) call,然后將wg.Add(1),這樣就對(duì)應(yīng)了上面的wg.Wait(),阻塞剩下的協(xié)程。隨后將 new 的 call 放入 map 中,注意此時(shí)只是完成了初始化,并沒(méi)有真正去執(zhí)行call請(qǐng)求,真正的處理邏輯在 g.makeCall(c, key, fn)中。

func (g *flightGroup) makeCall(c *call, key string, fn func() (interface{}, error)) {
  defer func() {
    g.lock.Lock()
    delete(g.calls, key)
    g.lock.Unlock()
    c.wg.Done()
  }()
  c.val, c.err = fn()
}

這個(gè)方法中做的事情很簡(jiǎn)單,就是執(zhí)行了傳遞的匿名函數(shù)fn()(也就是真正call請(qǐng)求要做的事情)。最后處理收尾的事情(通過(guò)defer),也是分成兩步:

  • 刪除 map 中的 key,使得下次發(fā)起請(qǐng)求可以獲取新的值。
  • 調(diào)用wg.Done(),讓之前阻塞的協(xié)程全部獲得結(jié)果并返回。

至此,SingleFlight 的核心代碼就解析完畢了,雖然代碼不長(zhǎng),但是這個(gè)思想還是很棒的,可以在實(shí)際工作中借鑒。

總結(jié)

  • map 非并發(fā)安全,記得加鎖。
  • 巧用 sync.WaitGroup 去完成需要阻塞控制協(xié)程的應(yīng)用場(chǎng)景。
  • 通過(guò)匿名函數(shù) fn 去封裝傳遞具體業(yè)務(wù)邏輯,在調(diào)用 fn 的上層函數(shù)中去完成統(tǒng)一的邏輯處理。

項(xiàng)目地址

https://github.com/zeromicro/go-zero

以上就是SingleFlight模式的Go并發(fā)編程學(xué)習(xí)的詳細(xì)內(nèi)容,更多關(guān)于SingleFlight模式Go并發(fā)編程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang利用Recover進(jìn)行錯(cuò)誤處理

    Golang利用Recover進(jìn)行錯(cuò)誤處理

    Golang?中的?recover?是一個(gè)鮮為人知但非常有趣和強(qiáng)大的功能,這篇文章小編就來(lái)帶大家深入了解一下在Golang中是如何利用Recover進(jìn)行錯(cuò)誤處理吧
    2023-12-12
  • 使用Go和Gorm實(shí)現(xiàn)讀取SQLCipher加密數(shù)據(jù)庫(kù)

    使用Go和Gorm實(shí)現(xiàn)讀取SQLCipher加密數(shù)據(jù)庫(kù)

    本文檔主要描述通過(guò)Go和Gorm實(shí)現(xiàn)生成和讀取SQLCipher加密數(shù)據(jù)庫(kù)以及其中踩的一些坑,文章通過(guò)代碼示例講解的非常詳細(xì), 對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-06-06
  • 在Go中構(gòu)建并發(fā)TCP服務(wù)器

    在Go中構(gòu)建并發(fā)TCP服務(wù)器

    今天小編就為大家分享一篇關(guān)于在Go中構(gòu)建并發(fā)TCP服務(wù)器的文章,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-10-10
  • 詳解go中panic源碼解讀

    詳解go中panic源碼解讀

    這篇文章主要介紹了go中panic源碼解讀,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-04-04
  • Go調(diào)度器學(xué)習(xí)之協(xié)作與搶占詳解

    Go調(diào)度器學(xué)習(xí)之協(xié)作與搶占詳解

    如果某個(gè)G執(zhí)行時(shí)間過(guò)長(zhǎng),其他的G如何才能被正常調(diào)度,這就引出了接下來(lái)的話題:協(xié)作與搶占。本文將通過(guò)一些示例為大家詳細(xì)講講調(diào)度器中協(xié)作與搶占的相關(guān)知識(shí),需要的可以參考一下
    2023-04-04
  • Go語(yǔ)言中defer使用的陷阱小結(jié)

    Go語(yǔ)言中defer使用的陷阱小結(jié)

    本文主要介紹了Go語(yǔ)言中defer使用的陷阱小結(jié),分別是defer語(yǔ)句不可以在return語(yǔ)句之后,defer語(yǔ)句執(zhí)行的匿名函數(shù),匿名函數(shù)的參數(shù)會(huì)被預(yù)先處理,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01
  • 如何使用go實(shí)現(xiàn)創(chuàng)建WebSocket服務(wù)器

    如何使用go實(shí)現(xiàn)創(chuàng)建WebSocket服務(wù)器

    文章介紹了如何使用Go語(yǔ)言和gorilla/websocket庫(kù)創(chuàng)建一個(gè)簡(jiǎn)單的WebSocket服務(wù)器,并實(shí)現(xiàn)商品信息的實(shí)時(shí)廣播,感興趣的朋友一起看看吧
    2024-11-11
  • Golang如何快速構(gòu)建一個(gè)CLI小工具詳解

    Golang如何快速構(gòu)建一個(gè)CLI小工具詳解

    這篇文章主要為大家介紹了Golang如何快速構(gòu)建一個(gè)CLI小工具詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • Go應(yīng)用中優(yōu)雅處理Error的技巧總結(jié)

    Go應(yīng)用中優(yōu)雅處理Error的技巧總結(jié)

    在程序員中,尤其是go新手,經(jīng)常聽(tīng)到的一個(gè)討論話題是:如何處理錯(cuò)誤,這篇文章主要給大家介紹了關(guān)于Go應(yīng)用中優(yōu)雅處理Error的一些相關(guān)技巧,需要的朋友可以參考下
    2021-09-09
  • 簡(jiǎn)單高效!Go語(yǔ)言封裝二級(jí)認(rèn)證功能實(shí)現(xiàn)

    簡(jiǎn)單高效!Go語(yǔ)言封裝二級(jí)認(rèn)證功能實(shí)現(xiàn)

    本文將介紹如何使用Go語(yǔ)言封裝二級(jí)認(rèn)證功能,實(shí)現(xiàn)簡(jiǎn)單高效的用戶認(rèn)證流程,二級(jí)認(rèn)證是一種安全措施,要求用戶在登錄后進(jìn)行額外的身份驗(yàn)證,以提高賬戶安全性,
    2023-10-10

最新評(píng)論

邓州市| 揭阳市| 乌海市| 钟山县| 镶黄旗| 八宿县| 务川| 宝清县| 咸宁市| 和林格尔县| 日照市| 平利县| 宁陵县| 洛宁县| 达拉特旗| 朔州市| 贵定县| 镇江市| 宝鸡市| 定陶县| 太仓市| 新密市| 镇远县| 阿克| 松原市| 唐河县| 洪江市| 山阴县| 睢宁县| 安图县| 望奎县| 吉安市| 梅州市| 蓝田县| 阳春市| 崇阳县| 枣阳市| 阜新市| 观塘区| 金堂县| 资溪县|