Golang package lightweight kV data cache — go cache source code analysis

Time:2020-9-30

Author: moon light dream
source: https://www.cnblogs.com/Moon-Light-Dream/
Reprint: welcome to reprint, but without the consent of the author, this statement must be retained; the original text must be linked; otherwise, legal liability will be imposed

What is go cache

There are many kV storage engines, such as redis, rocksdb, and so on. If you only implement a simple kV cache in memory in actual use, it will be too expensive to use the above engines. In golang, you can use the go cache package to implement a lightweight memory based kV storage or cache. GitHub source code address is: https://github.com/patrickmn/go-cache 。
In fact, the go cache package implements a thread safe map [string] interface {} in memory. It can take any type of object as value without serializing or transmitting data through the network, so it is suitable for stand-alone applications. Different TTL (or permanent storage) can be set for each group of kV data, and overdue cleaning can be realized automatically.
Generally, go cache is used as a data cache instead of a persistent data storage. For the scenario of rapid recovery after shutdown, go cache supports saving the cache data to the file, and loading the data from the file to the memory during recovery.

How to use go cache

Common interface analysis

For the basic operation of database, CRUD (add, delete, modify and query) is no more concerned. The interface corresponding to go cache is as follows:

  • Create an object: you need to create a cache object before using it

    1. func New(defaultExpiration, cleanupInterval time.Duration) *Cache: specify the default valid time and purge interval to create the cache object.
      • If the default expiration is less than 1 or noexpiration, the data in kV will not be cleaned up and the interface must be manually called to delete it.
      • If the cleanupinterval < 1, the cleanup logic will not be triggered automatically, and C. deleteexpired() should be triggered manually.
    2. func NewFrom(defaultExpiration, cleanupInterval time.Duration, items map[string]Item) *Cache: different from the above interface, a map is added to the input parameter, which can construct the existing data according to the format and directly create the cache.
  • C (create): add a piece of data. Several interfaces in go cache can implement the new functions, but the usage scenarios are different

    1. func (c Cache) Add(k string, x interface{}, d time.Duration) error: only when the key does not exist or the value corresponding to the key has expired, success can be added; otherwise, an error will be returned.
    2. func (c Cache) Set(k string, x interface{}, d time.Duration): add a kV record to the cache.
      • If the key does not exist, add a kV record; if the key already exists, replace the old value with the new value.
      • For the effective time D, if it is 0 (default expiration), the default effective time is used; if it is – 1 (noexpiration), it means there is no expiration time.
    3. func (c Cache) SetDefault(k string, x interface{}): the same as set, except that the TTL uses the default valid time.
  • R (read): only supports reading by key

    1. func (c Cache) Get(k string) (interface{}, bool): get the value through the key. If there is no key in the cache, the returned value is nil, and a parameter of bool type is returned to indicate whether the key exists.
    2. func (c Cache) GetWithExpiration(k string) (interface{}, time.Time, bool): different from the get interface, the information about the validity period of the key is added in the return parameter. If it is a key that will not expire, the returned value is time.Time Zero value of type.
  • U (update): press key to update

    1. Direct useSetInterface, as mentioned above, if the key already exists, the old value will be replaced with the new value, which can also achieve the effect of updating.
    2. func (c Cache) Replace(k string, x interface{}, d time.Duration) error: if the key exists and is expired, update the corresponding value to the new value; otherwise, error is returned.
    3. func (c Cache) Decrement(k string, n int64) error: for records of type int, int8, int16, int32, Int64, uintptr, uint, uint8, uint32, or Uint64, float32, float64 in the cache, you can use this interface to reduce the value value by n. If the key does not exist or the value is not of the above type, an error is returned.
    4. DecrementXXX: for the various types mentioned in the decrement interface, there are corresponding interfaces to handle them. At the same time, these interfaces can get the results after the value changes. asfunc (c *cache) DecrementInt8(k string, n int8) (int8, error), you can get the result after value-n from the return value.
    5. func (c Cache) Increment(k string, n int64) error: usage andDecrementIn the same way, add n to the value corresponding to the key.
    6. IncrementXXX: usage andDecrementXXXThe same.
  • D(Delete)

    1. func (c Cache) Delete(k string): delete records according to the key. If the key does not exist, it will be ignored directly and no error will be reported.
    2. func (c Cache) DeleteExpired(): delete all expired records in the cache. When the cache is declared, it will specify the time interval for automatic cleaning, and the user can also manually trigger it through this interface.
    3. func (c Cache) Flush(): clear the cache and delete all records.
  • Other interfaces:

    1. func (c Cache) ItemCount() int: returns the number of records in the cache.It should be noted that the returned value may be larger than the actual value. Records that have expired but have not been cleaned up will be counted.
    2. func (c *cache) OnEvicted(f func(string, interface{})): set a callback function (optional), which is called when a record is deleted from the cache (the user actively deletes or the cache cleans up the expired records). Set to nil close operation.

Install go cache package

This paper introduces the common interface of go cache, and then looks at how to use it from the code. Before coding, you need to install go cache. The command is as follows.

go get github.com/patrickmn/go-cache

A demo

How to use the above interface in golang to implement the addition, deletion, modification and query of kV database. Next, let’s take a demo. For the usage of other interfaces and more detailed descriptions, please refer to godoc.

import (
	"fmt"
	"time"
	
	" github.com/patrickmn/go -Cache "// import package before using
)

func main() {
	//Create a cache object. The default TTL is 5 minutes, and the expired data is cleaned every 10 minutes
	c := cache.New(5*time.Minute, 10*time.Minute)

	//Set a kV, key is "foo", value is "bar"
	//TTL is the default value (the input parameter of the object created above, or different values can be set) for 5 minutes
	c.Set("foo", "bar", cache.DefaultExpiration)

	//A kV without TTL is set. It will be deleted only when the key is specified by calling the delete interface
	c.Set("baz", 42, cache.NoExpiration)

	//Get the value corresponding to the key from the cache
	foo, found := c.Get("foo")
	if found {
		fmt.Println(foo)
	}

	//If you want to improve performance, store pointer type values
	c.Set("foo", &MyStruct, cache.DefaultExpiration)
	if x, found := c.Get("foo"); found {
		foo := x.(*MyStruct)
			// ...
	}
}

Source code analysis

1. Constant: the two internally defined constants' noexpiration 'and' defaultexpiration 'can be used as input parameters in the above interface, ` noexpiration' indicates that no valid time has been set, and 'defaultexpiration' represents the default valid time passed in when using new() or newfrom() to create cache objects.
const (
	NoExpiration time.Duration = -1
	DefaultExpiration time.Duration = 0
)
2. Item: the value type stored in the cache. Object is the real value, and expiration is the expiration time. You can use the '' ` expired() '` interface of the item to determine whether it is due or not. The implementation method is to compare the current time with the expiration time set by the item to determine whether it has expired.
type Item struct {
	Object     interface{}
	Expiration int64
}

func (item Item) Expired() bool {
	if item.Expiration == 0 {
		return false
	}
	return time.Now().UnixNano() > item.Expiration
}
3. Cache: the core data structure of go cache, which defines the default expiration time of each record and the underlying storage structure.
type cache struct {
	defaultExpiration  time.Duration               //Default expiration time
	Items map [string] item // the underlying storage structure is implemented with map 
	mu                 sync.RWMutex                //Map itself is not thread safe and needs to be locked during operation
	Onevicked func (string, interface {}) // callback function, which triggers the corresponding operation when the record is deleted
	Janitor * janitor // key for timed polling failure
}
4. Janitor: the key used for timing polling invalidation, in which the polling period and a channel without cache are defined to receive the end information.
type janitor struct {
	Interval  time.Duration  //Timing polling cycle
	Stop Chan bool // is used to receive the end information
}

func (j *janitor) Run(c *cache) {
	ticker :=  time.NewTicker (j.interval) // create a timeticker timer trigger
	for {
		select {
		case

**For the handling of janitor, the skills used here are worth learning * *. The following code will open a goroutine to run janitor at the same time when the new() cache object is running. After running, you can see that it is doneruntime.SetFinalizerIn this way, the possible memory leak problem is solved.

func stopJanitor(c *Cache) {
	c.janitor.stop  0 {
		runJanitor(c, ci)
		runtime.SetFinalizer(C, stopJanitor)
	}
	return C
}

The possible leak scenarios are as follows: the user creates a cache object, which is set to nil after use. In the user’s opinion, it will be recycled during GC. However, because goroutine is referenced, it will not be recycled during GC, which leads to memory leakage.

c := cache.New()
    // do some operation
    c = nil

The solution can increase the Close interface, call the Close interface after use, pass the information through channel, end the goroutine, but if the user forgot to call the Close interface after use, it will cause memory leak.
Another solution is to use theruntime.SetFinalizerAfter checking that the object C has no reference, GC will execute the associated setfinalizer function, terminate goroutine actively, and cancel the association between Object C and setfinalizer function. In this way, the next GC time, the object C has no reference and can be recycled by GC.

summary

  1. The source code of go cache is very small, and the code structure and processing logic are relatively simple. It can be used as a good material for golang novices to read.
  2. For single machine lightweight memory cache, if only from the perspective of function implementation, go cache is a good choice and easy to use.
  3. However, in practice, attention should be paid to the following aspects:
    • Go cache does not limit the size of memory used or the amount of storage, which may lead to high peak memory;
    • The value stored in go cache should use the pointer type as much as possible. Compared with the storage object, it will not only improve the performance, but also have advantages in memory consumption. Due to the GC mechanism of golang, the original memory occupied by map will not be released immediately after expansion. Therefore, if value stores objects, a large amount of memory will not be released.

Recommended Today

Singularity iPhone version officially launched

Recently, I haven’t updated my short book, technology blog, CocoaChina, etc. I’ve been busy developing my own product singularity app. I hope to solve our technical problems in this high-quality “app ape” product, so that more people know the singularity. We dig high-quality Internet technology articles every day for you to recommend (currently, it supports […]