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

go?singleflight緩存雪崩源碼分析與應(yīng)用

 更新時(shí)間:2023年09月22日 08:40:47   作者:海生  
這篇文章主要為大家介紹了go?singleflight緩存雪崩源碼分析與應(yīng)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

一、緩存雪崩的應(yīng)用

背景:

我們?cè)谥貑od的時(shí)候,此時(shí)會(huì)導(dǎo)致gocache中重啟,然后緩存同時(shí)大批量失效。如果此時(shí)并發(fā)比較高,會(huì)有很多goroutine,去同時(shí)訪問(wèn)redis。

加單飛,將一組相同的請(qǐng)求合并成一個(gè)請(qǐng)求,實(shí)際上只會(huì)去請(qǐng)求一次,然后對(duì)所有的請(qǐng)求返回相同的結(jié)果

singlefight實(shí)驗(yàn):

singlefight_test.go

需要重新從redis獲取數(shù)據(jù)存取到 gocache。

func BenchmarkUse(b *testing.B) {
    ctx := context.Background()
    wordTouchRedisClient.Set(ctx, "k", "v", time.Second*600)
    goCache := cache.New(time.Second*60, time.Second*60)
    //sg := singleflight.Group{}
    for i := 0; i < b.N; i++ {
       _, ok := goCache.Get("k")
       if !ok {
          go func() {
             //_, _, _ = sg.Do("k", func() (interface{}, error) {
             v, _ := wordTouchRedisClient.Get(ctx, "k").Result()
             goCache.Set("k", v, time.Second*60)
             //return v, nil
             //})
          }()
       }
    }
}
BenchmarkUse-8              94518             20173 ns/op

此時(shí)引入單飛

func BenchmarkUse(b *testing.B) {
    ctx := context.Background()
    wordTouchRedisClient.Set(ctx, "k", "v", time.Second*600)
    goCache := cache.New(time.Second*60, time.Second*60)
    sg := singleflight.Group{}
    for i := 0; i < b.N; i++ {
       _, ok := goCache.Get("k")
       if !ok {
          go func() {
             _, _, _ = sg.Do("k", func() (interface{}, error) {
                v, _ := wordTouchRedisClient.Get(ctx, "k").Result()
                goCache.Set("k", v, time.Second*60)
                return v, nil
             })
          }()
       }
    }
}
BenchmarkUse-8           21307608                46.96 ns/op
BenchmarkUse-2           25675206                45.37 ns/op

風(fēng)險(xiǎn):

  • 如果一個(gè)報(bào)錯(cuò), 同一批都報(bào)錯(cuò)

二、源碼分析

源碼注釋

// 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.
// singleflight包提供了重復(fù)函數(shù)調(diào)用抑制機(jī)制。
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.
// errGoexit 表示 runtime.Goexit 被用戶(hù)的函數(shù)調(diào)用了
var errGoexit = errors.New("runtime.Goexit was called")
// A panicError is an arbitrary value recovered from a panic
// panicError 是從panic中 恢復(fù)的任意值
// with the stack trace during the execution of given function.
// 執(zhí)行給定函數(shù)期間的堆棧跟蹤
type panicError struct {
    value interface{}
    stack []byte
}
// Error implements error interface.
// Error 實(shí)現(xiàn)錯(cuò)誤接口
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]:"
    // 堆棧跟蹤的第一行的形式為“goroutine N [status]:”
    // but by the time the panic reaches Do the goroutine may no longer exist
    // 但當(dāng)panic達(dá)到 Do 時(shí),goroutine 可能不再存在
    // and its status will have changed. Trim out the misleading line.
    // 并且它的狀態(tài)將會(huì)改變。修剪掉誤導(dǎo)性的線條。
    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
// call 是正在進(jìn)行的或已完成的 singleflight.Do() 調(diào)用
type call struct {
    wg sync.WaitGroup
    // These fields are written once before the WaitGroup is done
    // 這些字段在 WaitGroup 完成之前寫(xiě)入一次
    // and are only read after the WaitGroup is done.
    // 并且僅在 WaitGroup 完成后才讀取。
    val interface{}
    err error
    // These fields are read and written with the singleflight
    // 這些字段是用 singleflight mutex  讀寫(xiě)的
    // mutex held before the WaitGroup is done, and are read but
    //  在 WaitGroup完成前。
    // not written after the WaitGroup is done.
    // 并且 只讀不寫(xiě),在WaitGroup完成后。
    dups  int
    chans []chan<- Result
}
// Group represents a class of work and forms a namespace in
// Group 代表一個(gè)工作類(lèi),并在其中形成一個(gè)命名空間
// which units of work can be executed with duplicate suppression.
// 哪些工作單元可以通過(guò)重復(fù)抑制來(lái)執(zhí)行。
type Group struct {
    mu sync.Mutex       // protects m 用來(lái)保護(hù)m,并發(fā)安全
    m  map[string]*call // lazily initialized  延遲初始化
}
// Result holds the results of Do, so they can be passed
// Result保存了Do的結(jié)果,因此可以傳遞
// on a channel.
// 在通道上
type Result struct {
    Val    interface{}
    Err    error
    Shared bool
}
// Do executes and returns the results of the given function, 
// Do 執(zhí)行并返回給定函數(shù)的結(jié)果
// making sure that only one execution is in-flight for a given key at a time. 
// 確保在某一時(shí)刻對(duì)于給定的鍵只有一次正在執(zhí)行
// If a duplicate comes in, the duplicate caller waits for the original
// 如果有重復(fù)的調(diào)用者進(jìn)入,則重復(fù)的調(diào)用者將等待最初者
// to complete and receives the same results.
// 完成并收到相同的結(jié)果。
// The return value shared indicates whether v was given to multiple callers.
// 返回值shared表示v是否被給予多個(gè)調(diào)用者。
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)
    }
    if c, ok := g.m[key]; ok {
       c.dups++
       g.mu.Unlock()
       c.wg.Wait()
       if e, ok := c.err.(*panicError); ok {
          panic(e)
       } else if c.err == errGoexit {
          runtime.Goexit()
       }
       return c.val, c.err, true
    }
    c := new(call)
    c.wg.Add(1)
    g.m[key] = c
    g.mu.Unlock()
    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.
// DoChan 與 Do 類(lèi)似,但返回一個(gè)chanel通道 接收準(zhǔn)備好后的結(jié)果。
//
// The returned channel will not be closed.
// 返回的channel通道不會(huì)被關(guān)閉。
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.
// doCall 處理對(duì)key的單個(gè)調(diào)用。
func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
    normalReturn := false
    recovered := false
    // use double-defer to distinguish panic from runtime.Goexit,
    // 使用雙重延遲 來(lái)區(qū)分panic和runtime.Goexit,
    // more details see https://golang.org/cl/134395
    // 更多詳情參見(jiàn) https://golang.org/cl/134395
    defer func() {
       // the given function invoked runtime.Goexit
       // 調(diào)用給定函數(shù)runtime.Goexit
       if !normalReturn && !recovered {
          c.err = errGoexit
       }
       g.mu.Lock()
       defer g.mu.Unlock()
       c.wg.Done()
       if g.m[key] == c {
          delete(g.m, key)
       }
       if e, ok := c.err.(*panicError); ok {
          // In order to prevent the waiting channels from being blocked forever,
          // 為了防止等待通道永遠(yuǎn)被阻塞,
          // needs to ensure that this panic cannot be recovered.
          // 需要確保這種panic恐慌無(wú)法恢復(fù)。
          if len(c.chans) > 0 {
             go panic(e)
             select {} // Keep this goroutine around so that it will appear in the crash dump.
                       // 保留此 goroutine,以便它出現(xiàn)在故障轉(zhuǎn)儲(chǔ)中。  
          } else {
             panic(e)
          }
       } else if c.err == errGoexit {
          // Already in the process of goexit, no need to call again
          // 已經(jīng)在goexit過(guò)程中,無(wú)需再次調(diào)用
       } else {
          // Normal return
          // 正常返回
          for _, ch := range c.chans {
             ch <- Result{c.val, c.err, c.dups > 0}
          }
       }
    }()
    func() {
       defer func() {
          if !normalReturn {
             // Ideally, we would wait to take a stack trace until we've determined
             // 理想情況下,我們會(huì)等待獲取堆棧跟蹤,直到我們確定
             // whether this is a panic or a runtime.Goexit.
             // 這是恐慌還是runtime.Goexit。
             //
             // Unfortunately, the only way we can distinguish the two is to see
             // 不幸的是,我們區(qū)分兩者的唯一方法就是看
             // whether the recover stopped the goroutine from terminating, and by
             // 恢復(fù)是否阻止 goroutine 終止,并且通過(guò)
             // the time we know that, the part of the stack trace relevant to the
             // 當(dāng)我們知道時(shí),堆棧跟蹤中與
             // panic has been discarded.
             // 恐慌已被丟棄。
             if r := recover(); r != nil {
                c.err = newPanicError(r)
             }
          }
       }()
       c.val, c.err = fn()
       normalReturn = true
    }()
    if !normalReturn {
       recovered = true
    }
}
// Forget tells the singleflight to forget about a key.  Future calls
// Forget 告訴 singleflight 忘記某個(gè)鍵。未來(lái)的calls調(diào)用
// to Do for this key will call the function rather than waiting for
// 為此鍵執(zhí)行的操作將調(diào)用該函數(shù)而不是等待
// an earlier call to complete.
// 較早的調(diào)用完成。
func (g *Group) Forget(key string) {
    g.mu.Lock()
    delete(g.m, key)
    g.mu.Unlock()
}

并發(fā)情況下的goroutine執(zhí)行情況

func BenchmarkUse(b *testing.B) {
    ctx := context.Background()
    wordTouchRedisClient.Set(ctx, "k", "v", time.Second*600)
    goCache := cache.New(time.Second*60, time.Second*60)
    sg := singleflight.Group{}
    for i := 0; i < b.N; i++ {
       _, ok := goCache.Get("k")
       if !ok {
          go func() {
             _, _, _ = sg.Do("k", func() (interface{}, error) {
                v, _ := wordTouchRedisClient.Get(ctx, "k").Result()
                goCache.Set("k", v, time.Second*60)
                return v, nil
             })
          }()
       }
    }
}

如圖表展示

就是在第一個(gè) 子goroutine的從開(kāi)始到結(jié)束,啟動(dòng)的 其余子goroutine,都和第一個(gè)goroutine,都擁有相同的call,為同一個(gè)group。然后返回同樣的結(jié)果。

第一個(gè)子goroutine,結(jié)束完,就刪掉key,然后在下面的goroutine,為新的一組。

以上就是go singleflight緩存雪崩源碼分析與應(yīng)用的詳細(xì)內(nèi)容,更多關(guān)于go singleflight緩存雪崩的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • go使用Gin框架利用阿里云實(shí)現(xiàn)短信驗(yàn)證碼功能

    go使用Gin框架利用阿里云實(shí)現(xiàn)短信驗(yàn)證碼功能

    這篇文章主要介紹了go使用Gin框架利用阿里云實(shí)現(xiàn)短信驗(yàn)證碼,使用json配置文件及配置文件解析,編寫(xiě)路由controller層,本文通過(guò)代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2021-08-08
  • 淺析Go 字符串指紋

    淺析Go 字符串指紋

    這篇文章主要介紹了Go 字符串指紋的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)go語(yǔ)言,感興趣的朋友可以了解下
    2020-09-09
  • Go語(yǔ)言LeetCode題解1046最后一塊石頭的重量

    Go語(yǔ)言LeetCode題解1046最后一塊石頭的重量

    這篇文章主要為大家介紹了Go語(yǔ)言LeetCode題解1046最后一塊石頭的重量,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • Go變量作用域代碼實(shí)戰(zhàn)詳解

    Go變量作用域代碼實(shí)戰(zhàn)詳解

    Go語(yǔ)言提供了幾種不同的作用域類(lèi)型,使得開(kāi)發(fā)者可以靈活地控制變量的可見(jiàn)范圍和生命周期,本章節(jié)將詳細(xì)概述Go語(yǔ)言中變量的各種作用域,幫助讀者更好地理解和應(yīng)用這些概念,需要的朋友可以參考下
    2024-06-06
  • 解決Golang在Web開(kāi)發(fā)時(shí)前端莫名出現(xiàn)的空白換行

    解決Golang在Web開(kāi)發(fā)時(shí)前端莫名出現(xiàn)的空白換行

    最近在使用Go語(yǔ)言開(kāi)發(fā)Web時(shí),在前端莫名出現(xiàn)了空白換行,找了網(wǎng)上的一些資料終于找到了解決方法,現(xiàn)在分享給大家,有需要的可以參考。
    2016-08-08
  • Go語(yǔ)言?xún)?nèi)建函數(shù)len的使用

    Go語(yǔ)言?xún)?nèi)建函數(shù)len的使用

    Go語(yǔ)言中的len函數(shù)是一個(gè)內(nèi)建函數(shù),用于獲取數(shù)組、切片、字符串、映射和通道等數(shù)據(jù)類(lèi)型的長(zhǎng)度或大小,本文介紹了len函數(shù)在不同數(shù)據(jù)類(lèi)型中的使用場(chǎng)景和特點(diǎn),感興趣的可以了解一下
    2024-10-10
  • Golang pipe在不同場(chǎng)景下遠(yuǎn)程交互

    Golang pipe在不同場(chǎng)景下遠(yuǎn)程交互

    這篇文章主要介紹了Golang pipe在不同場(chǎng)景下遠(yuǎn)程交互,pipe實(shí)現(xiàn)從一個(gè)進(jìn)程重定向至另一個(gè)進(jìn)程,它是雙向數(shù)據(jù)通道,用于實(shí)現(xiàn)進(jìn)行間通信
    2023-03-03
  • Go實(shí)現(xiàn)比較時(shí)間大小

    Go實(shí)現(xiàn)比較時(shí)間大小

    這篇文章主要介紹了Go實(shí)現(xiàn)比較時(shí)間大小的方法和示例,非常的簡(jiǎn)單實(shí)用,有需要的小伙伴可以參考下。
    2015-04-04
  • Go語(yǔ)言中的通道channel詳情

    Go語(yǔ)言中的通道channel詳情

    這篇文章主要介紹了Go語(yǔ)言中的通道channel,在Go語(yǔ)言中管道類(lèi)似于一個(gè)數(shù)據(jù)流,每次放入或者取出一部分?jǐn)?shù)據(jù),數(shù)據(jù)取出后原通道內(nèi)的數(shù)據(jù)就刪除掉,在linux操作系統(tǒng)中管道會(huì)將函數(shù)的返回結(jié)果作為下一個(gè)函數(shù)的參數(shù),下文詳細(xì)內(nèi)容需要的朋友可以參考下
    2022-02-02
  • 使用Go語(yǔ)言實(shí)現(xiàn)向文件寫(xiě)入內(nèi)容的示例代碼

    使用Go語(yǔ)言實(shí)現(xiàn)向文件寫(xiě)入內(nèi)容的示例代碼

    向文件寫(xiě)入內(nèi)容 的完整內(nèi)容,這是文件操作的另一個(gè)核心技能,與讀取配套,適用于日志記錄、生成報(bào)告、寫(xiě)配置等場(chǎng)景,本文通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下
    2025-07-07

最新評(píng)論

油尖旺区| 溧阳市| 托克逊县| 金秀| 紫金县| 泰州市| 湖口县| 本溪| 绥棱县| 双鸭山市| 迭部县| 庆云县| 赤峰市| 玉树县| 环江| 朔州市| 堆龙德庆县| 阳江市| 丰镇市| 廊坊市| 鄂托克旗| 江陵县| 团风县| 江都市| 蚌埠市| 洛川县| 固安县| 桂林市| 五家渠市| 黄大仙区| 东海县| 通河县| 阿拉善右旗| 武汉市| 英德市| 竹山县| 繁昌县| 交城县| 赤城县| 墨竹工卡县| 唐海县|