欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語言使用singleflight解決緩存擊穿

 更新時間:2024年03月22日 09:28:38   作者:陳明勇  
在構(gòu)建高性能的服務(wù)時,緩存是優(yōu)化數(shù)據(jù)庫壓力和提高響應(yīng)速度的關(guān)鍵技術(shù),但使用緩存也會帶來一些問題,其中就包括緩存擊穿,下面我們就來看看Go語言中如何使用singleflight解決緩存擊穿問題吧

前言

在構(gòu)建高性能的服務(wù)時,緩存是優(yōu)化數(shù)據(jù)庫壓力和提高響應(yīng)速度的關(guān)鍵技術(shù)。使用緩存也會帶來一些問題,其中就包括 緩存擊穿,它不僅會導(dǎo)致數(shù)據(jù)庫壓力劇增,引起數(shù)據(jù)庫性能的下降,嚴(yán)重時甚至?xí)艨鍞?shù)據(jù)庫,導(dǎo)致數(shù)據(jù)庫不可用。

在 Go 語言中,golang.org/x/sync/singleflight 包提供了一種機制,確保對于任何特定 key 的并發(fā)請求在同一時刻只執(zhí)行一次。這個機制有效地防止了緩存擊穿問題。

本文將深入探討 Go 語言中 singleflight 包的使用。從緩存擊穿問題的基礎(chǔ)知識開始,進而詳細介紹 singleflight 包的使用,展示如何利用它來避免緩存擊穿。

準(zhǔn)備好了嗎?準(zhǔn)備一杯你最喜歡的咖啡或茶,隨著本文一探究竟吧。

緩存擊穿

緩存擊穿 是指在高并發(fā)的情況下,某個熱點的 key 突然過期,導(dǎo)致大量的請求直接訪問數(shù)據(jù)庫,造成數(shù)據(jù)庫的壓力過大,甚至宕機的現(xiàn)象。

緩存擊穿流程圖.png

常見的解決方案:

  • 設(shè)置熱點數(shù)據(jù)永不過期:對于一些確定的熱點數(shù)據(jù),可以將其設(shè)置為 永不過期,這樣就可以確保不會因為緩存失效而導(dǎo)致請求直接訪問數(shù)據(jù)庫。
  • 設(shè)置互斥鎖:為了防止緩存失效時所有請求同時查詢數(shù)據(jù)庫,可以采用鎖機制確保僅有一個請求查詢數(shù)據(jù)庫并更新緩存,而其他請求則在緩存更新后再進行訪問。
  • 提前更新:后臺監(jiān)控緩存的使用情況,當(dāng)緩存即將過期時,異步更新緩存,延長過期時間。

singleflight 包

Package singleflight provides a duplicate function call suppression mechanism.

這段英文來自官方文檔的介紹,直譯過來的意思是:singleflight 包提供了一種“重復(fù)函數(shù)調(diào)用抑制機制”。

換句話說,當(dāng)多個 goroutine 同時嘗試調(diào)用同一個函數(shù)(基于某個給定的 key)時,singleflight 會確保該函數(shù)只會被第一個到達的 goroutine 調(diào)用,其他 goroutine 會等待這次調(diào)用的結(jié)果,然后共享這個結(jié)果,而不是同時發(fā)起多個調(diào)用。

一句話概括就是 singleflight 將多個請求合并成一個請求,多個請求共享同一個結(jié)果。

組成部分

Group:這是 singleflight 包的核心結(jié)構(gòu)體。它管理著所有的請求,確保同一時刻,對同一資源的請求只會被執(zhí)行一次。Group 對象不需要顯式創(chuàng)建,直接聲明后即可使用。

Do 方法:Group 結(jié)構(gòu)體提供了 Do 方法,這是實現(xiàn)合并請求的主要方法,該方法接收兩個參數(shù):一個是字符串 key(用于標(biāo)識請求資源),另一個是函數(shù) fn,用來執(zhí)行實際的任務(wù)。在調(diào)用 Do 方法時,如果已經(jīng)有一個相同 key 的請求正在執(zhí)行,那么 Do 方法會等待這個請求完成并共享結(jié)果,否則執(zhí)行 fn 函數(shù),然后返回結(jié)果。

Do 方法有三個返回值,前兩個返回值是 fn 函數(shù)的返回值,類型分別為 interface{} 和 error,最后一個返回值是一個 bool 類型,表示 Do 方法的返回結(jié)果是否被多個調(diào)用共享。

DoChan:該方法與 Do 方法類似,但它返回的是一個通道,通道在操作完成時接收到結(jié)果。返回值是通道,意味著我們能以非阻塞的方式等待結(jié)果。

Forget:該方法用于從 Group 中刪除一個 key 以及相關(guān)的請求記錄,確保下次用同一 key 調(diào)用 Do 時,將立即執(zhí)行新請求,而不是復(fù)用之前的結(jié)果。

Result:這是 DoChan 方法返回結(jié)果時所使用的結(jié)構(gòu)體類型,用于封裝請求的結(jié)果。這個結(jié)構(gòu)體包含三個字段,具體如下:

  • Valinterface{} 類型):請求返回的結(jié)果。
  • Errerror 類型):請求過程中發(fā)生的錯誤信息。
  • Sharedbool 類型):表示這個結(jié)果是否被當(dāng)前請求以外的其他請求共享。

安裝

通過以下命令,在 go 應(yīng)用中安裝 singleflight 依賴:

go get golang.org/x/sync/singleflight

使用示例

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/singleflight/usage/main.go
package main

import (
    "errors"
    "fmt"
    "golang.org/x/sync/singleflight"
    "sync"
)

var errRedisKeyNotFound = errors.New("redis: key not found")

func fetchDataFromCache() (any, error) {
    fmt.Println("fetch data from cache")
    returnnil, errRedisKeyNotFound
}

func fetchDataFromDataBase() (any, error) {
    fmt.Println("fetch data from database")
    return"程序員陳明勇", nil
}

func fetchData() (any, error) {
    cache, err := fetchDataFromCache()
    if err != nil && errors.Is(err, errRedisKeyNotFound) {
        fmt.Println(errRedisKeyNotFound.Error())
        return fetchDataFromDataBase()
    }
    return cache, err
}

func main() {
    var (
        sg singleflight.Group
        wg sync.WaitGroup
    )

    forrange5 {
        wg.Add(1)
        gofunc() {
            defer wg.Done()
            v, err, shared := sg.Do("key", fetchData)
            if err != nil {
                panic(err)
            }
            fmt.Printf("v: %v, shared: %v\n", v, shared)
        }()
    }
    wg.Wait()
}

singleflight.png

這段代碼模擬了一個典型的并發(fā)訪問場景:從緩存獲取數(shù)據(jù),若緩存未命中,則從數(shù)據(jù)庫檢索。在此過程中,singleflight 庫起到了至關(guān)重要的作用。它確保在多個并發(fā)請求嘗試同時獲取相同數(shù)據(jù)時,實際的獲取操作(不論是訪問緩存還是查詢數(shù)據(jù)庫)只會執(zhí)行一次。這樣不僅減輕了數(shù)據(jù)庫的壓力,還有效防止了高并發(fā)環(huán)境下可能發(fā)生的緩存擊穿問題。

代碼運行結(jié)果如下所示:

fetch data from cache
redis: key not found         
fetch data from database     
v: 程序員陳明勇, shared: true
v: 程序員陳明勇, shared: true
v: 程序員陳明勇, shared: true
v: 程序員陳明勇, shared: true
v: 程序員陳明勇, shared: true

根據(jù)運行結(jié)果可知,當(dāng) 5 個 goroutine 并發(fā)獲取相同數(shù)據(jù)時,數(shù)據(jù)獲取操作實際上只由一個goroutine執(zhí)行了一次。此外,由于所有返回的 shared 值均為 true,這表明返回的結(jié)果被其他 4 個goroutine共享。

最佳實踐

key 的設(shè)計

在生成 key 的時候,我們應(yīng)該保證它的唯一性與一致性。

  • 唯一性:確保傳遞給 Do 方法的 key 具有唯一性,以便 Group 區(qū)分不同請求。推薦使用結(jié)構(gòu)化的命名方式來保證 key 的唯一性,例如,可以遵循類似 {類型}):{標(biāo)識} 的規(guī)范來構(gòu)建 key。以獲取用戶信息為例,相應(yīng)的 key 可以是 user:1234,其中 user 標(biāo)識數(shù)據(jù)類型,而 1234 則是具體的用戶標(biāo)識。
  • 一致性:對于相同的請求,無論何時調(diào)用,生成的 key 應(yīng)該保持一致,以便 Group 正確地合并相同的請求,防止非預(yù)期的錯誤。

超時控制

在調(diào)用 Group.Do 方法時,第一個到達的 goroutine 可以成功執(zhí)行 fn 函數(shù),而其他隨后到達的 goroutine 將進入阻塞狀態(tài)。如果阻塞狀態(tài)持續(xù)過長,可能需要采取降級策略以保證系統(tǒng)的響應(yīng)性,這時候,我們可以利用 Group.DoChan 方法和結(jié)合 select 語句實現(xiàn)超時控制。

以下是一個實現(xiàn)超時控制的簡單示例:

// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/singleflight/timeout_control/main.go
package main

import (
    "fmt"
    "golang.org/x/sync/singleflight"
    "time"
)

func main() {
    var sg singleflight.Group
    doChan := sg.DoChan("key", func() (interface{}, error) {
        time.Sleep(4 * time.Second)
        return"程序員陳明勇", nil
    })
    select {
    case <-doChan:
        fmt.Println("done")
    case <-time.After(2 * time.Second):
        fmt.Println("timeout")
        // 采用其他降級策略
    }
}

小結(jié)

本文首先介紹了 緩存擊穿 的含義及其常見的解決方案。

然后深入探討了 singleflight 包,從基礎(chǔ)概念、組成部分到具體的安裝和使用示例。

接著通過模擬一個典型的并發(fā)訪問場景來演示如何利用 singleflight 來防止在高并發(fā)場景下可能發(fā)生的緩存擊穿問題。

最后,探討在實踐中設(shè)計 key 和控制請求超時的最佳策略,以便更好地理解和應(yīng)用 singleflight,從而優(yōu)化并發(fā)處理邏輯。

到此這篇關(guān)于Go語言使用singleflight解決緩存擊穿的文章就介紹到這了,更多相關(guān)Go singleflight緩存擊穿內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go語言學(xué)習(xí)筆記之文件讀寫操作詳解

    Go語言學(xué)習(xí)筆記之文件讀寫操作詳解

    這篇文章主要為大家詳細介紹了Go語言對文件進行讀寫操作的方法,文中的示例代碼講解詳細,對我們學(xué)習(xí)Go語言有一定的幫助,需要的可以參考一下
    2022-05-05
  • 一些關(guān)于Go程序錯誤處理的相關(guān)建議

    一些關(guān)于Go程序錯誤處理的相關(guān)建議

    錯誤處理在每個語言中都是一項重要內(nèi)容,眾所周知,通常寫程序時遇到的分為異常與錯誤兩種,Golang中也不例外,這篇文章主要給大家介紹了一些關(guān)于Go程序錯誤處理的相關(guān)建議,需要的朋友可以參考下
    2021-09-09
  • 解決Golang中g(shù)oroutine執(zhí)行速度的問題

    解決Golang中g(shù)oroutine執(zhí)行速度的問題

    這篇文章主要介紹了解決Golang中g(shù)oroutine執(zhí)行速度的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • golang中的net/http庫基本使用詳解

    golang中的net/http庫基本使用詳解

    今天給大家分享golang中的net/http庫基本使用方法,文章開頭給大家詳細介紹了標(biāo)準(zhǔn)庫net/http如何處理一個請求,結(jié)合實例代碼給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧
    2024-04-04
  • Go實現(xiàn)整合Logrus實現(xiàn)日志打印

    Go實現(xiàn)整合Logrus實現(xiàn)日志打印

    這篇文章主要介紹了Go實現(xiàn)整合Logrus實現(xiàn)日志打印,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-07-07
  • 淺談go build后加文件和目錄的區(qū)別

    淺談go build后加文件和目錄的區(qū)別

    這篇文章主要介紹了淺談go build后加文件和目錄的區(qū)別,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Golang如何自定義logrus日志保存為日志文件

    Golang如何自定義logrus日志保存為日志文件

    這篇文章主要給大家介紹了關(guān)于Golang如何自定義logrus日志保存為日志文件的相關(guān)資料,logrus是目前Github上star數(shù)量最多的日志庫,logrus功能強大,性能高效,而且具有高度靈活性,提供了自定義插件的功能,很多開源項目都是用了logrus來記錄其日志,需要的朋友可以參考下
    2024-02-02
  • Go實現(xiàn)將任何網(wǎng)頁轉(zhuǎn)化為PDF

    Go實現(xiàn)將任何網(wǎng)頁轉(zhuǎn)化為PDF

    在許多應(yīng)用場景中,可能需要將網(wǎng)頁內(nèi)容轉(zhuǎn)化為?PDF?格式,使用Go編程語言,結(jié)合一些現(xiàn)有的庫,可以非常方便地實現(xiàn)這一功能,下面我們就來看看具體實現(xiàn)方法吧
    2024-11-11
  • Go模塊布局管理文檔翻譯理解

    Go模塊布局管理文檔翻譯理解

    這篇文章主要為大家介紹了Go模塊布局管理文檔翻譯理解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • Go語言并發(fā)編程 sync.Once

    Go語言并發(fā)編程 sync.Once

    這篇文章要介紹的是Go語言并發(fā)編程 sync.Once,sync.Once用于保證某個動作只被執(zhí)行一次,可用于單例模式中,下面文章我們來介紹一下它的使用方法,需要的朋友可以參考一下
    2021-10-10

最新評論