• 使用golang实现一个LRU进程缓存模块


    文件

    需要用到:container/list 模块。

    // This files was copied/modified from https://github.com/hashicorp/golang-lru
    // which was (see below)
    
    // This package provides a simple LRU cache. It is based on the
    // LRU implementation in groupcache:
    // https://github.com/golang/groupcache/tree/master/lru
    
    package utils
    
    import (
        "container/list"
        "sync"
        "time"
    )
    
    // Cache is a thread-safe fixed size LRU cache.
    type Cache struct {
        size                   int
        evictList              *list.List
        items                  map[interface{}]*list.Element
        lock                   sync.RWMutex
        name                   string
        defaultExpiry          int64
        invalidateClusterEvent string
        currentGeneration      int64
        len                    int
    }
    
    // entry is used to hold a value in the evictList.
    type entry struct {
        key        interface{}
        value      interface{}
        expires    time.Time
        generation int64
    }
    
    // New creates an LRU of the given size.
    func NewLru(size int) *Cache {
        return &Cache{
            size:      size,
            evictList: list.New(),
            items:     make(map[interface{}]*list.Element, size),
        }
    }
    
    // New creates an LRU with the given parameters.
    func NewLruWithParams(size int, name string, defaultExpiry int64, invalidateClusterEvent string) *Cache {
        lru := NewLru(size)
        lru.name = name
        lru.defaultExpiry = defaultExpiry
        lru.invalidateClusterEvent = invalidateClusterEvent
        return lru
    }
    
    // Purge is used to completely clear the cache.
    func (c *Cache) Purge() {
        c.lock.Lock()
        defer c.lock.Unlock()
    
        c.len = 0
        c.currentGeneration++
    }
    
    // Add adds the given key and value to the store without an expiry.
    func (c *Cache) Add(key, value interface{}) {
        c.AddWithExpiresInSecs(key, value, 0)
    }
    
    // Add adds the given key and value to the store with the default expiry.
    func (c *Cache) AddWithDefaultExpires(key, value interface{}) {
        c.AddWithExpiresInSecs(key, value, c.defaultExpiry)
    }
    
    // AddWithExpiresInSecs adds the given key and value to the cache with the given expiry.
    func (c *Cache) AddWithExpiresInSecs(key, value interface{}, expireAtSecs int64) {
        c.lock.Lock()
        defer c.lock.Unlock()
    
        c.add(key, value, time.Duration(expireAtSecs)*time.Second)
    }
    
    func (c *Cache) add(key, value interface{}, ttl time.Duration) {
        var expires time.Time
        if ttl > 0 {
            expires = time.Now().Add(ttl)
        }
    
        // Check for existing item, ignoring expiry since we'd update anyway.
        if ent, ok := c.items[key]; ok {
            c.evictList.MoveToFront(ent)
            e := ent.Value.(*entry)
            e.value = value
            e.expires = expires
            if e.generation != c.currentGeneration {
                e.generation = c.currentGeneration
                c.len++
            }
            return
        }
    
        // Add new item
        ent := &entry{key, value, expires, c.currentGeneration}
        entry := c.evictList.PushFront(ent)
        c.items[key] = entry
        c.len++
    
        if c.evictList.Len() > c.size {
            c.removeElement(c.evictList.Back())
        }
    }
    
    // Get returns the value stored in the cache for a key, or nil if no value is present. The ok result indicates whether value was found in the cache.
    func (c *Cache) Get(key interface{}) (value interface{}, ok bool) {
        c.lock.Lock()
        defer c.lock.Unlock()
    
        return c.getValue(key)
    }
    
    func (c *Cache) getValue(key interface{}) (value interface{}, ok bool) {
        if ent, ok := c.items[key]; ok {
            e := ent.Value.(*entry)
    
            if e.generation != c.currentGeneration || (!e.expires.IsZero() && time.Now().After(e.expires)) {
                c.removeElement(ent)
                return nil, false
            }
    
            c.evictList.MoveToFront(ent)
            return ent.Value.(*entry).value, true
        }
    
        return nil, false
    }
    
    // GetOrAdd returns the existing value for the key if present. Otherwise, it stores and returns the given value. The loaded result is true if the value was loaded, false if stored.
    // This API intentionally deviates from the Add-only variants above for simplicity. We should simplify the entire API in the future.
    func (c *Cache) GetOrAdd(key, value interface{}, ttl time.Duration) (actual interface{}, loaded bool) {
        c.lock.Lock()
        defer c.lock.Unlock()
    
        // Check for existing item
        if actualValue, ok := c.getValue(key); ok {
            return actualValue, true
        }
    
        c.add(key, value, ttl)
    
        return value, false
    }
    
    // Remove deletes the value for a key.
    func (c *Cache) Remove(key interface{}) {
        c.lock.Lock()
        defer c.lock.Unlock()
    
        if ent, ok := c.items[key]; ok {
            c.removeElement(ent)
        }
    }
    
    // Keys returns a slice of the keys in the cache, from oldest to newest.
    func (c *Cache) Keys() []interface{} {
        c.lock.RLock()
        defer c.lock.RUnlock()
    
        keys := make([]interface{}, c.len)
        i := 0
        for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
            e := ent.Value.(*entry)
            if e.generation == c.currentGeneration {
                keys[i] = e.key
                i++
            }
        }
    
        return keys
    }
    
    // Len returns the number of items in the cache.
    func (c *Cache) Len() int {
        c.lock.RLock()
        defer c.lock.RUnlock()
        return c.len
    }
    
    // Name identifies this cache instance among others in the system.
    func (c *Cache) Name() string {
        return c.name
    }
    
    // GetInvalidateClusterEvent returns the cluster event configured when this cache was created.
    func (c *Cache) GetInvalidateClusterEvent() string {
        return c.invalidateClusterEvent
    }
    
    func (c *Cache) removeElement(e *list.Element) {
        c.evictList.Remove(e)
        kv := e.Value.(*entry)
        if kv.generation == c.currentGeneration {
            c.len--
        }
        delete(c.items, kv.key)
    }
    lru.go

    相关操作

    package srvstore
    
    import (
        "com/ajmall/ads/dataprocess/model"
        "com/ajmall/ads/dataprocess/store"
        "com/ajmall/ads/dataprocess/store/sqlstore"
        "com/ajmall/ads/dataprocess/utils" // 文件放在了这里
        "com/ajmall/ads/plugins/mlog"
        "fmt"
        "strings"
        "time"
    )
    
    type OwnerShipSqlStore struct {
        store.SqlStore
        owenerShipCache *utils.Cache
    }
    
    // 根据ownership信息获取token数据信息
    func (o OwnerShipSqlStore) GetTokenMsg(name string) (string, string, string, string, *model.AppError) {
        var appErr model.AppError
        var ownerObj model.OwnerShip
        // 1、如果是Facebook的 直接返回即可
        if strings.Contains(name,"facebook"){
            ownerRetFB := o.GetMaster().Table("ownership").Where("name=?", name).Find(&ownerObj)
            if ownerRetFB.Error != nil{
                appErr.Message = ownerRetFB.Error.Error()
                return "", "", "", "", &appErr
            }
            refreshToken := ownerObj.RefreshToken
            return refreshToken, "", "", "", nil
        }
        // 2 、snapchat需要做缓存处理
        // 有的话直接从从cache中读取数据
        if varObj, ok := o.owenerShipCache.Get(name); ok {
            if owner, ok := varObj.(*model.OwnerShip); ok {
                return owner.Token, owner.ClientId, ownerObj.ClientSecret, ownerObj.Id , nil
            }
        }
        // 没有的话从mysql中获取数据
        ownerRet := o.GetMaster().Table("ownership").Where("name=?", name).Find(&ownerObj)
        if ownerRet.Error != nil {
            appErr.Message = ownerRet.Error.Error()
            return "", "", "", "", &appErr
        }
        refreshToken := ownerObj.RefreshToken
        token := ownerObj.Token
        clientId := ownerObj.ClientId
        clientSecret := ownerObj.ClientSecret
        ownershipId := ownerObj.Id
        // 判断一下 token_expire的值与当前时间的差值
        secondToExpire := (ownerObj.TokenExpire - model.GetMillis()) / model.SecondNum
        fmt.Println("secondToExpire>>>>>> ",secondToExpire)
        if secondToExpire >= 50 {
    // 放入缓存的操作 o.owenerShipCache.AddWithExpiresInSecs(name,
    &ownerObj, secondToExpire) // 超时时间小于50的话这个token就不安全了 }else{ // TODO : 重新请求接口获取token的值并写入到数据库中 //token, _ := utils.GetSnapchatToken(refreshToken,clientId,clientSecret) // TODO 这里直接返回能用的token~~~~注意后面的逻辑都给改一下啊 不用再调用refreshtoken函数了!!!! token, _ := utils.SnapchatRefreshToken(refreshToken,clientId,clientSecret) // 写入数据库中 o.WriteTokenToMysql(token) //fmt.Println("token666>>> ",token) return token, clientId, clientSecret, ownershipId, nil } return token, clientId, clientSecret, ownershipId, nil } // snapchat渠道 将生成的token数据写入到数据库中 TODO 只用于 snapchat!!! func (o OwnerShipSqlStore) WriteTokenToMysql(token string) *model.AppError { //var ownerObj model.OwnerShip // 获取当前时间的时间之后的1800秒的时间 // token_expire 等于当前时间戳的值往后推移1800秒 st,_ := time.ParseDuration("1800s") timeExpire := time.Now().Add(st) timeExpireStamp := timeExpire.UnixNano() / 1e6 o.GetMaster().Table("ownership").Model(&model.OwnerShip{}).Where("name=?", "snap_chat_pro").Updates(map[string]interface { }{"token_expire": timeExpireStamp, "token": token}) return nil } func NewOwnerShipStore(ss *sqlstore.SqlSupplier) store.OwnerShipStore { s := &OwnerShipSqlStore{ SqlStore: ss, } s.owenerShipCache = utils.NewLru(1000) if err := s.GetMaster().AutoMigrate(&model.OwnerShip{}); err != nil { mlog.Error("NewOwnerShipStore. Error " + err.Error()) } return s

    关于缓存淘汰算法LRU与LFU的文章

    https://www.jianshu.com/p/1f8e36285539

    其他参考的资料

    https://github.com/golang/groupcache/tree/master/lru

    https://github.com/hashicorp/golang-lru

  • 相关阅读:
    《超级迷宫》需求规格说明
    超级迷宫冲刺个人计划安排
    审评(HelloWorld团队)
    C语言中的++与*
    a、b交换
    微服务架构浅析及实践心得
    Servlet版本冲突引起的Error
    并发编程:一个100%会发生死锁的程序
    单元测试与Mockito
    Java基础:HashMap假死锁问题的测试、分析和总结
  • 原文地址:https://www.cnblogs.com/paulwhw/p/14251438.html
Copyright © 2020-2023  润新知