激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Golang - 泛型版 Singleflight:Go 中如何防止緩存擊穿?

泛型版 Singleflight:Go 中如何防止緩存擊穿?

2021-12-31 23:20polarisxu站長polaris Golang

并發是 Go 的優勢,但并發也需要很好的進行控制。標準庫中有 sync 包,經常使用的功能有 sync.Mutex、sync.WaitGroup 等。其實,除了標準庫,還有一個官方的擴展庫,也叫 sync,其中有一個子包:sync/singleflight,專門做并發控制,比如防

泛型版 Singleflight:Go 中如何防止緩存擊穿?

大家好,我是 polarisxu。

并發是 Go 的優勢,但并發也需要很好的進行控制。標準庫中有 sync 包,經常使用的功能有 sync.Mutex、sync.WaitGroup 等。其實,除了標準庫,還有一個官方的擴展庫,也叫 sync,其中有一個子包:sync/singleflight,專門做并發控制,比如防止緩存擊穿。

01 從例子說起

看一個模擬緩存的例子,有如下代碼:

  1. package main
  2. import (
  3. "errors"
  4. "flag"
  5. "log"
  6. "sync"
  7. )
  8. var errorNotExist = errors.New("not exist")
  9. var n int
  10. func init() {
  11. flag.IntVar(&n, "n", 5, "模擬的并發數,默認 5")
  12. }
  13. func main() {
  14. flag.Parse()
  15. var wg sync.WaitGroup
  16. wg.Add(n)
  17. // 模擬并發訪問
  18. for i := 0; i < n; i++ {
  19. go func() {
  20. defer wg.Done()
  21. // 假設都獲取 id = 1 這篇文章
  22. article := fetchArticle(1)
  23. log.Println(article)
  24. }()
  25. }
  26. wg.Wait()
  27. }
  28. type Article struct {
  29. ID int
  30. Content string
  31. }
  32. func fetchArticle(id int) *Article {
  33. article := findArticleFromCache(id)
  34. if article != nil && article.ID > 0 {
  35. return article
  36. }
  37. return findArticleFromDB(id)
  38. }
  39. var (
  40. cache = make(map[int]*Article)
  41. rwmutex sync.RWMutex
  42. )
  43. // 模擬從緩存獲取數據
  44. func findArticleFromCache(id int) *Article {
  45. rwmutex.RLock()
  46. defer rwmutex.RUnlock()
  47. return cache[id]
  48. }
  49. // 模擬從數據庫中獲取數據
  50. func findArticleFromDB(id int) *Article {
  51. log.Printf("SELECT * FROM article WHERE id=%d", id)
  52. article := &Article{ID: id, Content: "polarisxu"}
  53. rwmutex.Lock()
  54. defer rwmutex.Unlock()
  55. cache[id] = article
  56. return article
  57. }

我們模擬 5 個用戶并發訪問,同時獲取 ID=1 的文章,因為緩存中不存在,因此都到后端 DB 獲取具體數據。從運行結果可以看出這一點:

  1. $ go run main.go
  2. 2021/12/30 10:32:36 SELECT * FROM article WHERE id=1
  3. 2021/12/30 10:32:36 SELECT * FROM article WHERE id=1
  4. 2021/12/30 10:32:36 &{1 polarisxu}
  5. 2021/12/30 10:32:36 &{1 polarisxu}
  6. 2021/12/30 10:32:36 SELECT * FROM article WHERE id=1
  7. 2021/12/30 10:32:36 &{1 polarisxu}
  8. 2021/12/30 10:32:36 SELECT * FROM article WHERE id=1
  9. 2021/12/30 10:32:36 &{1 polarisxu}
  10. 2021/12/30 10:32:36 SELECT * FROM article WHERE id=1
  11. 2021/12/30 10:32:36 &{1 polarisxu}

顯然這是我們不希望看到的。

02 使用 singleflight

官方的擴展包 golang.org/x/sync 下面有一個子包 singleflight:

  1. Package singleflight provides a duplicate function call suppression mechanism.

它用來抑制函數的重復調用,這正好符合上面的場景:希望從數據庫獲取數據的函數只調用一次。

將 fetchArticle 函數改成這樣:

  1. var g singleflight.Group
  2. func fetchArticle(id int) *Article {
  3. article := findArticleFromCache(id)
  4. if article != nil && article.ID > 0 {
  5. return article
  6. }
  7. v, err, shared := g.Do(strconv.Itoa(id), func() (interface{}, error) {
  8. return findArticleFromDB(id), nil
  9. })
  10. // 打印 shared,看看都什么值
  11. fmt.Println("shared===", shared)
  12. if err != nil {
  13. log.Println("singleflight do error:", err)
  14. return nil
  15. }
  16. return v.(*Article)
  17. }

singleflight.Group 是一個結構體類型,沒有導出任何字段,它代表一類工作并形成一個命名空間,在該命名空間中可以抑制工作單元的重復執行。

該類型有三個方法,它們的功能見注釋:

  1. // 執行并返回給定函數的結果,確保對于給定的鍵,fn 函數只會執行一次。
  2. // 如果有重復的進來,重復的調用者會等待最原始的調用完成并收到相同的結果。
  3. // 返回值 shared 指示是否將 v 提供給多個調用者。
  4. // 返回值 v 是 fn 的執行結果
  5. // 返回值 err 是 fn 返回的 err
  6. func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool)
  7. // 和 Do 類似,但返回一個 channel(只能接收),用來接收結果。Result 是一個結構體,有三個字段,即 Do 返回的那三個。
  8. func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result
  9. func (g *Group) Forget(key string)

因此,改后的代碼,通過 Group.Do,即使并發多次調用,findArticleFromDB 也只會執行一次,并且這一次的結果會被并發多次執行共享。

運行后,結果如下:

  1. $ go run main.go
  2. 2021/12/30 11:55:44 SELECT * FROM article WHERE id=1
  3. shared=== true
  4. 2021/12/30 11:55:44 &{1 polarisxu}
  5. shared=== true
  6. 2021/12/30 11:55:44 &{1 polarisxu}
  7. shared=== true
  8. 2021/12/30 11:55:44 &{1 polarisxu}
  9. shared=== true
  10. 2021/12/30 11:55:44 &{1 polarisxu}
  11. shared=== true
  12. 2021/12/30 11:55:44 &{1 polarisxu}

和預期一樣,findArticleFromDB 只執行了一次,shared 的值也表示結果被多個調用者共享。

所以,使用 Go 后,再也不需要通過類似 Redis 中的 SETNX 這樣的命令來實現類似的功能了。

03 Forget 的用途

上面 Group 的方法中,有一個沒有給任何注釋,即 Forget。從名字猜到,用來忘掉什么,那具體什么意思呢?

通過上面的例子,我們知曉,通過 Do,可以實現多個并發調用只執行回調函數一次,并共享相同的結果。而 Forget 的作用是:

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.

即告訴 singleflight 忘記一個 key,未來對此 key 的 Do 調用將調用 fn 回調函數,而不是等待更早的調用完成,即相當于廢棄 Do 原本的作用。

可以在上面例子中 Do 調用之前,調用 g.Forget,驗證是否 Do 的調用都執行 fn 函數即 findArticleFromDB 函數了。

04 泛型版本

細心的讀者可能會發現,Do 方法返回的 v 是 interface{},在 fetchArticle 函數最后,我們做了類型斷言:v.(*Article)。

既然 Go1.18 馬上要來了,有了泛型,可以有泛型版本的 singleflight,不需要做類型斷言了。GitHub 已經有人實現并開源:https://github.com/marwan-at-work/singleflight。

改成這個泛型版本,要改以下幾處:

  • 導入包 marwan.io/singleflight,而非 github.com/marwan-at-work/singleflight,同時移除 golang.org/x/sync/singleflight
  • g 的聲明改為:var g singleflight.Group[*Article]
  • Do 的調用,返回值由 interface{} 類型改為:*Article:
  1. article, err, shared := g.Do(strconv.Itoa(id), func() (*Article, error) {
  2. return findArticleFromDB(id), nil
  3. })
  • 最后返回時,直接返回 article,不需要做類型斷言

05 總結

singleflight 很常用,你在 pkg.go.dev 搜索 singleflight,發現有很多輪子:https://pkg.go.dev/search?q=singleflight,好些項目不是使用官方的 golang.org/x/sync/singleflight,而是自己實現一個,不過這些實現基本只實現了最常用的 Do 方法。感興趣的可以查看他們的實現。

下次項目中需要類似功能,記得使用 singleflight 哦!

原文鏈接:https://mp.weixin.qq.com/s/zwTErb_kiEEsurscYQ6eRw

延伸 · 閱讀

精彩推薦
  • Golanggo日志系統logrus顯示文件和行號的操作

    go日志系統logrus顯示文件和行號的操作

    這篇文章主要介紹了go日志系統logrus顯示文件和行號的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    SmallQinYan12302021-02-02
  • Golanggolang如何使用struct的tag屬性的詳細介紹

    golang如何使用struct的tag屬性的詳細介紹

    這篇文章主要介紹了golang如何使用struct的tag屬性的詳細介紹,從例子說起,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看...

    Go語言中文網11352020-05-21
  • Golanggolang 通過ssh代理連接mysql的操作

    golang 通過ssh代理連接mysql的操作

    這篇文章主要介紹了golang 通過ssh代理連接mysql的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    a165861639710342021-03-08
  • Golanggolang json.Marshal 特殊html字符被轉義的解決方法

    golang json.Marshal 特殊html字符被轉義的解決方法

    今天小編就為大家分享一篇golang json.Marshal 特殊html字符被轉義的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧 ...

    李浩的life12792020-05-27
  • Golanggo語言制作端口掃描器

    go語言制作端口掃描器

    本文給大家分享的是使用go語言編寫的TCP端口掃描器,可以選擇IP范圍,掃描的端口,以及多線程,有需要的小伙伴可以參考下。 ...

    腳本之家3642020-04-25
  • Golanggolang的httpserver優雅重啟方法詳解

    golang的httpserver優雅重啟方法詳解

    這篇文章主要給大家介紹了關于golang的httpserver優雅重啟的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,...

    helight2992020-05-14
  • GolangGolang通脈之數據類型詳情

    Golang通脈之數據類型詳情

    這篇文章主要介紹了Golang通脈之數據類型,在編程語言中標識符就是定義的具有某種意義的詞,比如變量名、常量名、函數名等等,Go語言中標識符允許由...

    4272021-11-24
  • GolangGolang中Bit數組的實現方式

    Golang中Bit數組的實現方式

    這篇文章主要介紹了Golang中Bit數組的實現方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    天易獨尊11682021-06-09
主站蜘蛛池模板: 欧美视屏一区二区 | 中国女人内谢69xxxx天美 | 爽毛片| 国产一区二区成人在线 | 亚洲国产精品久久久久久久久 | 亚洲成人免费电影 | 特级黄色一级毛片 | 午夜视频在线免费播放 | 欧美成人免费电影 | 国产又粗又爽又深的免费视频 | 亚洲网站在线 | 九九视频在线观看黄 | 日本在线播放一区 | 黄色大片网站在线观看 | 成人国产高清 | av在线观| 麻豆蜜桃在线观看 | 久久精品日产第一区二区三区 | 97超视频在线观看 | 亚洲 91| 国产精品久久久乱弄 | 自拍偷拍亚洲图片 | 久久精品99久久久久久2456 | 爽爽视频免费看 | 国产乱淫av一区二区三区 | 久草视频国产在线 | 欧美a级大胆视频 | 91,视频免费看 | 国产午夜精品久久久久久免费视 | 久久日本 | 欧美性受xxxx白人性爽 | 欧美黑大粗硬毛片视频 | 色就色 综合偷拍区91网 | 日本一级黄色大片 | 国产免费观看av | 成人综合一区二区 | chinesexxx少妇露脸 | 国产精品伊人久久 | 深夜免费福利视频 | 成人午夜在线免费 | av懂色|