github.com/patrickmn/go-cache是知名golang local cache实现里面最简单的一种,可以理解为就是简单的map加锁实现的,它通过定时器来进行过期key的淘汰,并且利用runtime.SetFinalizer(C, stopJanitor)来停止我们的过期key协程,会在倒数第二个gc周期的时候,调用该方法,关闭我们的定时清理协程。首先看下如何使用它
package main
import (
"fmt"
"time"
"github.com/patrickmn/go-cache"
)
func main() { {
c := cache.New(5*time.Minute, 10*time.Minute)
c.Set("foo", "bar", cache.DefaultExpiration)
foo, found := c.Get("foo")
if found {
fmt.Println(foo)
}
}
1,cache.New() 会创建一个缓存对象,它需要两个参数:
c := cache.New(5*time.Minute, 10*time.Minute)
第一个参数:表示缓存项的过期时间,如上示例表示 5 分钟后过期。
第二个参数:表示缓存项的清除时间,如上示例表示 10 分钟后自动清除。
2,c.Set() 需要传递三个参数:
c.Set("key", "value", cache.DefaultExpiration)
第一个参数:指定缓存项的 key。
第二个参数:指定缓存项的 value。
第三个参数:指定该项目的过期时间。注意:一,此处指定的时间优先级大于 New 时的时间,如果这里指定为 2 分钟,则该缓存项将在 2 分钟后过期;二,如果配置为 cache.DefaultExpiration 则表示缓存项永不过期。
3,Get
使用 Get 方法从缓存中获取一个键值对:
value, found := c.Get("key")
if found {
fmt.Println(value)
}
介绍完使用规则后我们开始分析源码,它只有两个文件cache.go和sharded.go,首先看下第一个文件。它定义存储对象类型,包含两个参数,Object和对应的过期时间。
type Item struct {
Object interface{}
Expiration int64
}
NoExpiration time.Duration = -1
DefaultExpiration time.Duration = 0
分别表示永不过期和使用创建cache对象的时候使用的默认过期时间。
type Cache struct {
*cache
// If this is confusing, see the comment at the bottom of New()
}
type cache struct {
defaultExpiration time.Duration
items map[string]Item
mu sync.RWMutex
onEvicted func(string, interface{})
janitor *janitor
}
cache对象就是传说中的map+锁+过期清理函数。其中janitor,定义了清理函数定期执行的时间和接受停止信号的chan
type janitor struct {
Interval time.Duration
stop chan bool
}
下面看下设置存储对象接口,它的第三个参数过期时间0,使用默认超时,-1不超时
func (c *cache) Set(k string, x interface{}, d time.Duration) {
c.mu.Lock()
c.items[k] = Item{
Object: x,
Expiration: e,
}
// TODO: Calls to mu.Unlock are currently not deferred because defer
// adds ~200 ns (as of go1.)
c.mu.Unlock()
func (c *cache) set(k string, x interface{}, d time.Duration) {
func (c *cache) SetDefault(k string, x interface{}) {
c.Set(k, x, DefaultExpiration)
}
func (c *cache) Add(k string, x interface{}, d time.Duration) error {
c.mu.Lock()
_, found := c.get(k)
if found {
c.mu.Unlock()
return fmt.Errorf("Item %s already exists", k)
}
c.set(k, x, d)
c.mu.Unlock()
return nil
}
func (c *cache) Replace(k string, x interface{}, d time.Duration) error {
get函数就是从map里面取对象。
func (c *cache) Get(k string) (interface{}, bool) {
c.mu.RLock()
// "Inlining" of get and Expired
item, found := c.items[k]
func (c *cache) GetWithExpiration(k string) (interface{}, time.Time, bool) {
func (c *cache) Increment(k string, n int64) error {
func (c *cache) IncrementFloat(k string, n float64) error {
func (c *cache) IncrementInt64(k string, n int64) (int64, error) {
func (c *cache) Decrement(k string, n int64) error {
自增和递减函数对每个基础类型都定义了一版,可以考虑结合范型优化一把代码了。
删除代码的时候要判断是否设置了资源清理函数,如果设置了就会执行资源清理函数。
func (c *cache) Delete(k string) {
c.mu.Lock()
v, evicted := c.delete(k)
c.mu.Unlock()
if evicted {
c.onEvicted(k, v)
}
}
func (c *cache) delete(k string) (interface{}, bool) {
if c.onEvicted != nil {
if v, found := c.items[k]; found {
delete(c.items, k)
return v.Object, true
}
}
delete(c.items, k)
return nil, false
}
type keyAndValue struct {
key string
value interface{}
}
func (c *cache) DeleteExpired() {
for k, v := range c.items {
// "Inlining" of expired
if v.Expiration > 0 && now > v.Expiration {
ov, evicted := c.delete(k)
if evicted {
evictedItems = append(evictedItems, keyAndValue{k, ov})
}
}
}
c.mu.Unlock()
for _, v := range evictedItems {
c.onEvicted(v.key, v.value)
}
func (c *cache) OnEvicted(f func(string, interface{})) {
c.mu.Lock()
c.onEvicted = f
c.mu.Unlock()
}
可以支持序列化到gob格式,或者序列化到文件中。
func (c *cache) Save(w io.Writer) (err error) {
enc := gob.NewEncoder(w)
func (c *cache) SaveFile(fname string) error {
fp, err := os.Create(fname)
if err != nil {
return err
}
err = c.Save(fp)
func (c *cache) Load(r io.Reader) error {
dec := gob.NewDecoder(r)
func (c *cache) Items() map[string]Item {
func (c *cache) ItemCount() int {
func (j *janitor) Run(c *cache) {
ticker := time.NewTicker(j.Interval)
for {
select {
case <-ticker.C:
c.DeleteExpired()
case <-j.stop:
ticker.Stop()
return
}
}
}
func stopJanitor(c *Cache) {
c.janitor.stop <- true
}
func runJanitor(c *cache, ci time.Duration) {
j := &janitor{
Interval: ci,
stop: make(chan bool),
}
c.janitor = j
go j.Run(c)
}
func newCache(de time.Duration, m map[string]Item) *cache {
func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache {
c := newCache(de, m)
C := &Cache{c}
if ci > 0 {
runJanitor(c, ci)
runtime.SetFinalizer(C, stopJanitor)
}
return C
初始化cache的时候就会启动清理协程,定期执行清理操作。
func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
items := make(map[string]Item)
return newCacheWithJanitor(defaultExpiration, cleanupInterval, items)
}
func NewFrom(defaultExpiration, cleanupInterval time.Duration, items map[string]Item) *Cache {
sharded.go里面提供了一种基于key 做hash实现分片缓存的方式,但是都不是导出的。
type unexportedShardedCache struct {
*shardedCache
}
type shardedCache struct {
seed uint32
m uint32
cs []*cache
janitor *shardedJanitor
}
func djb33(seed uint32, k string) uint32 {
func (sc *shardedCache) bucket(k string) *cache {
return sc.cs[djb33(sc.seed, k)%sc.m]
}
设置的时候在每一个分桶里设置缓存对象。
func (sc *shardedCache) Set(k string, x interface{}, d time.Duration) {
sc.bucket(k).Set(k, x, d)
}
type shardedJanitor struct {
Interval time.Duration
stop chan bool
}
func runShardedJanitor(sc *shardedCache, ci time.Duration) {
j := &shardedJanitor{
Interval: ci,
}
sc.janitor = j
go j.Run(sc)
}
func newShardedCache(n int, de time.Duration) *shardedCache {
func unexportedNewSharded(defaultExpiration, cleanupInterval time.Duration, shards int) *unexportedShardedCache {
本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!