Samael

cache2go源码阅读

最近做的一个tinyurl里用到了一个cache2go的库, 代码不多, 功能代码不到1000行, 但是对理解Go并发编程很有用, 这里来阅读一下.

代码结构

代码结构很简单, 实现功能的文件其实只有三个

├── LICENSE.txt
├── README.md
├── benchmark_test.go
├── cache.go // 功能文件
├── cache_test.go 
├── cacheitem.go // 功能文件
├── cachetable.go // 功能文件
├── errors.go
└── examples
    ├── callbacks
    │   └── callbacks.go
    ├── dataloader
    │   └── dataloader.go
    └── mycachedapp
        └── mycachedapp.go

数据结构

cacheitem

除了实现对data访问的功能, 还实现了读锁, 统计访问次数, 生命周期等功能

// cacheitem
type CacheItem struct {
	sync.RWMutex

	// The item's key.
	key interface{}
	// The item's data.
	data interface{}
	// How long will the item live in the cache when not being accessed/kept alive.
	lifeSpan time.Duration

	// Creation timestamp.
	createdOn time.Time
	// Last access timestamp.
	accessedOn time.Time
	// How often the item was accessed.
	accessCount int64

	// Callback method triggered right before removing the item from the cache
	aboutToExpire func(key interface{})
}

cachetable

cacheitem的容器, 除了实现对cacheitem的操作, 还实现了定时检查过期, 触发器, 迭代器, 和简单的统计功能

// cachetabel
// CacheTable is a table within the cache
type CacheTable struct {
	sync.RWMutex

	// The table's name.
	name string
	// All cached items.
	items map[interface{}]*CacheItem

	// Timer responsible for triggering cleanup.
	cleanupTimer *time.Timer
	// Current timer duration.
	cleanupInterval time.Duration

	// The logger used for this table.
	logger *log.Logger

	// Callback method triggered when trying to load a non-existing key.
	loadData func(key interface{}, args ...interface{}) *CacheItem
	// Callback method triggered when adding a new item to the cache.
	addedItem func(item *CacheItem)
	// Callback method triggered before deleting an item from the cache.
	aboutToDeleteItem func(item *CacheItem)
}

笔记

以下就是随手记的, 并没有什么条理

过期检查

思想就是在item找到生命周期最短的那个, 设置一个定时器到时进行过期检查, 这样可以防止, 单独开一个Goroutine空转影响性能 在每次Add时要调用一下. 因为增加之后需要对找新的生命周期最短的

func (table *CacheTable) expirationCheck() {
	table.Lock()
	if table.cleanupTimer != nil {
		table.cleanupTimer.Stop()
	}
	if table.cleanupInterval > 0 {
		table.log("Expiration check triggered after", table.cleanupInterval, "for table", table.name)
	} else {
		table.log("Expiration check installed for table", table.name)
	}

	// To be more accurate with timers, we would need to update 'now' on every
	// loop iteration. Not sure it's really efficient though.
	now := time.Now()
	smallestDuration := 0 * time.Second
	for key, item := range table.items {
		// Cache values so we don't keep blocking the mutex.
		item.RLock()
		lifeSpan := item.lifeSpan
		accessedOn := item.accessedOn
		item.RUnlock()

		if lifeSpan == 0 {
			continue
		}
		if now.Sub(accessedOn) >= lifeSpan {
			// Item has excessed its lifespan.
			table.deleteInternal(key)
		} else {
			// Find the item chronologically closest to its end-of-lifespan.
			if smallestDuration == 0 || lifeSpan-now.Sub(accessedOn) < smallestDuration {
				smallestDuration = lifeSpan - now.Sub(accessedOn)
			}
		}
	}

	// Setup the interval for the next cleanup run.
	table.cleanupInterval = smallestDuration
	if smallestDuration > 0 {
		table.cleanupTimer = time.AfterFunc(smallestDuration, func() {
			go table.expirationCheck()
		})
	}
	table.Unlock()
}

Share this: