github.com/vicanso/lru-ttl@v1.5.1/lru_ttl.go (about)

     1  // Copyright 2020 tree xie
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package lruttl
    16  
    17  import (
    18  	"errors"
    19  	"time"
    20  
    21  	lru "github.com/hashicorp/golang-lru"
    22  )
    23  
    24  type Key interface{}
    25  
    26  type Cache struct {
    27  	ttl       time.Duration
    28  	lru       *lru.Cache
    29  	onEvicted func(key Key, value interface{})
    30  }
    31  
    32  // CacheOption cache option
    33  type CacheOption func(c *Cache)
    34  
    35  type cacheItem struct {
    36  	expiredAt int64
    37  	value     interface{}
    38  }
    39  
    40  func (item *cacheItem) isExpired() bool {
    41  	return item.expiredAt < time.Now().UnixNano()
    42  }
    43  
    44  // New returns a new lru cache with ttl
    45  func New(maxEntries int, defaultTTL time.Duration, opts ...CacheOption) *Cache {
    46  	if maxEntries <= 0 || defaultTTL <= 0 {
    47  		panic(errors.New("maxEntries and default ttl must be gt 0"))
    48  	}
    49  	c := &Cache{
    50  		ttl: defaultTTL,
    51  	}
    52  	for _, opt := range opts {
    53  		opt(c)
    54  	}
    55  	var fn func(key, value interface{})
    56  	// 如果有设置on evicted
    57  	if c.onEvicted != nil {
    58  		fn = func(key, value interface{}) {
    59  			c.onEvicted(key, value)
    60  		}
    61  	}
    62  
    63  	l, err := lru.NewWithEvict(maxEntries, fn)
    64  	// lru 缓存全局初始化,因此直接panic
    65  	// 除了长度少于0,其它情况不会出错
    66  	if err != nil {
    67  		panic(err)
    68  	}
    69  	c.lru = l
    70  
    71  	return c
    72  
    73  }
    74  
    75  // CacheEvictedOption sets evicted function to cache
    76  func CacheEvictedOption(fn func(key Key, value interface{})) CacheOption {
    77  	return func(c *Cache) {
    78  		c.onEvicted = fn
    79  	}
    80  }
    81  
    82  // Add adds a value to the cache, it will use default ttl if the ttl is nil.
    83  func (c *Cache) Add(key Key, value interface{}, ttl ...time.Duration) {
    84  	expiredAt := time.Now().UnixNano()
    85  	if len(ttl) != 0 {
    86  		expiredAt += ttl[0].Nanoseconds()
    87  	} else {
    88  		expiredAt += c.ttl.Nanoseconds()
    89  	}
    90  	c.lru.Add(key, &cacheItem{
    91  		expiredAt: expiredAt,
    92  		value:     value,
    93  	})
    94  }
    95  
    96  // Get returns value and exists from the cache by key, if value is expired then remove it.
    97  // If the value is expired, value is not nil but exists is false.
    98  func (c *Cache) Get(key Key) (interface{}, bool) {
    99  	data, ok := c.lru.Get(key)
   100  	if !ok {
   101  		return nil, false
   102  	}
   103  	item, ok := data.(*cacheItem)
   104  	if !ok {
   105  		return nil, false
   106  	}
   107  	// 过期的元素数据也返回,但ok为false
   108  	// 由于未做并发控制,因此有可能并发时导致数据被清除(另外一个goroutine刚好在更新)
   109  	// 由于是缓存数据并不会导致数据出错,因此不添加并发控制
   110  	value := item.value
   111  	if item.isExpired() {
   112  		// 过期的元素删除
   113  		c.lru.Remove(key)
   114  		return value, false
   115  	}
   116  	return value, true
   117  }
   118  
   119  // GetBytes is the same as Get function, but returns []byte
   120  func (c *Cache) GetBytes(key Key) ([]byte, bool) {
   121  	value, ok := c.Get(key)
   122  	var buf []byte
   123  	if value != nil {
   124  		buf, _ = value.([]byte)
   125  	}
   126  	return buf, ok
   127  }
   128  
   129  // TTL returns the ttl of key
   130  func (c *Cache) TTL(key Key) time.Duration {
   131  	data, ok := c.lru.Peek(key)
   132  	if !ok {
   133  		// 元素不存在
   134  		return time.Duration(-2)
   135  	}
   136  	item, ok := data.(*cacheItem)
   137  	if !ok {
   138  		// 元素转换失败则认为不存在
   139  		return time.Duration(-2)
   140  	}
   141  	now := time.Now().UnixNano()
   142  	if item.expiredAt <= now {
   143  		// 元素已过期
   144  		return time.Duration(-1)
   145  	}
   146  	return time.Duration(item.expiredAt - now)
   147  }
   148  
   149  // Peek get a key's value from the cache, but not move to front.
   150  // The performance is better than get.
   151  // It will not be removed if the cache is expired.
   152  func (c *Cache) Peek(key Key) (interface{}, bool) {
   153  	data, ok := c.lru.Peek(key)
   154  	if !ok {
   155  		return nil, false
   156  	}
   157  	item, ok := data.(*cacheItem)
   158  	if !ok {
   159  		return nil, false
   160  	}
   161  	// 过期的元素数据也返回,但ok为false
   162  	value := item.value
   163  	if item.isExpired() {
   164  		// 过期不清除
   165  		return value, false
   166  	}
   167  	return value, true
   168  }
   169  
   170  // PeekBytes is the same as Peek function, but returns []byte
   171  func (c *Cache) PeekBytes(key Key) ([]byte, bool) {
   172  	value, ok := c.Peek(key)
   173  	var buf []byte
   174  	if value != nil {
   175  		buf, _ = value.([]byte)
   176  	}
   177  	return buf, ok
   178  }
   179  
   180  // Remove removes the key's value from the cache.
   181  func (c *Cache) Remove(key Key) {
   182  	c.lru.Remove(key)
   183  }
   184  
   185  // Len returns the number of items in the cache.
   186  func (c *Cache) Len() int {
   187  	return c.lru.Len()
   188  }
   189  
   190  // Keys gets all keys of cache
   191  func (c *Cache) Keys() []Key {
   192  	keys := c.lru.Keys()
   193  	result := make([]Key, len(keys))
   194  	for i, k := range keys {
   195  		result[i] = k
   196  	}
   197  	return result
   198  }