日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

go-zero微服務(wù)框架中提供了許多開箱即用的工具,好的工具不僅能提升服務(wù)的性能而且還能提升代碼的魯棒性避免出錯,實現(xiàn)代碼風(fēng)格的統(tǒng)一方便他人閱讀等等。

本文主要講述進程內(nèi)共享調(diào)用神器SharedCalls。

使用場景

并發(fā)場景下,可能會有多個線程(協(xié)程)同時請求同一份資源,如果每個請求都要走一遍資源的請求過程,除了比較低效之外,還會對資源服務(wù)造成并發(fā)的壓力。舉一個具體例子,比如緩存失效,多個請求同時到達某服務(wù)請求某資源,該資源在緩存中已經(jīng)失效,此時這些請求會繼續(xù)訪問DB做查詢,會引起數(shù)據(jù)庫壓力瞬間增大。而使用SharedCalls可以使得同時多個請求只需要發(fā)起一次拿結(jié)果的調(diào)用,其他請求"坐享其成",這種設(shè)計有效減少了資源服務(wù)的并發(fā)壓力,可以有效防止緩存擊穿。

高并發(fā)場景下,當(dāng)某個熱點key緩存失效后,多個請求會同時從數(shù)據(jù)庫加載該資源,并保存到緩存,如果不做防范,可能會導(dǎo)致數(shù)據(jù)庫被直接打死。針對這種場景,go-zero框架中已經(jīng)提供了實現(xiàn),具體可參看sqlc和mongoc等實現(xiàn)代碼。

為了簡化演示代碼,我們通過多個線程同時去獲取一個id來模擬緩存的場景。如下:

func main() {
  const round = 5
  var wg sync.WaitGroup
  barrier := syncx.NewSharedCalls()

  wg.Add(round)
  for i := 0; i < round; i++ {
    // 多個線程同時執(zhí)行
    go func() {
      defer wg.Done()
      // 可以看到,多個線程在同一個key上去請求資源,獲取資源的實際函數(shù)只會被調(diào)用一次
      val, err := barrier.Do("once", func() (interface{}, error) {
        // sleep 1秒,為了讓多個線程同時取once這個key上的數(shù)據(jù)
        time.Sleep(time.Second)
        // 生成了一個隨機的id
        return stringx.RandId(), nil
      })
      if err != nil {
        fmt.Println(err)
      } else {
        fmt.Println(val)
      }
    }()
  }

  wg.Wait()
}

運行,打印結(jié)果為:

837c577b1008a0db
837c577b1008a0db
837c577b1008a0db
837c577b1008a0db
837c577b1008a0db

可以看出,只要是同一個key上的同時發(fā)起的請求,都會共享同一個結(jié)果,對獲取DB數(shù)據(jù)進緩存等場景特別有用,可以有效防止緩存擊穿。

關(guān)鍵源碼分析

  • SharedCalls interface提供了Do和DoEx兩種方法的抽象
// SharedCalls接口提供了Do和DoEx兩種方法
type SharedCalls interface {
  Do(key string, fn func() (interface{}, error)) (interface{}, error)
  DoEx(key string, fn func() (interface{}, error)) (interface{}, bool, error)
}
  • SharedCalls interface的具體實現(xiàn)sharedGroup
// call代表對指定資源的一次請求
type call struct {
  wg  sync.WaitGroup  // 用于協(xié)調(diào)各個請求goroutine之間的資源共享
  val interface{}     // 用于保存請求的返回值
  err error           // 用于保存請求過程中發(fā)生的錯誤
}

type sharedGroup struct {
  calls map[string]*call
  lock  sync.Mutex
}
  • sharedGroup的Do方法
    • key參數(shù):可以理解為資源的唯一標識。
    • fn參數(shù):真正獲取資源的方法。
    • 處理過程分析:
// 當(dāng)多個請求同時使用Do方法請求資源時
func (g *sharedGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
  // 先申請加鎖
  g.lock.Lock()

  // 根據(jù)key,獲取對應(yīng)的call結(jié)果,并用變量c保存
  if c, ok := g.calls[key]; ok {
    // 拿到call以后,釋放鎖,此處call可能還沒有實際數(shù)據(jù),只是一個空的內(nèi)存占位
    g.lock.Unlock()
    // 調(diào)用wg.Wait,判斷是否有其他goroutine正在申請資源,如果阻塞,說明有其他goroutine正在獲取資源
    c.wg.Wait()
    // 當(dāng)wg.Wait不再阻塞,表示資源獲取已經(jīng)結(jié)束,可以直接返回結(jié)果
    return c.val, c.err
  }

  // 沒有拿到結(jié)果,則調(diào)用makeCall方法去獲取資源,注意此處仍然是鎖住的,可以保證只有一個goroutine可以調(diào)用makecall
  c := g.makeCall(key, fn)
  // 返回調(diào)用結(jié)果
  return c.val, c.err
}
  • sharedGroup的DoEx方法
    • 和Do方法類似,只是返回值中增加了布爾值表示值是調(diào)用makeCall方法直接獲取的,還是取的共享成果
func (g *sharedGroup) DoEx(key string, fn func() (interface{}, error)) (val interface{}, fresh bool, err error) {
  g.lock.Lock()
  if c, ok := g.calls[key]; ok {
    g.lock.Unlock()
    c.wg.Wait()
    return c.val, false, c.err
  }

  c := g.makeCall(key, fn)
  return c.val, true, c.err
}
  • sharedGroup的makeCall方法
    • 該方法由Do和DoEx方法調(diào)用,是真正發(fā)起資源請求的方法。
// 進入makeCall的一定只有一個goroutine,因為要拿鎖鎖住的
func (g *sharedGroup) makeCall(key string, fn func() (interface{}, error)) *call {
  // 創(chuàng)建call結(jié)構(gòu),用于保存本次請求的結(jié)果
  c := new(call)
  // wg加1,用于通知其他請求資源的goroutine等待本次資源獲取的結(jié)束
  c.wg.Add(1)
  // 將用于保存結(jié)果的call放入map中,以供其他goroutine獲取
  g.calls[key] = c
  // 釋放鎖,這樣其他請求的goroutine才能獲取call的內(nèi)存占位
  g.lock.Unlock()

  defer func() {
    // delete key first, done later. can't reverse the order, because if reverse,
    // another Do call might wg.Wait() without get notified with wg.Done()
    g.lock.Lock()
    delete(g.calls, key)
    g.lock.Unlock()

    // 調(diào)用wg.Done,通知其他goroutine可以返回結(jié)果,這樣本批次所有請求完成結(jié)果的共享
    c.wg.Done()
  }()

  // 調(diào)用fn方法,將結(jié)果填入變量c中
  c.val, c.err = fn()
  return c
}

最后

本文主要介紹了go-zero框架中的 SharedCalls工具,對其應(yīng)用場景和關(guān)鍵代碼做了簡單的梳理,希望本篇文章能給大家?guī)硪恍┦斋@。

分享到:
標簽:擊穿 緩存
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定