1. 程式人生 > 其它 >GO 自定義Cache

GO 自定義Cache

GO 自定義Cache


DEMO



package main

import (
	"fmt"
	"sync"
	"time"
)

// 快取物件
type CacheItem struct {
	Value     interface{}   // 實際快取的物件
	TTL       time.Duration // 存活時間
	CreatedAt time.Time     // 建立時間,和 TTL 一起決定是否過期
}

// 快取是否過期
func (c *CacheItem) Expired() bool {
	return time.Now().Sub(c.CreatedAt) > c.TTL
}

// 本地快取實現類
type LocalCache struct {
	sync.RWMutex                       // 繼承讀寫鎖,用於併發控制
	Items        map[string]*CacheItem // K-V儲存
	GCDuration   time.Duration         // 惰性刪除, 後臺執行時間間隔
}

// 新建本地快取
func NewLocalCache(gcDuration time.Duration) *LocalCache {
	localCache := &LocalCache{Items: map[string]*CacheItem{}, GCDuration: gcDuration}

	// 啟動協程,定期掃描過期鍵,進行刪除
	go localCache.GC()

	return localCache
}

// 存入物件
func (lc *LocalCache) Set(key string, value interface{}, ttl time.Duration) {
	lc.Lock()
	defer lc.Unlock()

	lc.Items[key] = &CacheItem{
		Value:     value,
		TTL:       ttl,
		CreatedAt: time.Now(),
	}
}

// 查詢物件 key不存在或過期返回: nil 正常返回: 實際儲存的物件 CacheItem.Value
func (lc *LocalCache) Get(key string) interface{} {
	lc.RLock()
	defer lc.RUnlock()

	if item, ok := lc.Items[key]; ok {
		if !item.Expired() {
			return item.Value
		} else {
			// 鍵已過期, 直接刪除
			// 需要注意的是,這裡不能呼叫lc.Del()方法,因為go的讀寫鎖是不支援鎖升級的
			delete(lc.Items, key)
		}
	}

	return nil
}

// 刪除快取
func (lc *LocalCache) Del(key string) {
	lc.Lock()
	defer lc.Unlock()

	if _, ok := lc.Items[key]; ok {
		delete(lc.Items, key)
	}
}

// 非同步執行,掃描過期鍵並刪除
func (lc *LocalCache) GC() {
	for {
		select {
		case <-time.After(lc.GCDuration):
			keysToExpire := []string{}

			lc.RLock()
			for key, item := range lc.Items {
				if item.Expired() {
					keysToExpire = append(keysToExpire, key)
				}
			}
			lc.RUnlock()

			for _, k := range keysToExpire {
				lc.Del(k)
			}
		}
	}
}

func main() {
	// 建立一個每 10 秒鐘惰性清理過期物件的 cache
	cache := NewLocalCache(10 * time.Second)

	// 獲取一個不存在的 key
	get, _ := cache.Get("todo").(string)
	fmt.Printf("get = %v\n", get)

	// 設定 過期時間為 5秒 的 k-v
	cache.Set("todo", "something", 5*time.Second)

	// 獲取存在且未過期的 k-v
	get2, _ := cache.Get("todo").(string)
	fmt.Printf("get2 = %v\n", get2)

	// 獲取過期的 k-v, 並檢視是否被清理
	time.Sleep(5 * time.Second)
	get3, _ := cache.Get("todo").(string)
	fmt.Printf("get3 = %v\n", get3)
	fmt.Printf("cache = %v\n", cache)

	// ----------------------------

	// 設定一個5秒過期的 k-v 等待惰性回收時間後 檢視是否被清理
	cache.Set("test", "hello", 5*time.Second)
	get4, _ := cache.Get("test").(string)
	fmt.Printf("get4 = %v\n", get4)
	fmt.Printf("cache = %v\n", cache)

	time.Sleep(10*time.Second)
	fmt.Printf("等待10s後cache = %v\n", cache)
}