introduction
You will not suffer any losses after learning, and you will not be deceived after learning. You must have basic skills when you enter the factory to make nails. You will definitely feel very happy after reading it. The core code is300Many lines, few codes, but no discounts on functions
What did you learn from this project?
Golang basic syntax
Cache data structure
Lock usage (concurrency security & sharding to reduce lock granularity)
LRU (cache elimination algorithm)
Key expiration deletion policy (timed deletion)
Writing test cases
Code Principle
New function
Responsible for creating*EasyCache
Object, the underlying layer of the object containsThe purpose of shards is to reduce lock conflicts
func New(conf Config) (*EasyCache, error) { if !() { returnnil, ("shards number must be power of two") } if <= 0 { = defaultCap } // init cache object cache := &EasyCache{ shards: make([]*cacheShard, ), conf: conf, hash: , shardMask: uint64( - 1), // mask close: make(chanstruct{}), } var onRemove OnRemoveCallback if != nil { onRemove = } else { onRemove = } // init shard for i := 0; i < ; i++ { [i] = newCacheShard(conf, i, onRemove, ) } return cache, nil }
newCacheShard function
Used to initialize actual storagek/v
Data structure*cacheShard
(that is, a single shard). The storage at the bottom layer of shard uses two maps and one list:
items
Responsible for preserving allk/v
(End or expired)expireItems
Responsible for keeping expiredk/v
, the purpose is to reduce the amount of data scan key`list
Used asLRU
Record the least recent usekey
order. Read this article for LRU code implementationLeetcode LRU problem solution, helps to understand the details of LRU in this project.
func newCacheShard(conf Config, id int, onRemove OnRemoveCallback, close chan struct{}) *cacheShard { shard := &cacheShard{ items: make(map[string]*), expireItems: make(map[string]*), cap: , list: (), logger: newLogger(), cleanupInterval: defaultInternal, cleanupTicker: (defaultInternal), addChan: make(chanstring), isVerbose: , id: id, onRemove: onRemove, close: close, } // goroutine clean expired key go () return shard }
expireCleanup
Responsible for regularly deleting expired keys in this shard: The key to code understanding lies in differentkey
There will be different expiration times, such as key=a expiration time 3s, key=b expiration time 5s.
The timer execution interval should not be too long. For example, 10s, a/b has expired and will not be cleaned yet, it is too timely
The execution interval of the timer timer cannot be too short, for example, 1s, the execution frequency is too high, a/b has not expired, and it is idle.
The interval will definitely change dynamically. At the beginning, it is a 3s interval. After execution, clean up a. At this time, b still has (5-3) = 2s survival time, so the interval is set to 2s. After the execution is completed, there is no data, and the interval is set to a large value.
smallestInternal = defaultInternal
In sleep
Let’s think about another situation here. According to the above explanation, set the interval between 3 seconds at the beginning, and you can clean up a when it expires. If the user sets the key=c expiration time 1s at this time, then if the timer executes in 3s, it becomes too long. So we need to send a signal:
, reset the interval
/* 1. When the timer expires, perform expired cleaning. 2. When the newly added key has an expiration time, the execution is triggered by addChan */ func (cs *cacheShard) expireCleanup() { for { select { case <-: case <-: // Trigger immediately case <-: // stop goroutine if { ("[shard %d] flush..", ) } () // free return } () // Record the minimum interval of the next timer (purpose: the key expires, delete it as soon as possible) smallestInternal := 0 * now := () () for key, ele := range { // traverse expired keys item := .(*cacheItem) if () == 0 { // No expiration time ("warning wrong data\n") continue } if (()) >= () { // Expired // del delete(, key) delete(, key) (ele) (key, (), Expired) if { ("[shard %d]: expire del key <%s> createdOn:%v, lifeSpan:%d ms \n", , key, (), ().Milliseconds()) } } else { d := () - (()) if smallestInternal == 0 || d < smallestInternal { smallestInternal = d } } } if smallestInternal == 0 { smallestInternal = defaultInternal } = smallestInternal () () } }
set function understanding
The key is that users can repeatedly set the same key:
(key, 0, 5*) // expire 5s (key, 0, 0*) // expire 0s
The first time it is set to expire 5s, and it is immediately modified to expire 0s, so it is necessary to determine whether the key has existed before.
If there is a duplication & there is an expiration time, it needs to be expired
expireItems
ExcludedIf there is no new one, just add it directly (prerequisite: there is still remaining capacity)
Basic rules of LRU
The latest data is placed in the list Front
If the maximum capacity is exceeded, delete the element from the Back of the list
func (cs *cacheShard) set(key string, value interface{}, lifeSpan ) error { () defer () oldEle, ok := [key] if ok { // old item oldItem := .(*cacheItem) oldLifeSpan := () // modify = newCacheItem(key, value, lifeSpan) (oldEle) if oldLifeSpan &gt; 0 &amp;&amp; lifeSpan == 0 { // The original one has an expiration time, the new one has no expiration time delete(, key) } if oldLifeSpan == 0 &amp;&amp; lifeSpan &gt; 0 { // The original no expiration time, the current expiration time [key] = oldEle if lifeSpan &lt; { gofunc() { &lt;- key }() } } } else { // new item iflen() &gt;= int() { // lru: No space delVal := (()) item := delVal.(*cacheItem) delete(, ()) if () &gt; 0 { delete(, ()) } (key, (), NoSpace) if { ("[shard %d] no space del key &lt;%s&gt;\n", , ()) } } // add ele := (newCacheItem(key, value, lifeSpan)) [key] = ele if lifeSpan &gt; 0 { [key] = ele if lifeSpan &lt; { gofunc() { &lt;- key }() } } } if { if lifeSpan == 0 { ("[shard %d]: set persist key &lt;%s&gt;\n", , key) } else { ("[shard %d]: set expired key &lt;%s&gt;", , key) } } returnnil }
The above is the detailed content of the Golang implementation of EasyCache cache library example. For more information about Golang EasyCache cache library, please follow my other related articles!