Expiring cache
First, we're still using the var cache cmap.ConcurrentMap
Second, we need a new field to store, the expire information. So, let's save a struct as a value for the cache item.
type CacheItem struct { Key string Value []byte Expire time.Time }Adding an item to cache is almost the same, let's just take the expiring into account and also the set maximum size of the cache.
func AddItemToCache(key string, value []byte, expire time.Duration) { if cache.Count() <= cacheSize { cache.Set(key, CacheItem{Key: key, Value: value, Expire: time.Now().Add(expire)}) } else { panic("cacheSize reached") } }Getting an item from the cache is trivial. Again, let's take the expired items into account by removing them.
func GetItemFromCache(key string) *CacheItem { if cache.Has(key) { if tmp, ok := cache.Get(key); ok { item := tmp.(CacheItem) if item.Expire.After(time.Now()) { return &item } removeItem(item.Key) } } return nil }
Usages of an expiring cache
You can fill the cache with a scheduler on the background, or by filling the cache when you get a nil from a get. You can also take this further by fetching the almost expired item on the background and still giving the item from the cache. This way there is not so much cache misses when there is less traffic.
cacheItem := GetItemFromCache(weatherURLs[1]) if cacheItem != nil { w.Write(cacheItem.Value) } else { if weather := fetchWeather(weatherURLs[1]); weather != nil { j, _ := json.Marshal(&weather.Query.Results.Channel.Item) AddItemToCache(weatherURLs[1], j, time.Minute) w.Write(j) } else { json.NewEncoder(w).Encode("not found") } }
Removing stale items
In addition to removing stale items when bumping into them while getting an item from cache, we can remove stale items by runningfunc removeStaleCacheItems() { for _, item := range cache.Items() { cItem := item.(CacheItem) if time.Now().After(cItem.Expire) { removeItem(cItem.Key) } } }once in a minute for example. We loop all items and check if the current time is after expiration time. This is started as a background job in the init function.
func init() { go doEvery(time.Minute, removeStaleCacheItems) } func doEvery(d time.Duration, f func()) { for range time.Tick(d) { f() } }
Testing
Testing is otherwise about the same, but we have the new expiring test. Easy enough, just give a little time for an added item and then sleep a bit more than that. When getting the first added item after expiring, you get nil back as supposed to.func TestExpire(t *testing.T) { assert := assert.New(t) AddItemToCache("item", []byte("value"), time.Second) assert.Equal(string(GetItemFromCache("item").Value), "value", "should be equal") time.Sleep(time.Second * 2) assert.True(GetItemFromCache("item") == nil, "should be equal") }
The whole source code can be found from github.
Ei kommentteja:
Lähetä kommentti
Huomaa: vain tämän blogin jäsen voi lisätä kommentin.