面试题答案
一键面试接口定义
首先定义缓存接口,该接口通常需要包含基本的操作方法,比如获取缓存值、设置缓存值以及删除缓存值。
type Cache interface {
Get(key string) (interface{}, bool)
Set(key string, value interface{})
Delete(key string)
}
方法设计
- Get方法:根据传入的键获取对应的值,如果存在则返回值和
true
,不存在则返回nil
和false
。 - Set方法:设置键值对到缓存中。
- Delete方法:根据键删除缓存中的对应项。
利用同步原语确保并发安全性
使用sync.Mutex
来保护缓存数据结构。
type SafeCache struct {
data map[string]interface{}
mu sync.Mutex
}
func NewSafeCache() *SafeCache {
return &SafeCache{
data: make(map[string]interface{}),
}
}
func (c *SafeCache) Get(key string) (interface{}, bool) {
c.mu.Lock()
defer c.mu.Unlock()
value, exists := c.data[key]
return value, exists
}
func (c *SafeCache) Set(key string, value interface{}) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[key] = value
}
func (c *SafeCache) Delete(key string) {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.data, key)
}
性能瓶颈分析
- 锁竞争:由于使用
sync.Mutex
对整个缓存进行加锁,在高并发场景下,多个goroutine同时访问缓存时会频繁竞争锁,导致性能下降。 - 粒度问题:锁的粒度太粗,即使是不同的操作(如
Get
和Set
)在不同的键上执行,也会因为锁的存在而相互等待。
优化方向
- 细粒度锁:可以使用多个
sync.Mutex
,例如按照一定规则(如哈希值)将缓存数据划分到不同的子区域,每个子区域使用单独的锁,从而减少锁竞争。 - 读写锁:如果读操作远远多于写操作,可以使用
sync.RWMutex
,读操作时允许多个goroutine同时进行,写操作时才独占锁,提高并发性能。 - 无锁数据结构:考虑使用无锁数据结构(如
sync.Map
,Go 1.9引入),它在高并发场景下性能表现更好,并且不需要手动管理锁。
type SafeCache2 struct {
data sync.Map
}
func NewSafeCache2() *SafeCache2 {
return &SafeCache2{}
}
func (c *SafeCache2) Get(key string) (interface{}, bool) {
value, exists := c.data.Load(key)
return value, exists
}
func (c *SafeCache2) Set(key string, value interface{}) {
c.data.Store(key, value)
}
func (c *SafeCache2) Delete(key string) {
c.data.Delete(key)
}