golang学习计划一 利用golang标准库创建cache

最近面试遇到利用golang标准库创建cache的问题,之前一直用的第三方的库,例如由PatrickMN提供的开源库go-cache(github.com/patrickmn/go-cache),但是面试问到了还是得研究下的。

问题思路

cache无非就几样关键的:

  1. 数据标识(key)
  2. 数据(value)
  3. 过期时间(expire_time)

首先,定义我们的缓存数据,其内包含数据以及数据过期时间

type cacheItem struct {
	value     interface{}
	expiration time.Time
}

虽然我们有了这些数据,但是还没有相关的标识去区分他们:

type SimpleCache struct {
	mu     sync.Mutex  //互斥锁,保障数据安全性
	values   map[string]*cacheItem
}

然后再创建一个构建器:

func NewSimpleCache() *SimpleCache {
	return &SimpleCache{
		values:   make(map[string]*cacheItem),
	}
}

还有相关的读写方法:

//写方法,将数据存入缓存并设置过期时间
func (c *SimpleCache) Set(key string, value interface{}, ttl time.Duration) {
	c.mu.Lock()
	defer c.mu.Unlock()

	c.values[key] = &cacheItem{
		value:     value,
		expiration: time.Now().Add(ttl),
	}
}
//度方法
func (c *SimpleCache) Get(key string) (interface{}, bool) {
	c.mu.Lock()
	defer c.mu.Unlock()

	item, exists := c.values[key]
	if !exists || item.expiration.Before(time.Now()) {
		return nil, false
	}
	return item.value, true
}

代码到这里其实一个简单的cache就已经完成了,虽然完成了,但是还是有很大的弊端,比如说:
1.上述的互斥锁会阻塞所有试图访问缓存的goroutine,对于本身为了效率而生的cache来说是很不合格的,所以这里选择使用读写锁分离读写操作会更好些
2.添加缓存容量限制,并使用LRU(Least Recently Used)策略实现可以自动移除最长时间未被访问的缓存项,这对于容量有限的缓存尤其重要。
3.增加缓存命中率、未命中率、eviction次数等统计信息,便于监控和调优
相关的优化如下:

*新增缓存命中率、未命中率、eviction次数

type CacheStats struct {
	Hits   uint64
	Misses uint64
	Evicts uint64
}

*使用读写锁
*新增cache容量
*lruOrder切片,用来维护缓存项的LRU顺序,最近使用的项放在末尾

type SimpleCache struct {
	mu       sync.RWMutex
	values   map[string]*cacheItem
	lruOrder []string
	maxItems int
	stats    CacheStats
}

*更新后的构造器

func NewSimpleCache(maxItems int) *SimpleCache {
	return &SimpleCache{
		values:   make(map[string]*cacheItem),
		lruOrder: make([]string, 0, maxItems),
		maxItems: maxItems,
	}
}

*更新后的读取和写入

//加写锁保证线程安全。
//若键已存在,则先从LRU列表中移除。
//更新或插入新的cacheItem到values中,并将其添加到LRU列表的末尾。
//如果超出最大项数,触发evictLRU移除最老的缓存项。
func (c *SimpleCache) Set(key string, value interface{}, ttl time.Duration) {
	c.mu.Lock()
	defer c.mu.Unlock()

	_, exists := c.values[key]
	if exists {
		c.removeLRUItem(key)
	}

	c.values[key] = &cacheItem{
		value:     value,
		expiration: time.Now().Add(ttl),
	}
	c.addLRUItem(key)

	if len(c.lruOrder) > c.maxItems {
		c.evictLRU()
	}
}
//加读锁保证并发安全。
//检查键是否存在且未过期,更新统计信息,并返回值。
//优化了读取逻辑,减少不必要的过期检查。
func (c *SimpleCache) Get(key string) (interface{}, bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	item, exists := c.values[key]
	if !exists || item.expiration.Before(time.Now()) {
		c.stats.Misses++
		return nil, false
	}
	c.stats.Hits++
	return item.value, true
}

func (c *SimpleCache) removeLRUItem(key string) {
	for i, k := range c.lruOrder {
		if k == key {
			c.lruOrder = append(c.lruOrder[:i], c.lruOrder[i+1:]...)
			break
		}
	}
}

*以下是LUR维护及满容量数据清理操作:

//removeLRUItem 和 addLRUItem: 分别用于从LRU列表中移除和添加缓存项的键。这两个辅助函数帮助维护缓存项的访问顺序
func (c *SimpleCache) removeLRUItem(key string) {
	for i, k := range c.lruOrder {
		if k == key {
			c.lruOrder = append(c.lruOrder[:i], c.lruOrder[i+1:]...)
			break
		}
	}
}

func (c *SimpleCache) addLRUItem(key string) {
	c.lruOrder = append(c.lruOrder, key)
}
//evictLRU: 当缓存项数量超过限制时,移除最老的缓存项。这个方法会从values和lruOrder中移除最老的键,并更新统计信息
func (c *SimpleCache) evictLRU() {
	keyToRemove := c.lruOrder[0]
	delete(c.values, keyToRemove)
	c.removeLRUItem(keyToRemove)
	c.stats.Evicts++
}

*最后是状态统计

func (c *SimpleCache) Stats() CacheStats {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.stats
}

写了一个简单的测试用例:

func main() {
	cache := NewSimpleCache(2)
	cache.Set("key1", "Hello, World!", 2*time.Second)
	cache.Set("key2", "Hello, Golang!", 2*time.Second)

	fmt.Println(cache.Get("key1"))
	fmt.Println(cache.Get("key2"))

	time.Sleep(5 * time.Second)

	fmt.Println(cache.Get("key1")) // 应该已被移除
	fmt.Println(cache.Get("key2"))

	fmt.Println("Cache Stats:", cache.Stats())
}

运行结果如下:
在这里插入图片描述

私聊获取相关代码

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值