基于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

其他接口,请大家自行测试,以上代码均在本地调试通过!!!

posted @ 2023-06-14 15:45  99号的格调  阅读(81)  评论(0编辑  收藏  举报