并發(fā)安全本地化存儲(chǔ)go-cache讀寫鎖實(shí)現(xiàn)多協(xié)程并發(fā)訪問
簡介
go-cache廣泛使用在go語言編程中,適合迎來在單機(jī)上 存儲(chǔ)鍵值對(duì)形式的內(nèi)存緩存。
在github上地址為 https://github.com/patrickmn/go-cache他在并發(fā)的時(shí)候,線程安全(讀寫鎖) + map[string]interface{} + 過期時(shí)間 來作為go的本地化存儲(chǔ)。
這也是他的三大特性:
- 線程安全,通過讀寫鎖支持多個(gè)協(xié)程并發(fā)訪問
- 不需要序列化,鍵值對(duì)形式,任意值類型map[string]interface{}
- 自定義每個(gè)key的過期時(shí)間
數(shù)據(jù)結(jié)構(gòu)
主要有Cache,以及其組成 cache,Item兩個(gè)結(jié)構(gòu)。
type Cache struct { *cache // If this is confusing, see the comment at the bottom of New() // 如果這令人困惑,請(qǐng)參閱New()底部的注釋。 } type cache struct { defaultExpiration time.Duration items map[string]Item //存儲(chǔ)鍵值對(duì) mu sync.RWMutex // 讀寫鎖,并發(fā)安全 onEvicted func(string, interface{}) // 被清除時(shí)的回調(diào)函數(shù) janitor *janitor // 腳本,定期清理過期數(shù)據(jù) } type Item struct { Object interface{} // 存儲(chǔ)的值 Expiration int64 // 到期時(shí)間 }
創(chuàng)建Cache對(duì)象
使用New(defaultExpiration默認(rèn)過期時(shí)間, cleanupInterval定時(shí)清理時(shí)間)函數(shù)來初始化。
傳遞到兩個(gè)參數(shù):key的過期時(shí)間,以及定時(shí)腳本清理過期數(shù)據(jù)的時(shí)間。
// Return a new cache with a given default expiration duration and cleanup interval. // 返回具有給定默認(rèn)過期和清除時(shí)間的 new cache新緩存 // If the expiration duration is less than one (or NoExpiration), // the items in the cache never expire (by default), // 假如 到期時(shí)間為-1,永不過期 // and must be deleted manually. // 并且只能手動(dòng)刪除。 // If the cleanup interval is less than one, expired items are not // 如果清除時(shí)間小于1,過期item,在call之前是沒有被刪除的。 // deleted from the cache before calling c.DeleteExpired(). func New(defaultExpiration, cleanupInterval time.Duration) *Cache { items := make(map[string]Item) return newCacheWithJanitor(defaultExpiration, cleanupInterval, items) } func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache { c := newCache(de, m) // This trick ensures that the janitor goroutine (which--granted it // 這個(gè)代碼,確認(rèn)是否啟動(dòng) janitor看門人goroutine // was enabled--is running DeleteExpired on c forever) does not keep // the returned C object from being garbage collected. When it is // garbage collected, the finalizer stops the janitor goroutine, after // which c can be collected. C := &Cache{c} if ci > 0 { runJanitor(c, ci) // 調(diào)用守衛(wèi)進(jìn)程來清理過期數(shù)據(jù) runtime.SetFinalizer(C, stopJanitor) // runtime.SetFinalizer(C, stopJanitor)會(huì)指定調(diào)用函數(shù)停止后臺(tái) goroutine, // 當(dāng) GC 準(zhǔn)備釋放對(duì)象時(shí),會(huì)調(diào)用stopJanitor方法, // Run函數(shù)中j.stop通道會(huì)輸出一個(gè)信號(hào),從而退出協(xié)程。 } return C } func runJanitor(c *cache, ci time.Duration) { j := &janitor{ Interval: ci, stop: make(chan bool), } c.janitor = j go j.Run(c) // 調(diào)用守衛(wèi)進(jìn)程來清理過期數(shù)據(jù) } // 開啟一個(gè)定時(shí)器,來定時(shí)清理數(shù)據(jù)。 func (j *janitor) Run(c *cache) { // 創(chuàng)建了一個(gè)計(jì)時(shí)器,時(shí)間到時(shí)ticker.C通道會(huì)輸出一個(gè)值,調(diào)用DeleteExpired()函數(shù) // 該函數(shù)會(huì)通過遍歷cache中的map[string]Item的過期時(shí)間,過期則直接從map中刪除, // 如果該值有回調(diào)函數(shù),則在刪除后執(zhí)行回調(diào)函數(shù)。 ticker := time.NewTicker(j.Interval) for { select { case <-ticker.C: c.DeleteExpired() case <-j.stop: ticker.Stop() return } } } // Delete all expired items from the cache. // 刪除cache中所有的過期items // 此時(shí)會(huì)加鎖,如果定時(shí)清理的時(shí)間比較長,并且key比較多的話, // 會(huì)導(dǎo)致一直被 清理協(xié)程鎖住。其他的協(xié)程沒法寫入。 func (c *cache) DeleteExpired() { var evictedItems []keyAndValue now := time.Now().UnixNano() c.mu.Lock() // 過期刪除的時(shí)候,需要上鎖。 for k, v := range c.items { // "Inlining" of expired if v.Expiration > 0 && now > v.Expiration { ov, evicted := c.delete(k) if evicted { evictedItems = append(evictedItems, keyAndValue{k, ov}) } } } c.mu.Unlock() // 刪除完解鎖 for _, v := range evictedItems { c.onEvicted(v.key, v.value) } } func (c *cache) delete(k string) (interface{}, bool) { if c.onEvicted != nil { if v, found := c.items[k]; found { delete(c.items, k) return v.Object, true } } delete(c.items, k) return nil, false }
Get獲取數(shù)據(jù)
// Get an item from the cache. Returns the item or nil, and a bool indicating // whether the key was found. // 從cache中Get一個(gè)item.返回item或者nil,和一個(gè)bool值。 func (c *cache) Get(k string) (interface{}, bool) { c.mu.RLock() // "Inlining" of get and Expired item, found := c.items[k] if !found { c.mu.RUnlock() return nil, false } // 獲取到item,是否能返回還需要判斷過期時(shí)間 if item.Expiration > 0 { if time.Now().UnixNano() > item.Expiration { c.mu.RUnlock() return nil, false } } c.mu.RUnlock() return item.Object, true }
Set保存數(shù)據(jù)
set保存數(shù)據(jù),d的表達(dá)有三種情況:
- 為0,使用默認(rèn)的過期時(shí)間
- 為-1,永不過期
- 大于0的正常值,就是過期時(shí)間
// Add an item to the cache, replacing any existing item. If the duration is 0 // 將item添加到緩存中,以更換任何現(xiàn)有item。如果duration持續(xù)時(shí)間為0 // (DefaultExpiration), the cache's default expiration time is used. If it is -1 // (DefaultExpiration),使用緩存的默認(rèn)到期時(shí)間。如果是-1 // (NoExpiration), the item never expires. // (否開發(fā)),該項(xiàng)目永遠(yuǎn)不會(huì)到期。 func (c *cache) Set(k string, x interface{}, d time.Duration) { // "Inlining" of set var e int64 if d == DefaultExpiration { d = c.defaultExpiration } if d > 0 { e = time.Now().Add(d).UnixNano() } c.mu.Lock() c.items[k] = Item{ Object: x, Expiration: e, } // TODO: Calls to mu.Unlock are currently not deferred because defer // adds ~200 ns (as of go1.) c.mu.Unlock() }
常見問題
1、高并發(fā)
因?yàn)槭羌渔i在整個(gè)cache上,相比那些加鎖在分片上的其余緩存,并發(fā)會(huì)低一些。
2、關(guān)于內(nèi)存溢出
如果設(shè)置的清理時(shí)間為0,就是永不清理,或者時(shí)間過長,有可能導(dǎo)致緩存越來越多。
因?yàn)闆]有主動(dòng)清理,占用的緩存越鬧越大。
3、關(guān)于定時(shí)清理
如果時(shí)間過長,一次清理太大,又因?yàn)榧渔i整個(gè)cache,可能會(huì)導(dǎo)致其他的協(xié)程無法寫入。
4、關(guān)于map[string]interface{}存儲(chǔ)的值,有可能會(huì)變。
interface{},如果存的是數(shù)組,或者指針等,當(dāng)取出使用的時(shí)候,修改值,會(huì)導(dǎo)致緩存中的原始值變化。
以上就是patrickmn/go-cache源碼閱讀與分析的詳細(xì)內(nèi)容,更多關(guān)于patrickmn/go-cache源碼閱讀與分析的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用go的interface案例實(shí)現(xiàn)多態(tài)范式操作
這篇文章主要介紹了使用go的interface案例實(shí)現(xiàn)多態(tài)范式操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12golang 在windows中設(shè)置環(huán)境變量的操作
這篇文章主要介紹了golang 在windows中設(shè)置環(huán)境變量的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-04-04Go語言中strings.HasPrefix、strings.Split、strings.SplitN()?函數(shù)
本文主要介紹了Go語言中strings.HasPrefix、strings.Split、strings.SplitN()函數(shù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-08-08詳解Go語言中的數(shù)據(jù)類型及類型轉(zhuǎn)換
這篇文章主要為大家介紹了Go語言中常見的幾種數(shù)據(jù)類型,以及他們之間的轉(zhuǎn)換方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-04-04golang實(shí)現(xiàn)協(xié)程池的方法示例
本文主要介紹了golang實(shí)現(xiàn)協(xié)程池的方法示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02Go標(biāo)準(zhǔn)庫-ServeMux的使用與模式匹配深入探究
這篇文章主要為大家介紹了Go標(biāo)準(zhǔn)庫-ServeMux的使用與模式匹配深入探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01Golang實(shí)現(xiàn)自己的Redis數(shù)據(jù)庫內(nèi)存實(shí)例探究
這篇文章主要為大家介紹了Golang實(shí)現(xiàn)自己的Redis數(shù)據(jù)庫內(nèi)存實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01