沉浸式go-cache源碼閱讀!

大家好,我是豆小匠。

這期來閱讀go-cache的源碼,了解本地緩存的實現方式,同時掌握一些閱讀源碼的技巧~


1. 源碼獲取

git clone https://github.com/patrickmn/go-cache.git

用Goland打開可以看到真正實現功能的也就兩個go文件,cache.go 1162行,sharded.go 193行,共1355行,用來作為源碼閱讀的練手素材是非常合適的。

img

通過README.md文件,可以了解這個包的使用方法:

import ("fmt""github.com/patrickmn/go-cache""time"
)func main() {// 創建一個緩存對象,默認過期時間5分鐘,每10分鐘清理一次緩存c := cache.New(5*time.Minute, 10*time.Minute)// 設置緩存key:foo,value:bar,過期時間是包里定義的一個常量,一會看看具體定義了啥c.Set("foo", "bar", cache.DefaultExpiration)// 獲取key為foo的緩存,通過類型斷言獲取原始的數據foo, found := c.Get("foo")if found {MyFunction(foo.(string))}
}

2. 源碼閱讀

上面我們看到,創建一個緩存實例,需要傳入緩存清理的間隔,也就是說緩存的刪除不是根據緩存過期時間實時刪除的,那怎么處理才能讓已過期的緩存在邏輯上失效呢?

帶著疑問,開始閱讀cache.go文件。

2.1. Cache定義

type Cache struct {*cache // 為何套娃,先按下不表
}type cache struct {defaultExpiration time.Duration	// 默認過期時間items             map[string]Item // 所有緩存key value,用一個map保存,key是string,value是一個結構體Itemmu                sync.RWMutex	// 讀寫鎖,可以知道go-cache大概率是并發安全的onEvicted         func(string, interface{}) // 這啥,先不管janitor           *janitor // 這啥,先不管
}type Item struct {Object     interface{}	// 真正存儲的緩存數據Expiration int64	// 這個數據的過期時間
}

看完Cache結構體的定義,先有個整體印象,再看它的方法實現~

2.2. Cache初始化

在README.go,我們已經知道,初始化的函數是New(defaultExpiration, cleanupInterval time.Duration),雙擊shift,輸入New,就能找到這個函數。

img

type janitor struct {Interval time.Duration	// 清理過期緩存的間隔stop     chan bool // 接受停止協程的信號
}func New(defaultExpiration, cleanupInterval time.Duration) *Cache {items := make(map[string]Item)	// 定義緩存容器,會存到cache對象的itemsreturn newCacheWithJanitor(defaultExpiration, cleanupInterval, items) // 創建一個帶有清理協程的Cache對象
}func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache {c := newCache(de, m) // 生成小寫那個cache對象(私有)C := &Cache{c}if ci > 0 {	// 傳入定時刪除緩存時間大于0,啟動看清理協程runJanitor(c, ci)	// 啟動清理協程,定時刪除過期的cache keyruntime.SetFinalizer(C, stopJanitor) // 設置C被回收時,執行函數停止清理協程}return C
}

runtime.SetFinalizer:對象可以關聯一個SetFinalizer函數, 當gc檢測到unreachable對象有關聯的SetFinalizer函數時,會執行關聯的SetFinalizer函數, 同時取消關聯。 這樣當下一次gc的時候,對象重新處于unreachable狀態并且沒有SetFinalizer關聯, 就會被回收。

通過上面源碼的閱讀,我們可以知道:

  1. 清理過期緩存通過一個清理協程定期清理。
  2. 當Cache不可達時,GC會觸發停止janitor協程的函數,下一次GC,Cache和cache(內部cache對象)都會被回收。(如果janitor協程和Cache綁定,Cache對象不會被回收,有內存泄露的風險)
c := cache.New(5*time.Minute, 10*time.Minute)
c = nil	// 這里cache已經不使用了,第一次GC會執行SetFinalizer函數,停掉清理協程,第二次GC則會把Cache和cache對象都回收掉

如果清理協程綁定在Cache對象,因為協程一直在運行,即使在使用者看來c已經設置為nil,cache不再使用,GC也無法回收Cache。

2.3. 緩存失效判斷

Cache上是不掛方法的,方法都掛在內部對象cache上。

img

我們先看Get方法:

func (c *cache) Get(k string) (interface{}, bool) {c.mu.RLock()	// 加讀鎖item, found := c.items[k]if !found {c.mu.RUnlock()return nil, false}// 下面這里會判斷item里的過期時間,過期時間小于當前時間,則在邏輯上失效,返回nil, falseif item.Expiration > 0 {	// 如果expiration為0,說明設置的是永不過期if time.Now().UnixNano() > item.Expiration {c.mu.RUnlock()return nil, false}}c.mu.RUnlock()return item.Object, true
}

看源碼可以很清晰的看到,緩存過期不是通過是否存在key來判斷的,而是通過item里存的expiration時間來判斷,因此定時清理緩存是為了清理空間。

2.4. 總體梳理

其他方法都非常明確,我們可以挑幾個常用的看看實現,最后整理下cache這個類的成員變量和方法,畫個圖,完事!

img

前面埋的坑:onEvicted 是刪除key的回調函數。

另外sharded.go文件是一個實驗性的代碼,用于緩存分片,目前還沒對外暴露。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/371272.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/371272.shtml
英文地址,請注明出處:http://en.pswp.cn/news/371272.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

CoreAnimation 變換

CoreAnimation 變換 CoreAnimation 目錄 博客園MakeDown支持不佳,如有需要請進GitHub 本片博客主要內容: 仿射變換 - CGAffineTransform3D變換 - CATransform3D仿射變換 - CGAffineTransform CGAffineTransform 是用于二維空間的旋轉,縮放和平移的屬性.首先展示一個簡單的樣例,…

20170907wdVBA_GetCellsContentToExcel

WORD 加載項 代碼模板 Dim cmdBar As CommandBar, cmdBtn As CommandBarControl Const cmdBtnCap As String "批量提取操作步驟"Sub AutoExec()Call DelCmdBtnCall AddCmdBtnEnd Sub Sub AutoExit()Call DelCmdBtn End SubSub AddCmdBtn()Set cmdBar Application.C…

mysql 5.7 mirror_Centos7 Docker離線部署Mysql5.7

1 環境信息查看系統內核[rootlocalhost /]# cat /etc/redhat-releaseCentOS Linux release 7.5.1804 (Core)2 虛擬機拉取鏡像此處資源獲取在虛擬機中進行,完成后上傳到服務器安裝2.1 拉取mysql5.7鏡像[rootlocalhost /]# docker pull mysql:5.72.2 導出鏡像[rootloc…

Java中的簡單REST客戶端

如今,大多數用于與某些服務器通信的移動應用程序都使用REST服務。 這些服務也是與JavaScript或jQuery一起使用的常見做法。 現在,我知道在Java中為REST服務創建客戶端的2種方法,在本文中,我將嘗試演示這兩種方法,希望它…

3.20 下午

閱讀《藝術學概論》 戲劇沖突是戲劇的靈魂 沖突包括:人物性格的沖突、行為的沖突、 思想感情的沖突乃至心理狀態的沖突等等 轉載于:https://www.cnblogs.com/bgd140206110/p/6590005.html

華為root工具_華為Mate9解鎖后無法ROOT 需要手動刷入Recovery怎么辦【解決方法】...

很多朋友手機到手之后,都希望能夠ROOT使用更多的系統功能。近日有網友向小編詢問,為何華為Mate9解鎖后無法ROOT,明明已經通過官方的解鎖教程解鎖的,但是之后使用“大師”等第三方刷機工具,無法ROOT。其實ROOT的關鍵就在…

JAX-WS入門

JAX-WS代表XML Web Services的Java API。 它是一種Java編程語言API,用于創建Web服務和使用XML進行通信的客戶端。 這篇文章是JAX-WS的快速入門。 先決條件 GlassFish與Eclipse集成在一起 。 創建JAX-WS Web服務 1.在Eclipse中創建一個名為“ com.eviac.blog.jax…

canvas 圖片反色

代碼實例&#xff1a; <!DOCTYPE HTML> <html> <head><meta charset"utf-8"><title>圖片反色</title><style type"text/css">body{ background:black;}#c1{ background:white;}</style><script type&q…

python中的文件父路徑怎么表達_python中的文件父路徑怎么表達_如何在Python中訪問父目錄...

所以我有一個朋友給我的Python腳本&#xff0c;但是我沒有Python的經驗。代碼如下&#xff1a;from os import path, chdir, listdir, mkdir, getcwdfrom sys import argvfrom zipfile import ZipFilefrom time import sleep#Defines what extensions to look for within the f…

Maven的中央倉庫地址

www.mvnrepository.com轉載于:https://www.cnblogs.com/j-liu3323/p/6590435.html

Spring–添加AOP支持

我聽到了一個有關一位高級&#xff08;且酬勞頗豐&#xff09;軟件工程師的故事。 他的任務是記錄他正在研究的項目中每個控制器中的每個方法。 工程師重寫了所有控制器方法&#xff0c;因此使用如下代碼&#xff1a; RequestMapping(method RequestMethod.GET)public String …

vscode python第三方庫檢測_VSCode中使用Pylint檢查python代碼

為什么使用lint在日常開發中&#xff0c;不同開發人員會寫下不同風格的代碼&#xff0c;導致代碼可維護性變差&#xff0c;為了解決風格不一致問題&#xff0c;我們可以制定代碼規范&#xff0c;讓開發人員都遵守同樣的規范編寫代碼。在開發過程中&#xff0c;部分代碼存在質量…

Spring MVC-集成(Integration)-集成LOG4J示例(轉載實踐)

以下內容翻譯自&#xff1a;https://www.tutorialspoint.com/springmvc/springmvc_log4j.htm 說明&#xff1a;示例基于Spring MVC 4.1.6。 以下示例說明如何使用Spring Web MVC框架來觸發LOG4J。首先&#xff0c;讓我們使用Eclipse IDE&#xff0c;并按照以下步驟使用Spring W…

NUMA架構和Java

是時候部署您的應用程序了&#xff0c;期待著采購最適合負載要求的硬件。 如今&#xff0c;具有40核或80核的包裝盒非常普遍。 總體概念是更多的內核&#xff0c;更多的處理能力&#xff0c;更多的吞吐量。 但是我看到了一些相反的結果&#xff0c;表明小型的CPU密集型測試運行…

存儲過程常用技巧

我們在進行pl/sql編程時打交道最多的就是存儲過程了。存儲過程的結構是非常的簡單的&#xff0c;我們在這里除了學習存儲過程的基本結構外&#xff0c;還會學習編寫存儲過程時相關的一些實用的知識。如&#xff1a;游標的處理&#xff0c;異常的處理&#xff0c;集合的選擇等等…

vue是用a標簽打開新頁面_vue 在新窗口打開頁面并設置不同的背景

開發一個新系統&#xff0c;前端用的vue&#xff0c;vue是單體應用&#xff0c;所有頁面都在一個窗口里實現&#xff0c;但項目要求在點button鏈接后要新打開一個瀏覽器頁面&#xff0c;解決方法如下&#xff1a;1. 給此button設置新事件 click"createdefect"提交缺陷…

卡爾曼濾波的推導

卡爾曼濾波的推導1 最小二乘法在一個線性系統中&#xff0c;若\(x\)為常量&#xff0c;是我們要估計的量&#xff0c;關于\(x\)的觀測方程如下&#xff1a; \[ y Hx v \tag{1.1}\] \(H\)是觀測矩陣&#xff08;或者說算符&#xff09;&#xff0c;\(v\)是噪音&#xff0c;\(y…

Java注釋-保留

考慮一下Java批注&#xff1a; public interface AnAnnotaton {}帶有此注釋的類&#xff1a; AnAnnotaton class AnAnnotatedClass{}還有一個測試&#xff0c;檢查類中是否存在此批注&#xff1a; import static org.hamcrest.MatcherAssert.assertThat; import static org.h…

MYSQL查詢選修三門以上課程_SQL高級查詢的練習題

Student(S#,Sname,Sage,Ssex) 學生表Course(C#,Cname,T#) 課程表SC(S#,C#,score) 成績表Teacher(T#,Tname) 教師表問題&#xff1a;1、查詢“001”課程比“002”課程成績高的所有學生的學號&#xff1b;select a.S# from (select s#,score from SC where C#001) a,(select s#,s…

Determing client's IP

AuthorDeterming clients IPАнатоли&23.04.2009 18:39:46Registered userHow to determine clients IP address in THTTPServer.OnClientConnected, THTTPServer.OnClientDisonnected and TRtcFunction.OnExecute events?Danijel Tkalcec [RTC]23.04.2009 19:45:05…