Derek解读-Bytom源码分析-持久化存储LevelDB(二)-cache缓存
简介
https://github.com/Bytom/bytom
本章介绍Derek解读-Bytom源码分析-持久化存储LevelDB(二)-cache缓存
作者使用MacOS操作系统,其他平台也大同小异
Golang Version: 1.8
block cache缓存介绍
上一篇《Derek解读-Bytom源码分析-持久化存储LevelDB》文章介绍了比原链数据持久化到磁盘。当执行读取数据时从cache中得到,实现快速访问。
比原链的block cache主要实现有两个部分:
- lru: 缓存淘汰算法
- fillFn: 回调函数
比原链的block cache实现过程如下:
- 执行GetBlock函数
- 从block cache中查询,如果命中缓存则返回
- block cache中未命中则执行fillFn回调函数从磁盘中获取并缓存block cache中,然后返回
blockCache分析
blockCache结构
database/leveldb/cache.go
| 1 | type blockCache struct { | 
- mu: 互斥锁,保证共享数据操作的一致性
- lru: 缓存淘汰算法
- fillFn: 回调函数
- single: 回调函数的调用机制,同一时间保证只有一个回调函数在执行。
缓存命中和不命中的操作
 database/leveldb/cache.go 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29func (c *blockCache) lookup(hash *bc.Hash) (*types.Block, error) {
	if b, ok := c.get(hash); ok {
		return b, nil
	}
	block, err := c.single.Do(hash.String(), func() (interface{}, error) {
		b := c.fillFn(hash)
		if b == nil {
			return nil, fmt.Errorf("There are no block with given hash %s", hash.String())
		}
		c.add(b)
		return b, nil
	})
	if err != nil {
		return nil, err
	}
	return block.(*types.Block), nil
}
func (c *blockCache) get(hash *bc.Hash) (*types.Block, bool) {
	c.mu.Lock()
	block, ok := c.lru.Get(*hash)
	c.mu.Unlock()
	if block == nil {
		return nil, ok
	}
	return block.(*types.Block), ok
}
lookup函数从block cache中查询,如果命中缓存则返回。如果block cache未命中则single.Do执行fillFn回调函数从磁盘中获取并缓存block cache中,然后返回
LRU缓存淘汰算法
LRU(Least recently used)算法根据数据的历史访问记录来进行淘汰数据,如果数据最近被访问过,那么缓存被命中的几率也更高。

- 新的数据插入到链表头部
- 每当缓存命中,则将数据移到链表头部
- 当链表满的时候,将链表尾部的数据丢弃
LRU算法实现
LRU一般使用hash map和doubly linked list双向链表实现
实例化LRU CACHE
 vendor/github.com/golang/groupcache/lru/lru.go 1
2
3
4
5
6
7func New(maxEntries int) *Cache {
	return &Cache{
		MaxEntries: maxEntries,
		ll:         list.New(),
		cache:      make(map[interface{}]*list.Element),
	}
}
- MaxEntries:最大缓存条目数,如果为0则表示无限制
- ll: 双向链表数据结构
LRU查询元素
| 1 | func (c *Cache) Get(key Key) (value interface{}, ok bool) { | 
Get获取元素,如果元素命中则将元素移至链表头部,保持最新的访问
LRU添加元素
| 1 | func (c *Cache) Add(key Key, value interface{}) { | 
Add添加元素有三步操作:
- 如果当前缓存中存在该元素则将元素移至链表头并返回
- 如果缓存中不存在该元素则将元素插入链表头部
- 如果缓存的元素超过MaxEntries限制则移除链表最末尾的元素