基于golang实现仿redis内存缓存功能
首先,介绍一下该项目的需求:
1.支持设定过期时间,精确到秒(通过参数来传递)
2.支持设定最大内存,当内存超出时,做出合适的处理
3.支持并发安全(由于golang的map不支持并发安全,但这里也可考虑使用sync.map)【这里的实现方法为加锁】
对于redis中,常用的模块,大概表述为设定key,value,删除key,value,根据指定key获取value,清空所有键值对数据,获取所有的key,判断key是否存在
根据上述需求,我们来定义一个接口,分别实现上述方法:
import "time" type Cache interface { //size:1kb 100KB 1MB 2MB 1GB SetMaxMemory(size string) bool //将value写入缓存 Set(key string, val interface{}, expire time.Duration) bool //根绝key值获取value Get(key string) (interface{}, bool) //删除key值 Del(key string) bool //判断key是否存在 Exists(key string) bool //晴空所有key Flush() bool //获取缓存中所有key的数量 Keys() int64 }
在这之后,我们需要来分别实现这些方法,考虑到redis中的缓存的高效性,这里我们也创建一个结构体,通过绑定该结构体的接口实现方法,来模拟redis中的相应功能;下面的两个结构体分别表示:
我们要设定最大内存,那么也需要考虑到所设定内存的大小及其转换情况,这些限制也要考虑到,同样,对于健值对数据,我们需要考虑到并发安全问题,所以该结构体中的locker字段表示在接下来的接口实现中,对资源的写操作,我们需要将其进行加锁,来放置其他的goroutine去竞争,导致最终的结果不是我们想要的。
type memoryCache struct { //最大内存表示字段 maxMemorySize int64 //最大内存字符串表示 maxMemorySizeStr string //当前内存大小 currentMemorySize int64 //缓存键值对 values map[string]*memoryCacheValue //锁 locker sync.RWMutex //清除过期缓存 clearExpiredItemTimeInterval time.Duration } type memoryCacheValue struct { //value值 val interface{} //过期时间 expireTime time.Time //有效时长 expire time.Duration //value大小 size int64 }
下面的实现接口,实现了redis中的常见功能:set,get,delete,keys,flush,以及exists
// 初始化操作 func NewMemoryCache() Cache { m := &memoryCache{ values: make(map[string]*memoryCacheValue), clearExpiredItemTimeInterval: time.Second * 10, } go m.clearExpiredItem() return m } //size:1kb 100KB 1MB 2MB 1GB func (m *memoryCache) SetMaxMemory(size string) bool { // m.maxMemorySize = 0 // m.maxMemorySizeStr = size m.maxMemorySize, m.maxMemorySizeStr = ParseSize(size) fmt.Println(m.maxMemorySize, m.maxMemorySizeStr) return false } // 将value写入缓存 func (m *memoryCache) Set(key string, val interface{}, expire time.Duration) bool { m.locker.Lock() defer m.locker.Unlock() fmt.Println("called set") v := &memoryCacheValue{ val: val, expireTime: time.Now().Add(expire), expire: expire, size: GetValSize(val), } m.del(key) m.add(key, v) if m.currentMemorySize > m.maxMemorySize { m.del(key) log.Println(fmt.Sprintf("max memory size :%d", m.maxMemorySize)) //panic(fmt.Sprintf("max memory size :%s", m.maxMemorySize)) } return true } // 底层方法,获取获取键值对的value func (m *memoryCache) get(key string) (*memoryCacheValue, bool) { val, ok := m.values[key] return val, ok } //根据指定键删除指定的键值对 func (m *memoryCache) del(key string) { tmp, ok := m.get(key) if ok && tmp != nil { m.currentMemorySize -= tmp.size delete(m.values, key) } } //添加键值对 func (m *memoryCache) add(key string, val *memoryCacheValue) { m.values[key] = val m.currentMemorySize += val.size } // 根绝key值获取value func (m *memoryCache) Get(key string) (interface{}, bool) { m.locker.Lock() defer m.locker.Unlock() mcv, ok := m.get(key) if ok { //判断缓存是否过期 if mcv.expire != 0 && mcv.expireTime.Before(time.Now()) { m.del(key) //return nil, false } return mcv.val, ok } return nil, false } // 删除key值 func (m *memoryCache) Del(key string) bool { m.locker.Lock() defer m.locker.Unlock() m.del(key) return false } // 判断key是否存在 func (m *memoryCache) Exists(key string) bool { m.locker.Lock() defer m.locker.Unlock() _, ok := m.values[key] return ok } // 清空所有key func (m *memoryCache) Flush() bool { m.locker.Lock() defer m.locker.Unlock() m.values = make(map[string]*memoryCacheValue, 0) m.currentMemorySize = 0 return false } // 获取缓存中所有key的数量 func (m *memoryCache) Keys() int64 { m.locker.Lock() defer m.locker.Unlock() return int64(len(m.values)) } func (m *memoryCache) clearExpiredItem() { //声明一个定时触发器 timeTricker := time.NewTicker(m.clearExpiredItemTimeInterval) defer timeTricker.Stop() for { select { case <-timeTricker.C: for key, item := range m.values { if item.expire != 0 && time.Now().After(item.expireTime) { m.locker.Lock() m.del(key) m.locker.Unlock() } } } } }
对于内存数据格式,我们也需要考虑到不同单位之间转换的问题,设置过大,会导致资源浪费等问题;设置过小,会导致我们接下来的一些操作可能会出现问题,所以这里简单的实现了该方法
func CheckError(err error) { if err != nil { fmt.Println("该处有错误,请回归检查") } } const ( B = 1 << (iota * 10) KB MB GB TB PB ) func ParseSize(size string) (int64, string) { //默认大小为100MB re, _ := regexp.Compile("[0-9]+") unit := string(re.ReplaceAll([]byte(size), []byte(""))) //num := strconv.ParseInt(strings.Replace(size,unit,"",1),10,64) num, err := strconv.ParseInt(strings.Replace(size, unit, "", 1), 10, 64) CheckError(err) unit = strings.ToUpper(unit) var byteNum int64 = 0 switch unit { case "B": byteNum = num case "KB": byteNum = num * KB case "MB": byteNum = num * MB case "GB": byteNum = num * GB case "TB": byteNum = num * TB case "PB": byteNum = num * PB default: num = 0 byteNum = 0 } if num == 0 { log.Println("ParseSize 仅支持 B MB GB TB PB") num = 100 byteNum = num * MB unit = "MB" } sizeStr := strconv.FormatInt(num, 10) + unit return byteNum, sizeStr }
这些接口实现之后,我们要去做一个代理层,假设我们刚才所实现的方法是底层的接口,对外不能暴露,所以通过代理层去掉这些接口,解决了某些问题,代码如下:
type CacheServer struct { memCache cache.Cache } func NewMemoryCache() *CacheServer { return &CacheServer{ memCache: cache.NewMemoryCache(), } } func (m *CacheServer) SetMaxMemory(size string) bool { return m.memCache.SetMaxMemory(size) } func (m *CacheServer) Set(key string, val interface{}, expire ...time.Duration) bool { expires := time.Second * 0 if len(expire) > 0 { expires = expire[0] } return m.memCache.Set(key, val, expires) } func (m *CacheServer) Get(key string) (interface{}, bool) { return m.memCache.Get(key) } func (m *CacheServer) Del(key string) bool { return m.memCache.Del(key) } func (m *CacheServer) Exists(key string) bool { return m.memCache.Exists(key) } func (m *CacheServer) Flush() bool { return m.memCache.Flush() } func (m *CacheServer) Keys() int64 { return m.memCache.Keys() }
然后,我们通过服务端去调用这些接口,来验证我们的功能需求:
func main() { cache := cacheserver.NewMemoryCache() cache.SetMaxMemory("100MB") cache.Set("key1", "kol") cache.Set("key2", "Curry") cache.Set("key3", "Json") fmt.Print("Keys()方法: ") fmt.Println(cache.Keys()) }
输出结果:
called set
called set
called set
Keys()方法: 3
func main() { // cache := cache.NewMemoryCache() // cache.SetMaxMemory("300GB") // cache.Set("key1", 2, time.Second*10) // cache.Get("key1") cache := cacheserver.NewMemoryCache() cache.SetMaxMemory("100MB") cache.Set("key1", "kol") cache.Set("key2", "Curry") cache.Set("key3", "Json") fmt.Print("Get方法:") fmt.Println(cache.Get("key1")) fmt.Print("Get方法:") fmt.Println(cache.Get("key2")) fmt.Print("Get方法:") fmt.Println(cache.Get("key3")) fmt.Print("Keys()方法: ") fmt.Println(cache.Keys()) }
输出结果:
104857600 100MB
called set
called set
called set
Get方法:kol true
Get方法:Curry true
Get方法:Json true
Keys()方法: 3
其他接口,请大家自行测试,以上代码均在本地调试通过!!!