optimize lru purging (#8381)
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
2fba6fa799
Коммит
600528e1cf
113
utils/lru.go
113
utils/lru.go
@@ -9,15 +9,14 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"errors"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Caching Interface
|
// Caching Interface
|
||||||
type ObjectCache interface {
|
type ObjectCache interface {
|
||||||
AddWithExpiresInSecs(key, value interface{}, expireAtSecs int64) bool
|
AddWithExpiresInSecs(key, value interface{}, expireAtSecs int64)
|
||||||
AddWithDefaultExpires(key, value interface{}) bool
|
AddWithDefaultExpires(key, value interface{})
|
||||||
Purge()
|
Purge()
|
||||||
Get(key interface{}) (value interface{}, ok bool)
|
Get(key interface{}) (value interface{}, ok bool)
|
||||||
Remove(key interface{})
|
Remove(key interface{})
|
||||||
@@ -32,10 +31,11 @@ type Cache struct {
|
|||||||
evictList *list.List
|
evictList *list.List
|
||||||
items map[interface{}]*list.Element
|
items map[interface{}]*list.Element
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
onEvicted func(key interface{}, value interface{})
|
|
||||||
name string
|
name string
|
||||||
defaultExpiry int64
|
defaultExpiry int64
|
||||||
invalidateClusterEvent string
|
invalidateClusterEvent string
|
||||||
|
currentGeneration int64
|
||||||
|
len int
|
||||||
}
|
}
|
||||||
|
|
||||||
// entry is used to hold a value in the evictList
|
// entry is used to hold a value in the evictList
|
||||||
@@ -43,25 +43,16 @@ type entry struct {
|
|||||||
key interface{}
|
key interface{}
|
||||||
value interface{}
|
value interface{}
|
||||||
expireAtSecs int64
|
expireAtSecs int64
|
||||||
|
generation int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates an LRU of the given size
|
// New creates an LRU of the given size
|
||||||
func NewLru(size int) *Cache {
|
func NewLru(size int) *Cache {
|
||||||
cache, _ := NewLruWithEvict(size, nil)
|
return &Cache{
|
||||||
return cache
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLruWithEvict(size int, onEvicted func(key interface{}, value interface{})) (*Cache, error) {
|
|
||||||
if size <= 0 {
|
|
||||||
return nil, errors.New(T("utils.iru.with_evict"))
|
|
||||||
}
|
|
||||||
c := &Cache{
|
|
||||||
size: size,
|
size: size,
|
||||||
evictList: list.New(),
|
evictList: list.New(),
|
||||||
items: make(map[interface{}]*list.Element, size),
|
items: make(map[interface{}]*list.Element, size),
|
||||||
onEvicted: onEvicted,
|
|
||||||
}
|
}
|
||||||
return c, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLruWithParams(size int, name string, defaultExpiry int64, invalidateClusterEvent string) *Cache {
|
func NewLruWithParams(size int, name string, defaultExpiry int64, invalidateClusterEvent string) *Cache {
|
||||||
@@ -77,26 +68,19 @@ func (c *Cache) Purge() {
|
|||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
if c.onEvicted != nil {
|
c.len = 0
|
||||||
for k, v := range c.items {
|
c.currentGeneration++
|
||||||
c.onEvicted(k, v.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.evictList = list.New()
|
|
||||||
c.items = make(map[interface{}]*list.Element, c.size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) Add(key, value interface{}) bool {
|
func (c *Cache) Add(key, value interface{}) {
|
||||||
return c.AddWithExpiresInSecs(key, value, 0)
|
c.AddWithExpiresInSecs(key, value, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) AddWithDefaultExpires(key, value interface{}) bool {
|
func (c *Cache) AddWithDefaultExpires(key, value interface{}) {
|
||||||
return c.AddWithExpiresInSecs(key, value, c.defaultExpiry)
|
c.AddWithExpiresInSecs(key, value, c.defaultExpiry)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds a value to the cache. Returns true if an eviction occurred.
|
func (c *Cache) AddWithExpiresInSecs(key, value interface{}, expireAtSecs int64) {
|
||||||
func (c *Cache) AddWithExpiresInSecs(key, value interface{}, expireAtSecs int64) bool {
|
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
@@ -107,45 +91,46 @@ func (c *Cache) AddWithExpiresInSecs(key, value interface{}, expireAtSecs int64)
|
|||||||
// Check for existing item
|
// Check for existing item
|
||||||
if ent, ok := c.items[key]; ok {
|
if ent, ok := c.items[key]; ok {
|
||||||
c.evictList.MoveToFront(ent)
|
c.evictList.MoveToFront(ent)
|
||||||
ent.Value.(*entry).value = value
|
e := ent.Value.(*entry)
|
||||||
ent.Value.(*entry).expireAtSecs = expireAtSecs
|
e.value = value
|
||||||
return false
|
e.expireAtSecs = expireAtSecs
|
||||||
|
if e.generation != c.currentGeneration {
|
||||||
|
e.generation = c.currentGeneration
|
||||||
|
c.len++
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new item
|
// Add new item
|
||||||
ent := &entry{key, value, expireAtSecs}
|
ent := &entry{key, value, expireAtSecs, c.currentGeneration}
|
||||||
entry := c.evictList.PushFront(ent)
|
entry := c.evictList.PushFront(ent)
|
||||||
c.items[key] = entry
|
c.items[key] = entry
|
||||||
|
c.len++
|
||||||
|
|
||||||
evict := c.evictList.Len() > c.size
|
if c.evictList.Len() > c.size {
|
||||||
// Verify size not exceeded
|
c.removeElement(c.evictList.Back())
|
||||||
if evict {
|
|
||||||
c.removeOldest()
|
|
||||||
}
|
}
|
||||||
return evict
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get looks up a key's value from the cache.
|
|
||||||
func (c *Cache) Get(key interface{}) (value interface{}, ok bool) {
|
func (c *Cache) Get(key interface{}) (value interface{}, ok bool) {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
if ent, ok := c.items[key]; ok {
|
if ent, ok := c.items[key]; ok {
|
||||||
|
e := ent.Value.(*entry)
|
||||||
|
|
||||||
if ent.Value.(*entry).expireAtSecs > 0 {
|
if e.generation != c.currentGeneration || (e.expireAtSecs > 0 && (time.Now().UnixNano()/int64(time.Second)) > e.expireAtSecs) {
|
||||||
if (time.Now().UnixNano() / int64(time.Second)) > ent.Value.(*entry).expireAtSecs {
|
c.removeElement(ent)
|
||||||
c.removeElement(ent)
|
return nil, false
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.evictList.MoveToFront(ent)
|
c.evictList.MoveToFront(ent)
|
||||||
return ent.Value.(*entry).value, true
|
return ent.Value.(*entry).value, true
|
||||||
}
|
}
|
||||||
return
|
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes the provided key from the cache.
|
|
||||||
func (c *Cache) Remove(key interface{}) {
|
func (c *Cache) Remove(key interface{}) {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
@@ -155,25 +140,19 @@ func (c *Cache) Remove(key interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveOldest removes the oldest item from the cache.
|
|
||||||
func (c *Cache) RemoveOldest() {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
c.removeOldest()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keys returns a slice of the keys in the cache, from oldest to newest.
|
// Keys returns a slice of the keys in the cache, from oldest to newest.
|
||||||
func (c *Cache) Keys() []interface{} {
|
func (c *Cache) Keys() []interface{} {
|
||||||
c.lock.RLock()
|
c.lock.RLock()
|
||||||
defer c.lock.RUnlock()
|
defer c.lock.RUnlock()
|
||||||
|
|
||||||
keys := make([]interface{}, len(c.items))
|
keys := make([]interface{}, c.len)
|
||||||
ent := c.evictList.Back()
|
|
||||||
i := 0
|
i := 0
|
||||||
for ent != nil {
|
for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
|
||||||
keys[i] = ent.Value.(*entry).key
|
e := ent.Value.(*entry)
|
||||||
ent = ent.Prev()
|
if e.generation == c.currentGeneration {
|
||||||
i++
|
keys[i] = e.key
|
||||||
|
i++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return keys
|
return keys
|
||||||
@@ -183,7 +162,7 @@ func (c *Cache) Keys() []interface{} {
|
|||||||
func (c *Cache) Len() int {
|
func (c *Cache) Len() int {
|
||||||
c.lock.RLock()
|
c.lock.RLock()
|
||||||
defer c.lock.RUnlock()
|
defer c.lock.RUnlock()
|
||||||
return c.evictList.Len()
|
return c.len
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) Name() string {
|
func (c *Cache) Name() string {
|
||||||
@@ -194,20 +173,12 @@ func (c *Cache) GetInvalidateClusterEvent() string {
|
|||||||
return c.invalidateClusterEvent
|
return c.invalidateClusterEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeOldest removes the oldest item from the cache.
|
|
||||||
func (c *Cache) removeOldest() {
|
|
||||||
ent := c.evictList.Back()
|
|
||||||
if ent != nil {
|
|
||||||
c.removeElement(ent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeElement is used to remove a given list element from the cache
|
// removeElement is used to remove a given list element from the cache
|
||||||
func (c *Cache) removeElement(e *list.Element) {
|
func (c *Cache) removeElement(e *list.Element) {
|
||||||
c.evictList.Remove(e)
|
c.evictList.Remove(e)
|
||||||
kv := e.Value.(*entry)
|
kv := e.Value.(*entry)
|
||||||
delete(c.items, kv.key)
|
if kv.generation == c.currentGeneration {
|
||||||
if c.onEvicted != nil {
|
c.len--
|
||||||
c.onEvicted(kv.key, kv.value)
|
|
||||||
}
|
}
|
||||||
|
delete(c.items, kv.key)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,7 @@ import "testing"
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
func TestLRU(t *testing.T) {
|
func TestLRU(t *testing.T) {
|
||||||
evictCounter := 0
|
l := NewLru(128)
|
||||||
onEvicted := func(k interface{}, v interface{}) {
|
|
||||||
evictCounter += 1
|
|
||||||
}
|
|
||||||
l, err := NewLruWithEvict(128, onEvicted)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 256; i++ {
|
for i := 0; i < 256; i++ {
|
||||||
l.Add(i, i)
|
l.Add(i, i)
|
||||||
@@ -27,10 +20,6 @@ func TestLRU(t *testing.T) {
|
|||||||
t.Fatalf("bad len: %v", l.Len())
|
t.Fatalf("bad len: %v", l.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
if evictCounter != 128 {
|
|
||||||
t.Fatalf("bad evict count: %v", evictCounter)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, k := range l.Keys() {
|
for i, k := range l.Keys() {
|
||||||
if v, ok := l.Get(k); !ok || v != k || v != i+128 {
|
if v, ok := l.Get(k); !ok || v != k || v != i+128 {
|
||||||
t.Fatalf("bad key: %v", k)
|
t.Fatalf("bad key: %v", k)
|
||||||
@@ -73,26 +62,6 @@ func TestLRU(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// test that Add return true/false if an eviction occurred
|
|
||||||
func TestLRUAdd(t *testing.T) {
|
|
||||||
evictCounter := 0
|
|
||||||
onEvicted := func(k interface{}, v interface{}) {
|
|
||||||
evictCounter += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
l, err := NewLruWithEvict(1, onEvicted)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if l.Add(1, 1) || evictCounter != 0 {
|
|
||||||
t.Errorf("should not have an eviction")
|
|
||||||
}
|
|
||||||
if !l.Add(2, 2) || evictCounter != 1 {
|
|
||||||
t.Errorf("should have an eviction")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLRUExpire(t *testing.T) {
|
func TestLRUExpire(t *testing.T) {
|
||||||
l := NewLru(128)
|
l := NewLru(128)
|
||||||
|
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user