golang源码分析:go-cache

2023-09-06 19:27:03 浏览数 (2)

github.com/patrickmn/go-cache是知名golang local cache实现里面最简单的一种,可以理解为就是简单的map加锁实现的,它通过定时器来进行过期key的淘汰,并且利用runtime.SetFinalizer(C, stopJanitor)来停止我们的过期key协程,会在倒数第二个gc周期的时候,调用该方法,关闭我们的定时清理协程。首先看下如何使用它

代码语言:javascript复制
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() 会创建一个缓存对象,它需要两个参数:

代码语言:javascript复制
c := cache.New(5*time.Minute, 10*time.Minute)

第一个参数:表示缓存项的过期时间,如上示例表示 5 分钟后过期。

第二个参数:表示缓存项的清除时间,如上示例表示 10 分钟后自动清除。

2,c.Set() 需要传递三个参数:

代码语言:javascript复制
c.Set("key", "value", cache.DefaultExpiration)

第一个参数:指定缓存项的 key。

第二个参数:指定缓存项的 value。

第三个参数:指定该项目的过期时间。注意:一,此处指定的时间优先级大于 New 时的时间,如果这里指定为 2 分钟,则该缓存项将在 2 分钟后过期;二,如果配置为 cache.DefaultExpiration 则表示缓存项永不过期。

3,Get

使用 Get 方法从缓存中获取一个键值对:

代码语言:javascript复制
value, found := c.Get("key")
if found {
fmt.Println(value)
}

介绍完使用规则后我们开始分析源码,它只有两个文件cache.go和sharded.go,首先看下第一个文件。它定义存储对象类型,包含两个参数,Object和对应的过期时间。

代码语言:javascript复制
type Item struct {
Object     interface{}
Expiration int64
}
代码语言:javascript复制
NoExpiration time.Duration = -1
DefaultExpiration time.Duration = 0

分别表示永不过期和使用创建cache对象的时候使用的默认过期时间。

代码语言:javascript复制
type Cache struct {
*cache
// If this is confusing, see the comment at the bottom of New()
}
代码语言:javascript复制
type cache struct {
defaultExpiration time.Duration
items             map[string]Item
mu                sync.RWMutex
onEvicted         func(string, interface{})
janitor           *janitor
}

cache对象就是传说中的map 锁 过期清理函数。其中janitor,定义了清理函数定期执行的时间和接受停止信号的chan

代码语言:javascript复制
type janitor struct {
Interval time.Duration
stop     chan bool
}

下面看下设置存储对象接口,它的第三个参数过期时间0,使用默认超时,-1不超时

代码语言:javascript复制
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()
代码语言:javascript复制
func (c *cache) set(k string, x interface{}, d time.Duration) {
代码语言:javascript复制
func (c *cache) SetDefault(k string, x interface{}) {
c.Set(k, x, DefaultExpiration)
}
代码语言:javascript复制
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
}
代码语言:javascript复制
func (c *cache) Replace(k string, x interface{}, d time.Duration) error {

get函数就是从map里面取对象。

代码语言:javascript复制
func (c *cache) Get(k string) (interface{}, bool) {
c.mu.RLock()
// "Inlining" of get and Expired
item, found := c.items[k]
代码语言:javascript复制
func (c *cache) GetWithExpiration(k string) (interface{}, time.Time, bool) {
代码语言:javascript复制
func (c *cache) Increment(k string, n int64) error {
代码语言:javascript复制
func (c *cache) IncrementFloat(k string, n float64) error {
代码语言:javascript复制
func (c *cache) IncrementInt64(k string, n int64) (int64, error) {
代码语言:javascript复制
func (c *cache) Decrement(k string, n int64) error {

自增和递减函数对每个基础类型都定义了一版,可以考虑结合范型优化一把代码了。

删除代码的时候要判断是否设置了资源清理函数,如果设置了就会执行资源清理函数。

代码语言:javascript复制
func (c *cache) Delete(k string) {
c.mu.Lock()
v, evicted := c.delete(k)
c.mu.Unlock()
if evicted {
c.onEvicted(k, v)
}
}
代码语言:javascript复制
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
}
代码语言:javascript复制
type keyAndValue struct {
key   string
value interface{}
}
代码语言:javascript复制
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)
}
代码语言:javascript复制
func (c *cache) OnEvicted(f func(string, interface{})) {
c.mu.Lock()
c.onEvicted = f
c.mu.Unlock()
}

可以支持序列化到gob格式,或者序列化到文件中。

代码语言:javascript复制
func (c *cache) Save(w io.Writer) (err error) {
enc := gob.NewEncoder(w)
代码语言:javascript复制
func (c *cache) SaveFile(fname string) error {
fp, err := os.Create(fname)
if err != nil {
return err
}
err = c.Save(fp)
代码语言:javascript复制
func (c *cache) Load(r io.Reader) error {
dec := gob.NewDecoder(r)
代码语言:javascript复制
func (c *cache) Items() map[string]Item {
代码语言:javascript复制
func (c *cache) ItemCount() int {
代码语言:javascript复制
func (j *janitor) Run(c *cache) {
ticker := time.NewTicker(j.Interval)
for {
select {
case <-ticker.C:
c.DeleteExpired()
case <-j.stop:
ticker.Stop()
return
}
}
}
代码语言:javascript复制
func stopJanitor(c *Cache) {
c.janitor.stop <- true
}
代码语言:javascript复制
func runJanitor(c *cache, ci time.Duration) {
j := &janitor{
Interval: ci,
stop:     make(chan bool),
}
c.janitor = j
go j.Run(c)
}
代码语言:javascript复制
func newCache(de time.Duration, m map[string]Item) *cache {
代码语言:javascript复制
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的时候就会启动清理协程,定期执行清理操作。

代码语言:javascript复制
func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
items := make(map[string]Item)
return newCacheWithJanitor(defaultExpiration, cleanupInterval, items)
}
代码语言:javascript复制
func NewFrom(defaultExpiration, cleanupInterval time.Duration, items map[string]Item) *Cache {

sharded.go里面提供了一种基于key 做hash实现分片缓存的方式,但是都不是导出的。

代码语言:javascript复制
type unexportedShardedCache struct {
*shardedCache
}
代码语言:javascript复制
type shardedCache struct {
seed    uint32
m       uint32
cs      []*cache
janitor *shardedJanitor
}
代码语言:javascript复制
func djb33(seed uint32, k string) uint32 {
func (sc *shardedCache) bucket(k string) *cache {
return sc.cs[djb33(sc.seed, k)%sc.m]
}

设置的时候在每一个分桶里设置缓存对象。

代码语言:javascript复制
func (sc *shardedCache) Set(k string, x interface{}, d time.Duration) {
sc.bucket(k).Set(k, x, d)
}
代码语言:javascript复制
type shardedJanitor struct {
Interval time.Duration
stop     chan bool
}
代码语言:javascript复制
func runShardedJanitor(sc *shardedCache, ci time.Duration) {
j := &shardedJanitor{
Interval: ci,
}
sc.janitor = j
go j.Run(sc)
}
代码语言:javascript复制
func newShardedCache(n int, de time.Duration) *shardedCache {
代码语言:javascript复制
func unexportedNewSharded(defaultExpiration, cleanupInterval time.Duration, shards int) *unexportedShardedCache {

0 人点赞