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

Golang使用singleflight解決并發(fā)重復(fù)請(qǐng)求

 更新時(shí)間:2023年08月01日 15:09:49   作者:寫代碼的lorre  
高并發(fā)的場(chǎng)景下,經(jīng)常會(huì)出現(xiàn)并發(fā)重復(fù)請(qǐng)求資源的情況,singleflight是golang內(nèi)置的一個(gè)包,這個(gè)包提供了對(duì)重復(fù)函數(shù)調(diào)用的抑制功能,所以下面我們就來看看如何使用它解決并發(fā)重復(fù)請(qǐng)求吧

背景

高并發(fā)的場(chǎng)景下,經(jīng)常會(huì)出現(xiàn)并發(fā)重復(fù)請(qǐng)求資源的情況。

比如說,緩存失效時(shí),我們?nèi)フ?qǐng)求db獲取最新的數(shù)據(jù),如果這個(gè)key是一個(gè)熱key,那么在緩存失效的瞬間,可能會(huì)有大量的并發(fā)請(qǐng)求訪問到db,導(dǎo)致db訪問量陡增,甚至是打崩db,這種場(chǎng)景也就是我們常說的緩存擊穿。

針對(duì)同一個(gè)key的并發(fā)請(qǐng)求,這些請(qǐng)求和響應(yīng)實(shí)際上都是一樣的。所以我們可以把這種并發(fā)請(qǐng)求優(yōu)化為:只進(jìn)行一次實(shí)際請(qǐng)求去訪問資源,然后得到實(shí)際響應(yīng),所有的并發(fā)請(qǐng)求共享這個(gè)實(shí)際響應(yīng)的結(jié)果

針對(duì)分布式場(chǎng)景,我們可以使用分布式鎖來實(shí)現(xiàn)

針對(duì)單機(jī)場(chǎng)景,我們可以使用singleflight來實(shí)現(xiàn)

singleflight

singleflight是golang內(nèi)置的一個(gè)包,這個(gè)包提供了對(duì)重復(fù)函數(shù)調(diào)用的抑制功能,也就是保證并發(fā)請(qǐng)求只會(huì)有一個(gè)實(shí)際請(qǐng)求去訪問資源,所有并發(fā)請(qǐng)求共享實(shí)際響應(yīng)。

使用

singleflight在golang sdk源碼中的路徑為:src/internal/singleflight

但是internal是golang sdk內(nèi)部的包,所以我們不能直接去使用

使用步驟:

  • 引入go mod
  • 使用singleflight包

引入go mod

go get golang.org/x/sync

使用singleflight包

singleflight包主要提供了三個(gè)方法

// 方法作用:保證并發(fā)請(qǐng)求只會(huì)執(zhí)行一次函數(shù),并共享實(shí)際響應(yīng)
// 請(qǐng)求參數(shù)
// key:請(qǐng)求的唯一標(biāo)識(shí),相同的key會(huì)被視為并發(fā)請(qǐng)求
// fn:實(shí)際需要執(zhí)行的函數(shù)
// 響應(yīng)參數(shù)
// v:實(shí)際執(zhí)行函數(shù)的返回值
// err:實(shí)際執(zhí)行函數(shù)的錯(cuò)誤
// shared:返回值v是否被共享,若存在并發(fā)請(qǐng)求,則為true;若不存在并發(fā)請(qǐng)求則為false
func (g *Group) Do(key string, fn func() (any, error)) (v any, err error, shared bool)
// 方法作用:和Do類似,不過方法返回的是chan
func (g *Group) DoChan(key string, fn func() (any, error)) (<-chan Result, bool)
// 方法作用:刪除key,一般來說不會(huì)直接使用這個(gè)方法
func (g *Group) ForgetUnshared(key string) bool

針對(duì)以上的三個(gè)方法,我們重點(diǎn)了解一下Do方法的使用即可

沒有使用singleflight之前

package main
import (
   "fmt"
   "sync"
   "testing"
   "time"
)
var (
   mx        sync.Mutex
   wg        sync.WaitGroup
   cacheData = make(map[string]string, 0)
)
func TestSingleFlight(t *testing.T) {
   // 添加10個(gè)任務(wù),模擬并發(fā)請(qǐng)求
   wg.Add(10)
   for i := 0; i < 10; i++ {
      go getData("demo")
   }
   // 等待所有任務(wù)完成
   wg.Wait()
}
func getData(key string) {
   data, _ := getDataFromCache(key)
   if len(data) == 0 {
      // 緩存沒有找到,則進(jìn)行回源
      data, _ = getDataFromDB(key)
      // 設(shè)置緩存
      mx.Lock()
      cacheData[key] = data
      mx.Unlock()
   }
   fmt.Println(data)
   // 任務(wù)完成
   wg.Done()
}
func getDataFromCache(key string) (string, error) {
   return cacheData[key], nil
}
func getDataFromDB(key string) (string, error) {
   fmt.Println("getDataFromDB key: ", key)
   // 模擬訪問db的耗時(shí)
   time.Sleep(10 * time.Millisecond)
   return "db data", nil
}

執(zhí)行TestSingleFlight函數(shù)后,會(huì)發(fā)現(xiàn)并發(fā)請(qǐng)求多次調(diào)用了getDataFromDB函數(shù)

使用singleflight之后

package main
import (
   "fmt"
   "golang.org/x/sync/singleflight"
   "sync"
   "testing"
   "time"
)
var (
   mx        sync.Mutex
   wg        sync.WaitGroup
   g         singleflight.Group
   cacheData = make(map[string]string, 0)
)
func TestSingleFlight(t *testing.T) {
   // 添加10個(gè)任務(wù)
   wg.Add(10)
   for i := 0; i < 10; i++ {
      go getDataSingleWarp("demo")
   }
   // 等待所有任務(wù)完成
   wg.Wait()
}
func getDataSingleWarp(key string) {
   data, _ := getDataFromCache(key)
   if len(data) == 0 {
      // 使用singleflight來避免并發(fā)請(qǐng)求,實(shí)際改動(dòng)就這一行
      d, _, shared := g.Do(key, func() (interface{}, error) {
         return getDataFromDB(key)
      })
      fmt.Println(shared)
      data = d.(string)
      // 設(shè)置緩存
      mx.Lock()
      cacheData[key] = data
      mx.Unlock()
   }
   fmt.Println(data)
   wg.Done()
}
func getDataFromCache(key string) (string, error) {
   return cacheData[key], nil
}
func getDataFromDB(key string) (string, error) {
   fmt.Println("getDataFromDB key: ", key)
   // 模擬訪問db的耗時(shí)
   time.Sleep(10 * time.Millisecond)
   return "db data", nil
}

執(zhí)行TestSingleFlight函數(shù)后,會(huì)發(fā)現(xiàn)只調(diào)用了一次getDataFromDB函數(shù)

源碼分析

  • Group struct:封裝并發(fā)請(qǐng)求
  • call struct:每一個(gè)需要執(zhí)行的函數(shù),都會(huì)被封裝成一個(gè)call
  • func Do:對(duì)并發(fā)請(qǐng)求進(jìn)行控制的方法
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package singleflight provides a duplicate function call suppression
// mechanism.
package singleflight // import "golang.org/x/sync/singleflight"
import (
   "bytes"
   "errors"
   "fmt"
   "runtime"
   "runtime/debug"
   "sync"
)
// errGoexit indicates the runtime.Goexit was called in
// the user given function.
var errGoexit = errors.New("runtime.Goexit was called")
// A panicError is an arbitrary value recovered from a panic
// with the stack trace during the execution of given function.
type panicError struct {
   value interface{}
   stack []byte
}
// Error implements error interface.
func (p *panicError) Error() string {
   return fmt.Sprintf("%v\n\n%s", p.value, p.stack)
}
func newPanicError(v interface{}) error {
   stack := debug.Stack()
   // The first line of the stack trace is of the form "goroutine N [status]:"
   // but by the time the panic reaches Do the goroutine may no longer exist
   // and its status will have changed. Trim out the misleading line.
   if line := bytes.IndexByte(stack[:], '\n'); line >= 0 {
      stack = stack[line+1:]
   }
   return &panicError{value: v, stack: stack}
}
// call is an in-flight or completed singleflight.Do call
type call struct {
   // 保證相同key,只會(huì)進(jìn)行一次實(shí)際請(qǐng)求
   // 相同key的并發(fā)請(qǐng)求會(huì)共享返回
   wg sync.WaitGroup
   // These fields are written once before the WaitGroup is done
   // and are only read after the WaitGroup is done.
   // 實(shí)際執(zhí)行函數(shù)的返回值和錯(cuò)誤
   val interface{}
   err error
   // forgotten indicates whether Forget was called with this call's key
   // while the call was still in flight.
   // 是否已刪除當(dāng)前并發(fā)請(qǐng)求的key
   forgotten bool
   // These fields are read and written with the singleflight
   // mutex held before the WaitGroup is done, and are read but
   // not written after the WaitGroup is done.
   // 并發(fā)請(qǐng)求的次數(shù)
   dups  int
   chans []chan<- Result
}
// Group represents a class of work and forms a namespace in
// which units of work can be executed with duplicate suppression.
type Group struct {
   mu sync.Mutex       // protects m
   // key代表請(qǐng)求的唯一標(biāo)識(shí),相同的key會(huì)被視為并發(fā)請(qǐng)求
   // value代表實(shí)際請(qǐng)求,每一個(gè)實(shí)際請(qǐng)求都會(huì)被封裝為call  
   m  map[string]*call // lazily initialized
}
// Result holds the results of Do, so they can be passed
// on a channel.
type Result struct {
   Val    interface{}
   Err    error
   Shared bool
}
// Do executes and returns the results of the given function, making
// sure that only one execution is in-flight for a given key at a
// time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
// The return value shared indicates whether v was given to multiple callers.
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
   // 加鎖
   g.mu.Lock()
   // 懶加載
   if g.m == nil {
      g.m = make(map[string]*call)
   }
   // 判斷是否有并發(fā)請(qǐng)求,如果key已經(jīng)存在,則說明存在并發(fā)請(qǐng)求
   if c, ok := g.m[key]; ok {
      // 并發(fā)請(qǐng)求次數(shù)+1
      c.dups++
      // 解鎖
      g.mu.Unlock()
      // 等待實(shí)際請(qǐng)求執(zhí)行完
      c.wg.Wait()
      if e, ok := c.err.(*panicError); ok {
         panic(e)
      } else if c.err == errGoexit {
         runtime.Goexit()
      }
      // 共享響應(yīng)
      return c.val, c.err, true
   }
   c := new(call)
   c.wg.Add(1)
   // 添加并發(fā)請(qǐng)求key
   g.m[key] = c
   // 解鎖
   g.mu.Unlock()
   // 進(jìn)行實(shí)際請(qǐng)求
   g.doCall(c, key, fn)
   return c.val, c.err, c.dups > 0
}
// DoChan is like Do but returns a channel that will receive the
// results when they are ready.
//
// The returned channel will not be closed.
func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result {
   ch := make(chan Result, 1)
   g.mu.Lock()
   if g.m == nil {
      g.m = make(map[string]*call)
   }
   if c, ok := g.m[key]; ok {
      c.dups++
      c.chans = append(c.chans, ch)
      g.mu.Unlock()
      return ch
   }
   c := &call{chans: []chan<- Result{ch}}
   c.wg.Add(1)
   g.m[key] = c
   g.mu.Unlock()
   go g.doCall(c, key, fn)
   return ch
}
// doCall handles the single call for a key.
func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
   // 正常返回標(biāo)識(shí)
   normalReturn := false
   // 是否執(zhí)行了recover標(biāo)識(shí)
   recovered := false
   // use double-defer to distinguish panic from runtime.Goexit,
   // more details see https://golang.org/cl/134395
   defer func() {
      // the given function invoked runtime.Goexit
      if !normalReturn && !recovered {
         c.err = errGoexit
      }
      // 實(shí)際請(qǐng)求執(zhí)行完成
      c.wg.Done()
      // 加鎖
      g.mu.Lock()
      defer g.mu.Unlock()
      // 刪除并發(fā)請(qǐng)求key
      if !c.forgotten {
         delete(g.m, key)
      }
      if e, ok := c.err.(*panicError); ok {
         // In order to prevent the waiting channels from being blocked forever,
         // needs to ensure that this panic cannot be recovered.
         if len(c.chans) > 0 {
            go panic(e)
            select {} // Keep this goroutine around so that it will appear in the crash dump.
         } else {
            panic(e)
         }
      } else if c.err == errGoexit {
         // Already in the process of goexit, no need to call again
      } else {
         // Normal return
         for _, ch := range c.chans {
            ch <- Result{c.val, c.err, c.dups > 0}
         }
      }
   }()
   // 匿名函數(shù)立即執(zhí)行
   func() {
      defer func() {
         if !normalReturn {
            // Ideally, we would wait to take a stack trace until we've determined
            // whether this is a panic or a runtime.Goexit.
            //
            // Unfortunately, the only way we can distinguish the two is to see
            // whether the recover stopped the goroutine from terminating, and by
            // the time we know that, the part of the stack trace relevant to the
            // panic has been discarded.
            if r := recover(); r != nil {
               c.err = newPanicError(r)
            }
         }
      }()
      // 執(zhí)行實(shí)際函數(shù)
      c.val, c.err = fn()
      // 正常返回
      normalReturn = true
   }()
   if !normalReturn {
      recovered = true
   }
}
// Forget tells the singleflight to forget about a key.  Future calls
// to Do for this key will call the function rather than waiting for
// an earlier call to complete.
func (g *Group) Forget(key string) {
   g.mu.Lock()
   if c, ok := g.m[key]; ok {
      c.forgotten = true
   }
   delete(g.m, key)
   g.mu.Unlock()
}

到此這篇關(guān)于Golang使用singleflight解決并發(fā)重復(fù)請(qǐng)求的文章就介紹到這了,更多相關(guān)Go singleflight內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go語言常見錯(cuò)誤之誤用init函數(shù)實(shí)例解析

    Go語言常見錯(cuò)誤之誤用init函數(shù)實(shí)例解析

    Go語言中的init函數(shù)為開發(fā)者提供了一種在程序正式運(yùn)行前初始化包級(jí)變量的機(jī)制,然而,由于init函數(shù)的特殊性,不當(dāng)?shù)厥褂盟赡芤鹨幌盗袉栴},本文將深入探討如何有效地使用init函數(shù),列舉常見誤用并提供相應(yīng)的避免策略
    2024-01-01
  • Go語言模型:string的底層數(shù)據(jù)結(jié)構(gòu)與高效操作詳解

    Go語言模型:string的底層數(shù)據(jù)結(jié)構(gòu)與高效操作詳解

    這篇文章主要介紹了Go語言模型:string的底層數(shù)據(jù)結(jié)構(gòu)與高效操作詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Go語言中的值類型與引用類型的使用

    Go語言中的值類型與引用類型的使用

    Go語言中的數(shù)據(jù)類型可以分為值類型和引用類型,值類型包括基本和復(fù)合數(shù)據(jù)類型,特點(diǎn)是在傳遞時(shí)進(jìn)行復(fù)制,保證數(shù)據(jù)的安全性,引用類型如slice、map等,傳遞的是引用副本,便于大型數(shù)據(jù)操作且提高性能,理解這些概念有助于編寫更高效、安全的Go代碼
    2024-09-09
  • golang實(shí)現(xiàn)nacos獲取配置和服務(wù)注冊(cè)-支持集群詳解

    golang實(shí)現(xiàn)nacos獲取配置和服務(wù)注冊(cè)-支持集群詳解

    文章介紹了如何在Go語言中使用Nacos獲取配置和服務(wù)注冊(cè),支持集群初始化,客戶端結(jié)構(gòu)體中的IpAddresses可以配置多個(gè)地址,新客戶端支持集群配置,文章還討論了如何將Nacos獲取到的配置與項(xiàng)目中的Viper無縫對(duì)接,以便進(jìn)行Viper的正常操作
    2025-11-11
  • 使用Go語言實(shí)現(xiàn)簡單聊天系統(tǒng)

    使用Go語言實(shí)現(xiàn)簡單聊天系統(tǒng)

    本文介紹了如何使用Go語言和WebSocket技術(shù)構(gòu)建一個(gè)簡單的多人聊天室系統(tǒng),包括客戶端連接管理、消息廣播和并發(fā)處理,最后,通過編寫main.go、hub.go和client.go等核心代碼模塊,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-10-10
  • go語法入門泛型type?parameters簡稱T(類型形參)兩種場(chǎng)景使用

    go語法入門泛型type?parameters簡稱T(類型形參)兩種場(chǎng)景使用

    這篇文章主要為大家介紹了go語法入門泛型type?parameters簡稱T(類型形參)兩種場(chǎng)景使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Go語言中不可不知的語法糖盤點(diǎn)

    Go語言中不可不知的語法糖盤點(diǎn)

    Go?語言有一些非常實(shí)用的語法糖(syntactic?sugar),它們使得代碼更加簡潔和易讀,本文為大家整理了15個(gè)常見的語法糖,有需要的可以了解下
    2025-01-01
  • Golang?pprof性能測(cè)試與分析講解

    Golang?pprof性能測(cè)試與分析講解

    剛開始接觸go就遇到了一個(gè)內(nèi)存問題,在進(jìn)行內(nèi)存分析的時(shí)候發(fā)現(xiàn)了一下比較好的工具,在此留下記錄,下面這篇文章主要給大家介紹了關(guān)于go性能分析工具pprof的性能測(cè)試,需要的朋友可以參考下
    2023-04-04
  • viper配置框架的介紹支持zookeeper的讀取和監(jiān)聽

    viper配置框架的介紹支持zookeeper的讀取和監(jiān)聽

    這篇文章主要介紹了viper配置框架的介紹支持zookeeper的讀取和監(jiān)聽,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-05-05
  • golang?gorm的Callbacks事務(wù)回滾對(duì)象操作示例

    golang?gorm的Callbacks事務(wù)回滾對(duì)象操作示例

    這篇文章主要為大家介紹了golang?gorm的Callbacks事務(wù)回滾對(duì)象操作示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04

最新評(píng)論

广灵县| 民权县| 甘德县| 塔城市| 长治市| 富宁县| 汉川市| 乌审旗| 博兴县| 延寿县| 新泰市| 滦平县| 万宁市| 海伦市| 宜兰县| 名山县| 揭西县| 揭西县| 微山县| 鹰潭市| 乐都县| 介休市| 慈溪市| 尖扎县| 岳池县| 蒙山县| 新巴尔虎右旗| 灌云县| 高尔夫| 正蓝旗| 沧州市| 马关县| 永嘉县| 林周县| 四子王旗| 焉耆| 丹寨县| 夏河县| 武山县| 吉木乃县| 庄浪县|