成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

通過 SingleFlight 模式學習 Go 并發編程

開發
最近接觸到微服務框架go-zero,翻看了整個框架代碼,發現結構清晰、代碼簡潔,所以決定閱讀源碼學習下。

本次閱讀的源碼位于 core/syncx/singleflight.go 

 go-zero  SingleFlight 的作用是: 將并發請求合并成一個請求,以減少對下層服務的壓力。

應用場景

1.查詢緩存時,合并請求,提升服務性能。

假設有一個 IP 查詢的服務,每次用戶請求先在緩存中查詢一個 IP 的歸屬地,如果緩存中有結果則直接返回,不存在則進行 IP 解析操作。

如上圖所示,n 個用戶請求查詢同一個 IP(8.8.8.8)就會對應 n 個 Redis 的查詢,在高并發場景下,如果能將 n 個 Redis 查詢合并成一個 Redis 查詢,那么性能肯定會提升很多,而 SingleFlight 就是用來實現請求合并的,效果如下:

2.防止緩存擊穿。

緩存擊穿問題是指:在高并發的場景中,大量的請求同時查詢一個 key ,如果這個 key 正好過期失效了,就會導致大量的請求都打到數據庫,導致數據庫的連接增多,負載上升。

通過 SingleFlight 可以將對同一個Key的并發請求進行合并,只讓其中一個請求到數據庫進行查詢,其他請求共享同一個結果,可以很大程度提升并發能力。

應用方式

直接上代碼:

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個協程模擬獲取緩存操作
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 個協程請求 Redis 獲取一個 key 的內容,代碼很簡單,就是執行 Do() 方法。其中,接收兩個參數,第一個參數是獲取資源的標識,可以是 redis 中緩存的 key,第二個參數就是一個匿名函數,封裝好要做的業務邏輯。最終獲得的結果如下:

從上看出,10個協程都獲得了同一個結果,也就是只有一個協程真正執行了 rand.Int() 獲取了隨機數,其他的協程都共享了這個結果。

源碼解析

先看代碼結構:

type (
// 定義接口,有2個方法 Do 和 DoEx,其實邏輯是一樣的,DoEx 多了一個標識,主要看Do的邏輯就夠了
SingleFlight interface {
Do(key string, fn func() (interface{}, error)) (interface{}, error)
DoEx(key string, fn func() (interface{}, error)) (interface{}, bool, error)
}
// 定義 call 的結構
call struct {
wg sync.WaitGroup // 用于實現通過1個 call,其他 call 阻塞
val interface{} // 表示 call 操作的返回結果
err error // 表示 call 操作發生的錯誤
}
// 總控結構,實現 SingleFlight 接口
flightGroup struct {
calls map[string]*call // 不同的 call 對應不同的 key
lock sync.Mutex // 利用鎖控制請求
}
)

然后看最核心的 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
}

代碼很簡潔,利用 g.createCall(key) 對 key 發起 call 請求(其實就是做一件事情),如果此時已經有其他協程已經在發起 call 請求就阻塞住(done 為 true 的情況),等待拿到結果后直接返回。如果 done 是 false,說明當前協程是第一個發起 call 的協程,那么就執行 g.makeCall(c, key, fn) 真正地發起 call 請求(此后的其他協程就阻塞在了 g.createCall(key) )。

從上圖可知,其實關鍵就兩步:

  1. 判斷是第一個請求的協程(利用map)
  2. 阻塞住其他所有協程(利用 sync.WaitGroup)

來看下 g.createCall(key) 如何實現的:

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
}

先看第一步:判斷是第一個請求的協程(利用map)

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

此處判斷 map 中的 key 是否存在,如果已經存在,說明已經有其他協程在請求了,當前這個協程只需要等待,等待是利用了 sync.WaitGroup  Wait() 方法實現的,此處還是很巧妙的。 要注意的是,map 在 Go 中是非并發安全的,所以需要加鎖。

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

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

因為是第一個發起 call 的協程,所以需要 new 這個 call,然后將 wg.Add(1) ,這樣就對應了上面的 wg.Wait() ,阻塞剩下的協程。隨后將 new 的 call 放入 map 中,注意此時只是完成了初始化,并沒有真正去執行call請求,真正的處理邏輯在 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()
}

這個方法中做的事情很簡單,就是執行了傳遞的匿名函數 fn() (也就是真正call請求要做的事情)。最后處理收尾的事情(通過defer),也是分成兩步:

  1. 刪除 map 中的 key,使得下次發起請求可以獲取新的值。
  2. 調用 wg.Done() ,讓之前阻塞的協程全部獲得結果并返回。

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

總結

  • map 非并發安全,記得加鎖。
  • 巧用 sync.WaitGroup 去完成 需要阻塞控制協程 的應用場景。
  • 通過匿名函數 fn 去封裝傳遞具體業務邏輯,在調用 fn 的上層函數中去完成統一的邏輯處理。

項目地址

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

責任編輯:張燕妮 來源: 微服務實踐
相關推薦

2021-03-24 06:06:13

Go并發編程Singlefligh

2022-10-17 08:07:13

Go 語言并發編程

2023-09-12 11:10:00

代碼優化Go

2017-11-10 11:27:48

Go并行算法

2025-03-24 00:25:00

Go語言并發編程

2023-02-10 09:40:36

Go語言并發

2013-05-28 09:43:38

GoGo語言并發模式

2023-11-27 18:07:05

Go并發編程

2024-07-08 00:01:00

GPM模型調度器

2025-06-17 09:32:15

2023-12-29 08:10:41

Go并發開發

2023-10-28 15:37:39

Go編程語言

2021-12-30 18:34:29

緩存GoSinglefligh

2023-01-30 15:41:10

Channel控制并發

2024-09-06 10:48:13

2021-11-28 22:33:01

Go選項模式

2024-01-29 00:35:00

Go并發開發

2020-01-14 11:17:33

Go并發Linux

2014-04-25 10:13:00

Go語言并發模式

2024-10-15 10:00:06

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 天天躁日日躁狠狠躁2018小说 | 91在线精品秘密一区二区 | 久久一区二区三区电影 | 国产欧美在线观看 | 亚洲风情在线观看 | 欧美激情综合网 | 国产精品伦理一区二区三区 | 亚洲日韩中文字幕一区 | 欧美成人一区二区 | 欧美日韩国产一区二区三区 | 欧美日韩综合 | 国产黄色小视频 | 91精品国产高清一区二区三区 | 亚洲综合激情 | 国产精品1区2区 | 日韩成人在线视频 | 国产成人综合亚洲欧美94在线 | 天堂成人国产精品一区 | 91精品国产综合久久久久 | 久草精品视频 | 波多野结衣先锋影音 | 久久久久久女 | 999久久久久久久久6666 | 澳门永久av免费网站 | 成人欧美一区二区三区黑人孕妇 | 91电影 | 国产精品视频一区二区三区 | 天天综合久久 | 天天看天天爽 | 中文字幕在线第一页 | 夜夜夜夜夜夜曰天天天 | 国产精品久久久久久吹潮 | 蜜臀久久99精品久久久久久宅男 | 99精品在线观看 | 欧美一级免费看 | 99久久日韩精品免费热麻豆美女 | 风间由美一区二区三区在线观看 | 中文在线a在线 | 日本xx视频免费观看 | 在线视频 亚洲 | 国产精品 欧美精品 |