前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang源码分析:go-cache

golang源码分析:go-cache

作者头像
golangLeetcode
发布2023-09-06 19:27:03
4620
发布2023-09-06 19:27:03
举报
文章被收录于专栏:golang算法架构leetcode技术php

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 {
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-05-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 golang算法架构leetcode技术php 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档