最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang實(shí)現(xiàn)EasyCache緩存庫(kù)實(shí)例探究

 更新時(shí)間:2024年01月24日 10:59:26   作者:紹納 nullbody筆記  
這篇文章主要為大家介紹了Golang實(shí)現(xiàn)EasyCache緩存庫(kù)實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

學(xué)了不吃虧,學(xué)了不上當(dāng),進(jìn)廠打釘必備基本功,看完絕對(duì)有很爽的感覺。核心代碼也就300多行,代碼雖少但是功能一點(diǎn)不打折

通過本項(xiàng)目學(xué)到什么?

  • Golang基礎(chǔ)語(yǔ)法

  • 緩存數(shù)據(jù)結(jié)構(gòu)

  • 鎖的使用(并發(fā)安全 & 分片減小鎖粒度)

  • LRU(緩存淘汰算法)

  • key過期刪除策略(定時(shí)刪除)

  • 測(cè)試用例的編寫

代碼原理

New函數(shù)

負(fù)責(zé)創(chuàng)建 *EasyCache對(duì)象,對(duì)象的底層包含 conf.Shards個(gè)分片,目的在于減少鎖沖突

func New(conf Config) (*EasyCache, error) {

	if !utils.IsPowerOfTwo(conf.Shards) {
		returnnil, errors.New("shards number must be power of two")
	}

	if conf.Cap <= 0 {
		conf.Cap = defaultCap
	}
	// init cache object
	cache := &EasyCache{
		shards:    make([]*cacheShard, conf.Shards),
		conf:      conf,
		hash:      conf.Hasher,
		shardMask: uint64(conf.Shards - 1), // mask
		close:     make(chanstruct{}),
	}

	var onRemove OnRemoveCallback
	if conf.OnRemoveWithReason != nil {
		onRemove = conf.OnRemoveWithReason
	} else {
		onRemove = cache.notProvidedOnRemove
	}

	// init shard
	for i := 0; i < conf.Shards; i++ {
		cache.shards[i] = newCacheShard(conf, i, onRemove, cache.close)
	}
	return cache, nil
}

newCacheShard函數(shù)

用來初始化實(shí)際存放 k/v的數(shù)據(jù)結(jié)構(gòu)*cacheShard(也就是單個(gè)分片)。分片底層的存儲(chǔ)采用兩個(gè)map和一個(gè)list:

  • items負(fù)責(zé)保存所有的k/v(過期or不過期都有存)

  • expireItems負(fù)責(zé)保存有過期時(shí)間的k/v,目的在于減少掃描key`的數(shù)據(jù)量

  • list用作LRU記錄最近最少使用key的順序。LRU代碼實(shí)現(xiàn)看這篇文章 Leetcode LRU題解,有助于理解本項(xiàng)目中的LRU的細(xì)節(jié)。

func newCacheShard(conf Config, id int, onRemove OnRemoveCallback, close chan struct{}) *cacheShard {
	shard := &cacheShard{
		items:           make(map[string]*list.Element),
		expireItems:     make(map[string]*list.Element),
		cap:             conf.Cap,
		list:            list.New(),
		logger:          newLogger(conf.Logger),
		cleanupInterval: defaultInternal,
		cleanupTicker:   time.NewTicker(defaultInternal),
		addChan:         make(chanstring),
		isVerbose:       conf.Verbose,
		id:              id,
		onRemove:        onRemove,
		close:           close,
	}
	// goroutine clean expired key
	go shard.expireCleanup()
	return shard
}

expireCleanup

負(fù)責(zé)對(duì)本分片中過期的key進(jìn)行定期刪除:代碼理解的關(guān)鍵在于不同的key會(huì)有不同的過期時(shí)間,例如key=a 過期時(shí)間3s,key=b 過期時(shí)間5s。

  • 定時(shí)器定時(shí)執(zhí)行間隔不能太長(zhǎng),例如10s,a/b都已經(jīng)過期了還不清理,太不及時(shí)

  • 定時(shí)器定時(shí)執(zhí)行間隔不能太短,例如1s,執(zhí)行頻率又太高了,a/b都未過期,空轉(zhuǎn)

  • 過期間隔肯定是動(dòng)態(tài)變化的,一開始為3s間隔,執(zhí)行后清理掉a,此時(shí)b還剩(5-3)=2s的存活時(shí)間,所以間隔再設(shè)定為2s。再執(zhí)行完以后,沒有數(shù)據(jù)了,那間隔就在設(shè)定一個(gè)大值smallestInternal = defaultInternal處于休眠狀態(tài)

這里再思考一種情況,按照上述解釋一開始間隔設(shè)定3s,等到過期了就可以將a清理掉。那如果用戶這時(shí)又設(shè)定了key=c 過期時(shí)間1s,那如果定時(shí)器按照3s執(zhí)行又變成了間隔太長(zhǎng)了。所以我們需要發(fā)送信號(hào)cs.addChan:,重新設(shè)定過期間隔

/*
1.當(dāng)定時(shí)器到期,執(zhí)行過期清理
2.當(dāng)新增的key有過期時(shí)間,通過addChan觸發(fā)執(zhí)行
*/
func (cs *cacheShard) expireCleanup() {
	for {
		select {
		case <-cs.cleanupTicker.C:
		case <-cs.addChan: // 立即觸發(fā)
		case <-cs.close: // stop goroutine
			if cs.isVerbose {
				cs.logger.Printf("[shard %d] flush..", cs.id)
			}
			cs.flush() // free
			return
		}
		cs.cleanupTicker.Stop()
		// 記錄下一次定時(shí)器的最小間隔(目的:key過期了,盡快刪除)
		smallestInternal := 0 * time.Second
		now := time.Now()
		cs.lock.Lock()
		for key, ele := range cs.expireItems { // 遍歷過期key
			item := ele.Value.(*cacheItem)
			if item.LifeSpan() == 0 { // 沒有過期時(shí)間
				cs.logger.Printf("warning wrong data\n")
				continue
			}
			if now.Sub(item.CreatedOn()) >= item.LifeSpan() { // 過期
				// del
				delete(cs.items, key)
				delete(cs.expireItems, key)
				cs.list.Remove(ele)
				cs.onRemove(key, item.Value(), Expired)
				if cs.isVerbose {
					cs.logger.Printf("[shard %d]: expire del key <%s>  createdOn:%v,  lifeSpan:%d ms \n", cs.id, key, item.CreatedOn(), item.LifeSpan().Milliseconds())
				}
			} else {
				d := item.LifeSpan() - now.Sub(item.CreatedOn())
				if smallestInternal == 0 || d < smallestInternal {
					smallestInternal = d
				}
			}
		}
		if smallestInternal == 0 {
			smallestInternal = defaultInternal
		}
		cs.cleanupInterval = smallestInternal
		cs.cleanupTicker.Reset(cs.cleanupInterval)
		cs.lock.Unlock()
	}
}

set 函數(shù)理解

關(guān)鍵在于,用戶可以對(duì)同一個(gè)key重復(fù)設(shè)定:

cache.Set(key, 0, 5*time.Second) // expire 5s
cache.Set(key, 0, 0*time.Second) // expire 0s

第一次設(shè)定為5s過期,立刻又修改為0s不過期,所以在代碼中需要判斷key是否之前已經(jīng)存在,

  • 如果存在重復(fù)&有過期時(shí)間,需要從過期expireItems中剔除

  • 如果不存在直接新增即可(前提:容量還有剩余)

LRU的基本規(guī)則

  • 最新數(shù)據(jù)放到list的Front

  • 如果超過最大容量,從list的Back刪除元素

func (cs *cacheShard) set(key string, value interface{}, lifeSpan time.Duration) error {

	cs.lock.Lock()
	defer cs.lock.Unlock()

	oldEle, ok := cs.items[key]
	if ok { // old item
		oldItem := oldEle.Value.(*cacheItem)
		oldLifeSpan := oldItem.LifeSpan()

		// modify
		oldEle.Value = newCacheItem(key, value, lifeSpan)
		cs.list.MoveToFront(oldEle)

		if oldLifeSpan &gt; 0 &amp;&amp; lifeSpan == 0 { // 原來的有過期時(shí)間,新的沒有過期時(shí)間
			delete(cs.expireItems, key)
		}

		if oldLifeSpan == 0 &amp;&amp; lifeSpan &gt; 0 { // 原有的無過期時(shí)間,當(dāng)前有過期時(shí)間
			cs.expireItems[key] = oldEle
			if lifeSpan &lt; cs.cleanupInterval {
				gofunc() {
					cs.addChan &lt;- key
				}()
			}
		}

	} else { // new item

		iflen(cs.items) &gt;= int(cs.cap) { // lru: No space
			delVal := cs.list.Remove(cs.list.Back())
			item := delVal.(*cacheItem)
			delete(cs.items, item.Key())
			if item.LifeSpan() &gt; 0 {
				delete(cs.expireItems, item.Key())
			}
			cs.onRemove(key, item.Value(), NoSpace)

			if cs.isVerbose {
				cs.logger.Printf("[shard %d] no space del key &lt;%s&gt;\n", cs.id, item.Key())
			}
		}
		// add
		ele := cs.list.PushFront(newCacheItem(key, value, lifeSpan))
		cs.items[key] = ele
		if lifeSpan &gt; 0 {
			cs.expireItems[key] = ele
			if lifeSpan &lt; cs.cleanupInterval {
				gofunc() {
					cs.addChan &lt;- key
				}()
			}
		}
	}

	if cs.isVerbose {
		if lifeSpan == 0 {
			cs.logger.Printf("[shard %d]: set persist key &lt;%s&gt;\n", cs.id, key)
		} else {
			cs.logger.Printf("[shard %d]: set expired key &lt;%s&gt;", cs.id, key)
		}
	}
	returnnil
}

以上就是Golang實(shí)現(xiàn)EasyCache緩存庫(kù)實(shí)例探究的詳細(xì)內(nèi)容,更多關(guān)于Golang EasyCache緩存庫(kù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang之如何讀取文件內(nèi)容

    Golang之如何讀取文件內(nèi)容

    這篇文章主要介紹了Golang之如何讀取文件內(nèi)容問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • golang中定時(shí)器cpu使用率高的現(xiàn)象詳析

    golang中定時(shí)器cpu使用率高的現(xiàn)象詳析

    這篇文章主要給大家介紹了關(guān)于golang中定時(shí)器cpu使用率高的現(xiàn)象的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-04-04
  • golang時(shí)間、時(shí)區(qū)、格式的使用方法

    golang時(shí)間、時(shí)區(qū)、格式的使用方法

    這篇文章主要介紹了golang時(shí)間、時(shí)區(qū)、格式的使用方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2019-04-04
  • Go語(yǔ)言map用法實(shí)例分析

    Go語(yǔ)言map用法實(shí)例分析

    這篇文章主要介紹了Go語(yǔ)言map用法,實(shí)例分析了map的功能及使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02
  • golang監(jiān)聽ip數(shù)據(jù)包的實(shí)現(xiàn)步驟(golang純享版)

    golang監(jiān)聽ip數(shù)據(jù)包的實(shí)現(xiàn)步驟(golang純享版)

    這篇文章主要給大家介紹了golang監(jiān)聽ip數(shù)據(jù)包的實(shí)現(xiàn)步驟,本文以ip4 作為案例進(jìn)行包抓取示范,ip6抓取與ip4方式異曲同工,可自行舉一反三得出,文中通過圖文結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2024-02-02
  • Go的固定時(shí)長(zhǎng)定時(shí)器和周期性時(shí)長(zhǎng)定時(shí)器

    Go的固定時(shí)長(zhǎng)定時(shí)器和周期性時(shí)長(zhǎng)定時(shí)器

    本文主要介紹了Go的固定時(shí)長(zhǎng)定時(shí)器和周期性時(shí)長(zhǎng)定時(shí)器,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • 一文秒懂Go 編寫命令行工具的代碼

    一文秒懂Go 編寫命令行工具的代碼

    這篇文章主要介紹了一文秒懂Go 編寫命令行工具的代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01
  • Golang中的path/filepath包用法

    Golang中的path/filepath包用法

    這篇文章主要介紹了Golang中的path/filepath包用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • Golang中指針的使用詳解

    Golang中指針的使用詳解

    Golang是一門支持指針的編程語(yǔ)言,指針是一種特殊的變量,存儲(chǔ)了其他變量的地址。通過指針,可以在程序中直接訪問和修改變量的值,避免了不必要的內(nèi)存拷貝和傳遞。Golang中的指針具有高效、安全的特點(diǎn),在并發(fā)編程和底層系統(tǒng)開發(fā)中得到廣泛應(yīng)用
    2023-04-04
  • Go語(yǔ)言interface詳解

    Go語(yǔ)言interface詳解

    這篇文章主要介紹了Go語(yǔ)言interface詳解,本文講解了什么是interface、interface類型、interface值、空interface、interface函數(shù)參數(shù)等內(nèi)容,需要的朋友可以參考下
    2014-10-10

最新評(píng)論

攀枝花市| 颍上县| 七台河市| 鲁山县| 铜山县| 平乡县| 达日县| 常熟市| 隆尧县| 阿拉尔市| 营口市| 汪清县| 新建县| 军事| 万盛区| 龙州县| 贵德县| 凤庆县| 双桥区| 武胜县| 北安市| 申扎县| 昌宁县| 台南县| 湟源县| 长丰县| 怀宁县| 木里| 长岭县| 郁南县| 厦门市| 荔浦县| 香河县| 常宁市| 铁岭市| 云梦县| 莱芜市| 崇义县| 巫山县| 镇安县| 德清县|