企网站建设,网站嵌入播放器,成都软件培训机构排名前十,如何开一个微信公众号本文主要介绍go语言中本地缓存的使用#xff0c;首先由简单到复杂手写3个本地缓存示例#xff0c;使用内置的sync#xff0c;map等数据结构封装cache#xff0c;然后介绍常见的一些开源库#xff0c;以及对比常用的开源库 文章目录 前言手写本地缓存CacheNormalCacheExCac… 本文主要介绍go语言中本地缓存的使用首先由简单到复杂手写3个本地缓存示例使用内置的syncmap等数据结构封装cache然后介绍常见的一些开源库以及对比常用的开源库 文章目录 前言手写本地缓存CacheNormalCacheExCacheV3 开源库cache2gogo-cachebigcachegroupcache本地缓存对比 前言
本地缓存是指将一部分数据存储在应用程序本地内存中以提高数据访问速度和应用程序性能的技术。 使用本地缓存的优势
提高应用程序性能减少网络延迟改善用户体验降低外部存储系统的负荷
下面我们从简单到复杂写本地缓存
手写本地缓存
CacheNormal
在 Go 中你可以使用内置的 sync 包和 map 数据结构来实现本地缓存。
我们首先定义了一个名为 Cache 的结构体其中包含一个 data 字段它是一个 map[string]interface{} 类型的数据结构用于存储键值对。我们使用 sync.RWMutex 来保证并发安全性。
然后我们定义了 Set 方法和 Get 方法用于设置和获取缓存值。在 Set 方法中我们使用互斥锁 mu 来保证并发安全。在 Get 方法中我们使用读写锁 mu 的读锁来实现并发读取。
package cacheimport (sync
)type CacheNormal struct {data map[string]interface{}mu sync.RWMutex
}func NewCache() *CacheNormal {return CacheNormal{data: make(map[string]interface{}),}
}func (c *CacheNormal) Set(key string, value interface{}) {c.mu.Lock()defer c.mu.Unlock()c.data[key] value
}func (c *CacheNormal) Get(key string) (interface{}, bool) {c.mu.RLock()defer c.mu.RUnlock()value, ok : c.data[key]return value, ok
}
代码测试
package cacheimport (fmttestingtime
)func TestCacheNorm(t *testing.T) {cache : NewCache()// 设置缓存值cache.Set(key1, value1)cache.Set(key2, value2)// 读取缓存值value1, ok1 : cache.Get(key1)fmt.Println(Key1:, value1, ok1)value2, ok2 : cache.Get(key2)fmt.Println(Key2:, value2, ok2)// 等待一段时间time.Sleep(5 * time.Second)// 再次读取缓存值value1, ok1 cache.Get(key1)fmt.Println(Key1:, value1, ok1)value2, ok2 cache.Get(key2)fmt.Println(Key2:, value2, ok2)
}结果展示 下面我们实现一个带有过期时间的本地缓存。
CacheEx
要实现带有过期时间的本地缓存可以使用 Go 的 sync 包和 map 数据结构结合定时器time.Timer来实现。
我们定义了一个名为 CacheEx 的结构体其中包含了一个用于存储缓存项的 data 字段并且还有一个用于接收过期键的通道 expireCh。
通过调用 NewCacheEx 函数创建一个新的缓存对象该函数会启动一个协程 startCleanup 来定期清理过期的缓存项。
使用 Set 方法来设置缓存值并指定缓存项的过期时间。在这个方法中我们使用互斥锁来保证并发安全性并将缓存项的过期时间和值存储在 data 中。同时我们还使用 scheduleExpiration 方法来安排过期时的清理操作。
使用 Get 方法来获取缓存值。在这个方法中我们使用读锁来进行并发读取并检查缓存项是否过期。如果缓存项存在且未过期则返回对应的值否则返回空值。
package cacheimport (synctime
)type CacheEx struct {data map[string]cacheItemmu sync.RWMutexexpireCh chan string
}type cacheItem struct {value interface{}expiration time.Time
}func NewCacheEx() *CacheEx {c : CacheEx{data: make(map[string]cacheItem),expireCh: make(chan string),}go c.startCleanup()return c
}func (c *CacheEx) Set(key string, value interface{}, expiration time.Duration) {c.mu.Lock()defer c.mu.Unlock()expireTime : time.Now().Add(expiration)c.data[key] cacheItem{value: value,expiration: expireTime,}go c.scheduleExpiration(key, expireTime)
}func (c *CacheEx) Get(key string) (interface{}, bool) {c.mu.RLock()defer c.mu.RUnlock()item, ok : c.data[key]if ok item.expiration.After(time.Now()) {return item.value, true}return nil, false
}func (c *CacheEx) Delete(key string) {c.mu.Lock()defer c.mu.Unlock()delete(c.data, key)
}func (c *CacheEx) startCleanup() {for {key : -c.expireChc.Delete(key)}
}func (c *CacheEx) scheduleExpiration(key string, expireTime time.Time) {duration : time.Until(expireTime)timer : time.NewTimer(duration)-timer.Cc.expireCh - key
}代码测试
func TestCacheExpireTime(t *testing.T) {cache : NewCacheEx()// 设置缓存值带有过期时间cache.Set(key1, value1, 2*time.Second)cache.Set(key2, value2, 5*time.Second)// 读取缓存值value1, ok1 : cache.Get(key1)fmt.Println(Key1:, value1, ok1)value2, ok2 : cache.Get(key2)fmt.Println(Key2:, value2, ok2)// 等待一段时间time.Sleep(3 * time.Second)// 再次读取缓存值value1, ok1 cache.Get(key1)fmt.Println(Key1:, value1, ok1)value2, ok2 cache.Get(key2)fmt.Println(Key2:, value2, ok2)
}结果展示
CacheV3
package cacheimport (synctime
)type item struct {value interface{}expiration int64
}type CacheV3 struct {items sync.Maplock sync.RWMutexdefaultTTL time.DurationmaxCapacity intevictList []interface{}
}func NewCacheV3(defaultTTL time.Duration, maxCapacity int) *CacheV3 {return CacheV3{defaultTTL: defaultTTL,maxCapacity: maxCapacity,evictList: make([]interface{}, 0, maxCapacity),}
}func (c *CacheV3) Set(key string, value interface{}, ttl time.Duration) {c.lock.Lock()defer c.lock.Unlock()if c.cacheSize() c.maxCapacity {c.evict(1)}if ttl 0 {ttl c.defaultTTL}expiration : time.Now().Add(ttl).UnixNano()c.items.Store(key, item{value, expiration})time.AfterFunc(ttl, func() {c.lock.Lock()defer c.lock.Unlock()if _, found : c.items.Load(key); found {c.items.Delete(key)c.evictList append(c.evictList, key)}})
}func (c *CacheV3) Get(key string) (interface{}, bool) {c.lock.RLock()defer c.lock.RUnlock()if val, found : c.items.Load(key); found {item : val.(*item)if item.expiration 0 time.Now().UnixNano() item.expiration {c.items.Delete(key)return nil, false}return item.value, true}return nil, false
}func (c *CacheV3) evict(count int) {for i : 0; i count; i {key : c.evictList[0]c.evictList c.evictList[1:]c.items.Delete(key)}
}func (c *CacheV3) cacheSize() int {size : 0c.items.Range(func(_, _ interface{}) bool {sizereturn true})return size
}代码测试
func TestCacheV3(t *testing.T) {c : NewCacheV3(time.Minute, 100)c.Set(key1, value1, time.Second*30)c.Set(key2, value2, time.Minute)val, found : c.Get(key1)if found {fmt.Println(val)}time.Sleep(time.Second * 45)val, found c.Get(key1)if found {fmt.Println(val)}time.Sleep(time.Second * 30)val, found c.Get(key1)if found {fmt.Println(val)} else {fmt.Println(key1 expired)}
}结果展示
开源库
cache2go
最新代码请参考https://github.com/muesli/cache2go 以下代码仅供参考
type Item struct {//read write locksync.RWMutexkey interface{}data interface{}// cache duration.duration time.Duration// create timecreateTime time.Time//last access timeaccessTime time.Time//visit timescount int64// callback after deletingdeleteCallback func(key interface{})
}//create item.
func NewItem(key interface{}, duration time.Duration, data interface{}) *Item {t : time.Now()return Item{key: key,duration: duration,createTime: t,accessTime: t,count: 0,deleteCallback: nil,data: data,}
}//keep alive
func (item *Item) KeepAlive() {item.Lock()defer item.Unlock()item.accessTime time.Now()item.count
}func (item *Item) Duration() time.Duration {return item.duration
}func (item *Item) AccessTime() time.Time {item.RLock()defer item.RUnlock()return item.accessTime
}func (item *Item) CreateTime() time.Time {return item.createTime
}func (item *Item) Count() int64 {item.RLock()defer item.RUnlock()return item.count
}func (item *Item) Key() interface{} {return item.key
}func (item *Item) Data() interface{} {return item.data
}func (item *Item) SetDeleteCallback(f func(interface{})) {item.Lock()defer item.Unlock()item.deleteCallback f
}// table for managing cache items
type Table struct {sync.RWMutex//all cache itemsitems map[interface{}]*Item// trigger cleanupcleanupTimer *time.Timer// cleanup intervalcleanupInterval time.DurationloadData func(key interface{}, args ...interface{}) *Item// callback after adding.addedCallback func(item *Item)// callback after deletingdeleteCallback func(item *Item)
}func (table *Table) Count() int {table.RLock()defer table.RUnlock()return len(table.items)
}func (table *Table) Foreach(trans func(key interface{}, item *Item)) {table.RLock()defer table.RUnlock()for k, v : range table.items {trans(k, v)}
}func (table *Table) SetDataLoader(f func(interface{}, ...interface{}) *Item) {table.Lock()defer table.Unlock()table.loadData f
}func (table *Table) SetAddedCallback(f func(*Item)) {table.Lock()defer table.Unlock()table.addedCallback f
}func (table *Table) SetDeleteCallback(f func(*Item)) {table.Lock()defer table.Unlock()table.deleteCallback f
}func (table *Table) RunWithRecovery(f func()) {defer func() {if err : recover(); err ! nil {fmt.Printf(occur error %v \r\n, err)}}()f()
}func (table *Table) checkExpire() {table.Lock()if table.cleanupTimer ! nil {table.cleanupTimer.Stop()}if table.cleanupInterval 0 {table.log(Expiration check triggered after %v for table, table.cleanupInterval)} else {table.log(Expiration check installed for table)}// in order to not take the lock. use temp items.items : table.itemstable.Unlock()//in order to make timer more precise, update now every loop.now : time.Now()smallestDuration : 0 * time.Secondfor key, item : range items {//take out our things, in order not to take the lock.item.RLock()duration : item.durationaccessTime : item.accessTimeitem.RUnlock()// 0 means valid.if duration 0 {continue}if now.Sub(accessTime) duration {//cache item expired._, e : table.Delete(key)if e ! nil {table.log(occur error while deleting %v, e.Error())}} else {//find the most possible expire item.if smallestDuration 0 || duration-now.Sub(accessTime) smallestDuration {smallestDuration duration - now.Sub(accessTime)}}}//trigger next cleantable.Lock()table.cleanupInterval smallestDurationif smallestDuration 0 {table.cleanupTimer time.AfterFunc(smallestDuration, func() {go table.RunWithRecovery(table.checkExpire)})}table.Unlock()
}// add item
func (table *Table) Add(key interface{}, duration time.Duration, data interface{}) *Item {item : NewItem(key, duration, data)table.Lock()table.log(Adding item with key %v and lifespan of %d to table, key, duration)table.items[key] itemexpDur : table.cleanupIntervaladdedItem : table.addedCallbacktable.Unlock()if addedItem ! nil {addedItem(item)}//find the most possible expire item.if duration 0 (expDur 0 || duration expDur) {table.checkExpire()}return item
}func (table *Table) Delete(key interface{}) (*Item, error) {table.RLock()r, ok : table.items[key]if !ok {table.RUnlock()return nil, errors.New(fmt.Sprintf(no item with key %s, key))}deleteCallback : table.deleteCallbacktable.RUnlock()if deleteCallback ! nil {deleteCallback(r)}r.RLock()defer r.RUnlock()if r.deleteCallback ! nil {r.deleteCallback(key)}table.Lock()defer table.Unlock()table.log(Deleting item with key %v created on %s and hit %d times from table, key, r.createTime, r.count)delete(table.items, key)return r, nil
}//check exist.
func (table *Table) Exists(key interface{}) bool {table.RLock()defer table.RUnlock()_, ok : table.items[key]return ok
}//if exist, return false. if not exist add a key and return true.
func (table *Table) NotFoundAdd(key interface{}, lifeSpan time.Duration, data interface{}) bool {table.Lock()if _, ok : table.items[key]; ok {table.Unlock()return false}item : NewItem(key, lifeSpan, data)table.log(Adding item with key %v and lifespan of %d to table, key, lifeSpan)table.items[key] itemexpDur : table.cleanupIntervaladdedItem : table.addedCallbacktable.Unlock()if addedItem ! nil {addedItem(item)}if lifeSpan 0 (expDur 0 || lifeSpan expDur) {table.checkExpire()}return true
}func (table *Table) Value(key interface{}, args ...interface{}) (*Item, error) {table.RLock()r, ok : table.items[key]loadData : table.loadDatatable.RUnlock()if ok {//update visit count and visit time.r.KeepAlive()return r, nil}if loadData ! nil {item : loadData(key, args...)if item ! nil {table.Add(key, item.duration, item.data)return item, nil}return nil, errors.New(cannot load item)}return nil, nil
}// truncate a table.
func (table *Table) Truncate() {table.Lock()defer table.Unlock()table.log(Truncate table)table.items make(map[interface{}]*Item)table.cleanupInterval 0if table.cleanupTimer ! nil {table.cleanupTimer.Stop()}
}//support table sort
type ItemPair struct {Key interface{}AccessCount int64
}type ItemPairList []ItemPairfunc (p ItemPairList) Swap(i, j int) { p[i], p[j] p[j], p[i] }
func (p ItemPairList) Len() int { return len(p) }
func (p ItemPairList) Less(i, j int) bool { return p[i].AccessCount p[j].AccessCount }//return most visited.
func (table *Table) MostAccessed(count int64) []*Item {table.RLock()defer table.RUnlock()p : make(ItemPairList, len(table.items))i : 0for k, v : range table.items {p[i] ItemPair{k, v.count}i}sort.Sort(p)var r []*Itemc : int64(0)for _, v : range p {if c count {break}item, ok : table.items[v.Key]if ok {r append(r, item)}c}return r
}// print log.
func (table *Table) log(format string, v ...interface{}) {//fmt.Printf(format\r\n, v)
}func NewTable() *Table {return Table{items: make(map[interface{}]*Item),}
}go-cache
https://github.com/patrickmn/go-cache 优点 简单易用适合快速集成到现有项目中。支持过期时间可以自动淘汰过期的缓存项。支持多种数据类型的缓存。 缺点 性能略低于其他库不适合高并发读写的场景。不支持分布式缓存。
bigcache
https://github.com/allegro/bigcache 优点 高性能适用于需要快速读写大量数据的场景。使用murmurhash算法来计算哈希值减少了哈希冲突。使用多个shard来减少锁竞争。 缺点 不支持过期时间只能手动清除过期的缓存项。内存使用较高不适合存储大量数据。
groupcache
https://github.com/golang/groupcache 优点 支持分布式缓存可以在多台机器上共享缓存。采用LRU算法来淘汰缓存项具备一定的缓存性能。提供一致性哈希算法可以解决节点扩容等问题。 缺点 比较复杂使用起来较为繁琐。只支持字符串类型的键值对。
本地缓存对比
参考文档 https://zhuanlan.zhihu.com/p/487455942 https://www.jianshu.com/p/0ff2e8c61c9c?tdsourcetags_pctim_aiomsg 下面对每个库的详细介绍
go-cache
描述go-cache是一款简单而有效的内存缓存库支持设置过期时间和GC机制。并发安全是使用Go的sync.Map实现数据的并发安全存储和访问。存储限制无可以存储任意类型的数据。淘汰策略默认为LRU最近最少使用算法也支持手动删除过期的缓存项。分布式支持不支持。
freecache
描述freecache是一款高性能的内存缓存库使用LRU算法进行缓存项的淘汰。并发安全是使用读写锁实现并发安全访问。存储限制固定大小需要在初始化时指定总共可以缓存的字节数。淘汰策略默认为LRU最近最少使用算法不支持自定义。分布式支持不支持。
bigcache
描述bigcache是一款高性能的内存缓存库使用murmurhash哈希算法快速查找。并发安全是使用多个读写锁来实现高并发的访问控制。存储限制固定大小需要在初始化时指定最多可以缓存的条目数。淘汰策略默认为LRU最近最少使用算法不支持自定义。分布式支持不支持。
groupcache
描述groupcache是一款支持分布式缓存的库提供一致性哈希和HTTP请求缓存功能。并发安全是使用读写锁实现并发安全访问。存储限制无可以存储任意类型的数据。淘汰策略支持自定义淘汰策略例如手动删除过期的缓存项。分布式支持是支持分布式缓存将数据分片存储在多个节点上通过查询一致性哈希环来确定数据所在的节点。
gocache
描述gocache是一款快速、强大的内存缓存库支持过期时间、并发安全和自定义淘汰策略。并发安全是使用读写锁实现并发安全访问。存储限制无可以存储任意类型的数据。淘汰策略默认为LRU最近最少使用算法也支持自定义淘汰策略。分布式支持不支持。