吴忠躺衫网络科技有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

基于Go的緩存實現方法

馬哥Linux運維 ? 來源:DeepNoMind ? 2023-06-12 09:50 ? 次閱讀

概念

緩存是計算機科學中的一個重要概念。設想某個組件需要訪問外部資源,它向外部源請求資源,接收并使用資源,這些步驟都需要花費時間。當組件再次需要資源時,可以再次請求資源,但這種方式從時間上考慮是比較低效的。相反,組件可以將請求結果保存在本地某處,然后再次使用,使用本地數據總是比請求外部數據要快,這一策略就是緩存的基本概念。我們可以在內存、CPU緩存和服務器緩存(如Redis)中找到這些例子。

不同用例

Web服務中的緩存用于減少數據請求的延遲。Web服務保存第一次查詢的執行結果,然后在需要的時候再次使用,而不用再次訪問數據庫。取決于數據的特性,緩存有不同情況,可以有相對靜態的數據,如統計數據、計算結果,也有可能是經常變化的數據,如評論區或SNS。

最好的情況是緩存那些很少變化的數據。以月度統計數據為例,上個月的數據將不會變化,如果對它進行緩存,可能就不需要查詢數據庫獲取上個月的數據了。

9a1e14ba-0858-11ee-962d-dac502259ad0.png

愚蠢的設計

對于快速變化的數據,在存在多個服務器時最好謹慎些。看看上面的設計,以評論區服務為例,考慮如下場景,用戶A發表了一些評論,然后A決定刪除評論,用戶B嘗試回復評論。在某些情況下,A和B向不同的服務器發送請求。A的刪除操作可能不會傳播到B的服務器緩存。結果會是這樣: 緩存A和緩存B有不同的數據,數據庫不知道哪個才是真實的,數據的完整性被破壞了。

9a402cd0-0858-11ee-962d-dac502259ad0.png

更好的方式

在這種情況下,可以使用單一外部緩存(如上圖所示),多個服務器只訪問統一的緩存。

限制條件

緩存比數據庫要快,但在大小上要小得多。這是因為數據庫將數據存儲在驅動器中,緩存將數據存儲在內存中。它們遵循各自相同的特征,同樣也有不同的特點,如果主機停止工作,緩存的所有數據都會丟失,但數據庫的數據不會丟失。

由于緩存位于內存中,空間是有限的,需要選擇緩存哪些數據。在CS課上,我們會聽到LRU(Least Recently Used,最近最少使用),LFU(Least Frequently Used,最不常用)和FIFO(First In First Out,先入先出)這樣的詞,這些是"選擇哪一個"的標準,被稱為驅逐策略(eviction policy)。

設計&實現

需求

鍵值存儲(Key-Value Storage): 緩存既要有輸入鍵、輸出值的讀功能,也要有輸入鍵、值的寫功能。這些函數應該在平均O(logN)時間內完成,其中N是鍵的數量。

LRU驅逐策略: 由于緩存空間有限,如果緩存滿了,一些數據應該被清除,選擇用LRU算法實現。

TTL (Time To Live): 每個鍵值都有生存時間,如果TTL到期,該鍵值應該被驅逐。

API設計

鍵值存儲的意思是,如果請求鍵,緩存會返回那些存在的鍵的值,類似于hash-map抽象數據類型,以提供以下API概念的應用程序為例:

funcGet(keystring)(hitbool,value[]byte)
funcPut(keystring,value[]byte)(hitbool)

Get: 通過鍵讀取值的API。如果所提供的鍵在緩存中存在,則返回等效值。如果不存在,則返回hit=false。對于LRU策略,鍵將被標記為最近被使用,從而使該鍵不會被驅逐。

Put: 通過鍵寫入值的API。如果所提供的鍵存在,則value將被替換為新值。如果不存在,將創建新的鍵值存儲。因為該函數可以添加數據,其執行可能會導致溢出。在這種情況下,根據LRU策略,最近最少使用的鍵值將被清除。新添加/修改的鍵將被標記為最近使用的鍵。

數據結構

9a63e4fe-0858-11ee-962d-dac502259ad0.png

設計概念

我們使用兩種不同的數據結構: hash-map和雙向鏈表,實現鍵值讀寫和LRU策略的特性。

Hash-map: Hash-map是使用最廣泛的鍵值數據結構,在Go中是現成的數據類型,可以通過map[]定義。

雙向鏈表: LRU緩存可以通過雙向鏈表實現。

基于這兩種數據結構可以同時提供鍵值特性和LRU策略。參考以上設計概念圖,hash-map的鍵將是字符串鍵,值是指向鏈表節點的指針,節點將保存鍵的值。

如果用戶調用Get(),緩存應用程序將在hash-map中搜索鍵,跟隨指針到達鏈表中的一個節點,獲取值,完成LRU策略,并將值返回給用戶。

類似的,如果調用Put(),會在hash-map中搜索鍵,跟蹤指針并替換值,完成LRU策略,或者向hash-map中插入新鍵,并向鏈表中插入新節點。

并發控制

由于緩存被設計為支持頻繁訪問,因此在同一時間會有多個訪問,并且總是存在并發問題的可能性。

在該設計中,存在兩種不同的數據結構,并且并不總是同步的。在執行過程中,hash-map的修改和鏈表的修改之間有一個微小的時間間隔,請看下面的例子。

9a853d70-0858-11ee-962d-dac502259ad0.png

并發問題案例

該問題的觸發條件為: 當前緩存已滿,最近最少使用的鍵為1。這意味著,如果添加了新的鍵,鍵1和等效的值將被清除。

用戶A使用新鍵101調用Put()。hash-map檢查鍵,發現101不存在,決定清除1并將101添加到緩存中。

同時,用戶B使用鍵1調用Put()。hash-map確認鍵1存在,并決定修改該值。

A的調用繼續執行,從鏈表中刪除節點1,從hash-map中刪除鍵1。

緊接著,B的調用試圖訪問節點1的地址,并發現該地址已不存在,從而發生panic并造成應用失效。

防止這種情況發生的最簡單方法是使用互斥(Mutex),參考以下代碼。

func(s*CStorage)Get(keystring)(data[]byte,hitbool){
s.mutex.Lock()
defers.mutex.Unlock()

n,ok:=s.table[key]
if!ok{
returnnil,false
}
ifn.ttl.Before(time.Now()){
s.evict(n)
s.size--
returnnil,false
}

returnn.data,true
}

這段代碼是Get()的函數定義,可以看到在第一行中有互斥鎖代碼,在第二行中有defer的互斥鎖解鎖代碼(defer是Go關鍵字,將行執行推遲到函數的末尾)。這些代碼應用于所有其他數據存儲訪問功能,如Put、Delete、Clear等。

通過使用互斥鎖,每次執行都不會受到其他操作的影響,保證了數據訪問的安全性。

生存時間(Time To Live)

目前TTL是采用被動方式實現的,這意味著如果執行了數據訪問函數(Get, Put),它將檢查TTL是否過期并決定是否刪除。這也意味著即使節點已經過期,將仍然存在于數據結構中。

這種方法不需要消耗大量CPU時間來定期遍歷所有節點,但是緩存很可能會保存過期的值。

大多數情況下,這么做沒有問題,因為過期節點很可能是"最近最少使用"狀態。但是,如果有函數通過數據結構清除過期節點就更好了,所以我們使用RemoveExpired()函數。

func(s*CStorage)RemoveExpired()int64{
varcountint64=0
forkey,value:=ranges.table{
ifvalue.ttl.Before(time.Now()){
s.Delete(key)
count++
}
}
returncount
}

此函數將被定期調用以清除所有過期節點。

結果

實現的Go包可以導入其他Go項目。另外,我還做了獨立的緩存應用程序,提供gRPC API,細節可以查看這個存儲庫[2]。

結論

這是個很好的重新審視緩存概念的機會,并且我們用Go實現了緩存。緩存是降低組件延遲的好工具,雖然空間受限,但速度更快。

實現實際的緩存模塊可以用hash-map和雙向鏈表完成。并發問題有點棘手,所以不得不使用互斥鎖。此外,我們混合了被動和主動方式來刪除過期數據。




審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • TTL
    TTL
    +關注

    關注

    7

    文章

    504

    瀏覽量

    70429
  • FIFO存儲
    +關注

    關注

    0

    文章

    103

    瀏覽量

    6037
  • SNS
    SNS
    +關注

    關注

    0

    文章

    7

    瀏覽量

    6701
  • Hash算法
    +關注

    關注

    0

    文章

    43

    瀏覽量

    7417

原文標題:基于Go的緩存實現

文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    ASP緩存技術

    使用ASP中的緩存技術可以很大程度上提高你的網站性能,其實這些實現方法是非常的簡單,它將說明如何在服務器上的緩存是如何工作以及你如何使用一種被稱為斷開連接的ADO連接技術。在介紹這些技
    發表于 11-21 10:53

    【GoRK3288】1.Rockchip RK3288, GO!GO!!GO!!!

    `前言: 最近看了看Google的Go語言,發現有點意思,這個開源的項目準備用golang來實現。 其實開發板本身的驅動程序已經實現了各個功能,但是有的時候在使用中有些麻煩,有可能需要修改dts
    發表于 08-14 21:07

    go語言能做什么工作?

    文件系統Tsuru:開源的PAAS平臺,和SAE實現的功能一模一樣Groupcache:memcahe作者寫的用于Google下載系統的緩存系統God:類似redis的緩存系統,但是支持分布式和擴展性Gor
    發表于 03-22 15:03

    linux的DNS緩存清空方法

    Linux下DNS緩存實現通常有兩種方式:一種是用DNS緩存程序NSCD(name service cache daemon)負責管理DNS緩存
    發表于 07-25 07:53

    高速緩存/海量緩存的設計實現

    數據采集板并行采樣0.1s將產生32MB的數據量,所以,通常需要海量緩存來存儲采樣數據。  2、高速緩存實現  通常構成高速緩存的方案有三種:  第一種是FIFO(先進先出)方式。F
    發表于 12-04 15:59

    sdwebimage清除緩存方法

    清除通過SDWebImage進行的緩存;Sdwebimage手動清除緩存方法;iOS SDWebImage清空緩存方法.
    發表于 11-09 14:38 ?3639次閱讀
    sdwebimage清除<b class='flag-5'>緩存</b><b class='flag-5'>方法</b>

    PIC32MZ器件系列中使用L1CPU高速緩存實現的風險和解決方法

    本文檔提供了PIC32MZ 器件系列中一級(Level 1, L1)CPU高速緩存實現的相關信息,并介紹了高速緩存系統的相關風險。此外還提供了解決這些風險的方法
    發表于 06-15 11:26 ?9次下載
    PIC32MZ器件系列中使用L1CPU高速<b class='flag-5'>緩存</b><b class='flag-5'>實現</b>的風險和解決<b class='flag-5'>方法</b>

    dubbo-go 中的 TPS Limit 設計與實現

    則是 Dubbo 的 Go 語言實現。 最近在 dubbo-go 的 todo list 上發現,它還沒有實現 TPS Limit 的模塊,于是就抽空
    發表于 03-17 15:27 ?659次閱讀

    緩存如何工作,如何設計CPU緩存

    20世紀80年代,CPU性能有了顯著提升,但這受到板載內存訪問速度緩慢增長的阻礙。隨著這種差異的惡化,工程師們發現了一種通過新的設計技術緩存來解決問題的方法。本文將幫助你進一步了解什么是緩存,它如何工作以及如何設計CPU
    的頭像 發表于 11-19 17:23 ?2796次閱讀

    緩存的原理/作用/使用的場景/方法

    在項目中,有些請求查詢,并不需要每次都去查詢數據庫,而是先判斷緩存數據是否存在,如果存在,直接用緩存的數據返回結果,如果不存在,再去查詢數據庫,并將數據緩存起來,用于下次請求使用。
    發表于 12-21 16:36 ?2297次閱讀

    基于預測緩存的OpenFlow虛擬流表查找方法

    基于預測緩存的OpenFlow虛擬流表查找方法
    發表于 06-27 15:54 ?11次下載

    基于Memcached的緩存資源集中管理方法_郭棟

    基于Memcached的緩存資源集中管理方法_郭棟(監控電源65W多少錢)-基于Memcached的緩存資源集中管理方法_郭棟這是一份非常不錯的資料,歡迎下載,希望對您有幫助!
    發表于 07-26 13:11 ?2次下載
    基于Memcached的<b class='flag-5'>緩存</b>資源集中管理<b class='flag-5'>方法</b>_郭棟

    Go并發模型的實現原理

    Go語言是為并發而生的語言,Go語言是為數不多的在語言層面實現并發的語言;也正是Go語言的并發特性,吸引了全球無數的開發者。
    的頭像 發表于 04-15 08:49 ?1427次閱讀

    Go在單線程計算性能上的優勢

    一文中,我們討論了Go在單線程計算性能上的優勢。 現在,考慮這樣的一種場景: 我們需要從某些網址中同步數據并進行計算,保存到本地redis緩存中。 現在,我們可以通過編寫Go Worker的方式
    的頭像 發表于 11-02 11:16 ?527次閱讀
    <b class='flag-5'>Go</b>在單線程計算性能上的優勢

    Go語言中的函數、方法與接口詳解

    Go 沒有類,不過可以為結構體類型定義方法方法就是一類帶特殊的接收者參數的函數。方法接收者在它自己的參數列表內,位于 func 關鍵字和方法
    的頭像 發表于 04-23 16:21 ?915次閱讀
    天地人百家乐现金网| 最新娱乐城注册送彩金| 百家乐平预测软件| 百家乐官网路单破解器| 做生意仓库和办公桌在家里是不是讲风水| 大发888娱乐下载网址| 蓝盾百家乐官网平台| 大发888真钱娱乐游戏| rmb百家乐官网的玩法技巧和规则 木星百家乐官网的玩法技巧和规则 | 百家乐官网电话投注怎么玩| 百家乐7scs娱乐平台| 百家乐官网视频多开器| 网络百家乐路子玩| 百家乐官网去哪里玩最好| 百家乐制胜秘| 百家乐官网投注心得和技巧| 包赢百家乐的玩法技巧和规则| 娱乐城百家乐官网高手| 唐朝百家乐的玩法技巧和规则| 百家乐官网顺序| 大发888怎样存款| 乐享百家乐官网的玩法技巧和规则 | 大发888娱乐场 下载| 百家乐官网牌| 大发888游戏论坛| 真人百家乐官网网络游戏信誉怎么样| 博彩套利| 圣淘沙百家乐娱乐城| 百家乐官网网上公式| 名人百家乐的玩法技巧和规则| 百家乐官网历史路单| 宝龙百家乐的玩法技巧和规则 | 威尼斯人娱乐棋牌下载| 现金百家乐官网破解| 大发888登录下载| 百家乐网址哪里有| 金宝博百家乐官网娱乐城| 太阳城房价| 百家乐投注助手| 挖掘百家乐官网赢钱秘籍| 顶级赌场官方下载|