feat: local cache

This commit is contained in:
withchao 2024-01-15 11:42:18 +08:00
parent 749eb11830
commit 3e30e50a09
7 changed files with 172 additions and 116 deletions

View File

@ -14,18 +14,18 @@ type Cache[V any] interface {
func NewCache[V any](slotNum, slotSize int, successTTL, failedTTL time.Duration, target Target, onEvict EvictCallback[string, V]) Cache[V] { func NewCache[V any](slotNum, slotSize int, successTTL, failedTTL time.Duration, target Target, onEvict EvictCallback[string, V]) Cache[V] {
c := &slot[V]{ c := &slot[V]{
n: uint64(slotNum), n: uint64(slotNum),
slots: make([]*LRU[string, V], slotNum), slots: make([]*InertiaLRU[string, V], slotNum),
target: target, target: target,
} }
for i := 0; i < slotNum; i++ { for i := 0; i < slotNum; i++ {
c.slots[i] = NewLRU[string, V](slotSize, successTTL, failedTTL, c.target, onEvict) c.slots[i] = NewInertiaLRU[string, V](slotSize, successTTL, failedTTL, c.target, onEvict)
} }
return c return c
} }
type slot[V any] struct { type slot[V any] struct {
n uint64 n uint64
slots []*LRU[string, V] slots []*InertiaLRU[string, V]
target Target target Target
} }

View File

@ -1,5 +1 @@
package local package local
import "github.com/hashicorp/golang-lru/v2/simplelru"
type EvictCallback[K comparable, V any] simplelru.EvictCallback[K, V]

View File

@ -1,108 +1,20 @@
package local package local
import ( import "github.com/hashicorp/golang-lru/v2/simplelru"
"github.com/hashicorp/golang-lru/v2/simplelru"
"sync"
"time"
)
type LRU1[K comparable, V any] interface { type EvictCallback[K comparable, V any] simplelru.EvictCallback[K, V]
type LRU[K comparable, V any] interface {
Get(key K, fetch func() (V, error)) (V, error) Get(key K, fetch func() (V, error)) (V, error)
Del(key K) bool Del(key K) bool
Stop() Stop()
} }
//type expirableLRU[K comparable, V any] struct { type Target interface {
// core expirable.LRU[K, V] IncrGetHit()
//} IncrGetSuccess()
// IncrGetFailed()
//func (x *expirableLRU[K, V]) Get(key K, fetch func() (V, error)) (V, error) {
//
// return x.core.Get(key, fetch)
//}
//
//func (x *expirableLRU[K, V]) Del(key K) bool {
// return x.core.Remove(key)
//}
//
//func (x *expirableLRU[K, V]) Stop() {
//
//}
type waitItem[V any] struct {
lock sync.Mutex
expires int64
err error
value V
}
func NewLRU[K comparable, V any](size int, successTTL, failedTTL time.Duration, target Target, onEvict EvictCallback[K, V]) *LRU[K, V] {
var cb simplelru.EvictCallback[K, *waitItem[V]]
if onEvict != nil {
cb = func(key K, value *waitItem[V]) {
onEvict(key, value.value)
}
}
core, err := simplelru.NewLRU[K, *waitItem[V]](size, cb)
if err != nil {
panic(err)
}
return &LRU[K, V]{
core: core,
successTTL: successTTL,
failedTTL: failedTTL,
target: target,
}
}
type LRU[K comparable, V any] struct {
lock sync.Mutex
core *simplelru.LRU[K, *waitItem[V]]
successTTL time.Duration
failedTTL time.Duration
target Target
}
func (x *LRU[K, V]) Get(key K, fetch func() (V, error)) (V, error) {
x.lock.Lock()
v, ok := x.core.Get(key)
if ok {
x.lock.Unlock()
v.lock.Lock()
expires, value, err := v.expires, v.value, v.err
if expires != 0 && expires > time.Now().UnixMilli() {
v.lock.Unlock()
x.target.IncrGetHit()
return value, err
}
} else {
v = &waitItem[V]{}
x.core.Add(key, v)
v.lock.Lock()
x.lock.Unlock()
}
defer v.lock.Unlock()
if v.expires > time.Now().UnixMilli() {
return v.value, v.err
}
v.value, v.err = fetch()
if v.err == nil {
v.expires = time.Now().Add(x.successTTL).UnixMilli()
x.target.IncrGetSuccess()
} else {
v.expires = time.Now().Add(x.failedTTL).UnixMilli()
x.target.IncrGetFailed()
}
return v.value, v.err
}
func (x *LRU[K, V]) Del(key K) bool {
x.lock.Lock()
ok := x.core.Remove(key)
x.lock.Unlock()
return ok
}
func (x *LRU[K, V]) Stop() {
IncrDelHit()
IncrDelNotFound()
} }

View File

@ -0,0 +1,73 @@
package local
import (
"github.com/hashicorp/golang-lru/v2/expirable"
"sync"
"time"
)
func NewExpirableLRU[K comparable, V any](size int, successTTL, failedTTL time.Duration, target Target, onEvict EvictCallback[K, V]) LRU[K, V] {
var cb expirable.EvictCallback[K, *expirableLruItem[V]]
if onEvict != nil {
cb = func(key K, value *expirableLruItem[V]) {
onEvict(key, value.value)
}
}
core := expirable.NewLRU[K, *expirableLruItem[V]](size, cb, successTTL)
return &expirableLRU[K, V]{
core: core,
successTTL: successTTL,
failedTTL: failedTTL,
target: target,
}
}
type expirableLruItem[V any] struct {
lock sync.RWMutex
err error
value V
}
type expirableLRU[K comparable, V any] struct {
lock sync.Mutex
core *expirable.LRU[K, *expirableLruItem[V]]
successTTL time.Duration
failedTTL time.Duration
target Target
}
func (x *expirableLRU[K, V]) Get(key K, fetch func() (V, error)) (V, error) {
x.lock.Lock()
v, ok := x.core.Get(key)
if ok {
x.lock.Unlock()
x.target.IncrGetSuccess()
v.lock.RLock()
defer v.lock.RUnlock()
return v.value, v.err
} else {
v = &expirableLruItem[V]{}
x.core.Add(key, v)
v.lock.Lock()
x.lock.Unlock()
defer v.lock.Unlock()
v.value, v.err = fetch()
if v.err == nil {
x.target.IncrGetSuccess()
} else {
x.target.IncrGetFailed()
x.core.Remove(key)
}
return v.value, v.err
}
}
func (x *expirableLRU[K, V]) Del(key K) bool {
x.lock.Lock()
ok := x.core.Remove(key)
x.lock.Unlock()
return ok
}
func (x *expirableLRU[K, V]) Stop() {
}

View File

@ -0,0 +1,85 @@
package local
import (
"github.com/hashicorp/golang-lru/v2/simplelru"
"sync"
"time"
)
type inertiaLruItem[V any] struct {
lock sync.Mutex
expires int64
err error
value V
}
func NewInertiaLRU[K comparable, V any](size int, successTTL, failedTTL time.Duration, target Target, onEvict EvictCallback[K, V]) *InertiaLRU[K, V] {
var cb simplelru.EvictCallback[K, *inertiaLruItem[V]]
if onEvict != nil {
cb = func(key K, value *inertiaLruItem[V]) {
onEvict(key, value.value)
}
}
core, err := simplelru.NewLRU[K, *inertiaLruItem[V]](size, cb)
if err != nil {
panic(err)
}
return &InertiaLRU[K, V]{
core: core,
successTTL: successTTL,
failedTTL: failedTTL,
target: target,
}
}
type InertiaLRU[K comparable, V any] struct {
lock sync.Mutex
core *simplelru.LRU[K, *inertiaLruItem[V]]
successTTL time.Duration
failedTTL time.Duration
target Target
}
func (x *InertiaLRU[K, V]) Get(key K, fetch func() (V, error)) (V, error) {
x.lock.Lock()
v, ok := x.core.Get(key)
if ok {
x.lock.Unlock()
v.lock.Lock()
expires, value, err := v.expires, v.value, v.err
if expires != 0 && expires > time.Now().UnixMilli() {
v.lock.Unlock()
x.target.IncrGetHit()
return value, err
}
} else {
v = &inertiaLruItem[V]{}
x.core.Add(key, v)
v.lock.Lock()
x.lock.Unlock()
}
defer v.lock.Unlock()
if v.expires > time.Now().UnixMilli() {
return v.value, v.err
}
v.value, v.err = fetch()
if v.err == nil {
v.expires = time.Now().Add(x.successTTL).UnixMilli()
x.target.IncrGetSuccess()
} else {
v.expires = time.Now().Add(x.failedTTL).UnixMilli()
x.target.IncrGetFailed()
}
return v.value, v.err
}
func (x *InertiaLRU[K, V]) Del(key K) bool {
x.lock.Lock()
ok := x.core.Remove(key)
x.lock.Unlock()
return ok
}
func (x *InertiaLRU[K, V]) Stop() {
}

View File

@ -43,7 +43,7 @@ func (r *cacheTarget) String() string {
func TestName(t *testing.T) { func TestName(t *testing.T) {
target := &cacheTarget{} target := &cacheTarget{}
l := NewCache[string](100, 1000, time.Second*20, time.Second*5, target, nil) l := NewCache[string](100, 1000, time.Second*20, time.Second*5, target, nil)
//l := NewLRU[string, string](1000, time.Second*20, time.Second*5, target) //l := NewInertiaLRU[string, string](1000, time.Second*20, time.Second*5, target)
fn := func(key string, n int, fetch func() (string, error)) { fn := func(key string, n int, fetch func() (string, error)) {
for i := 0; i < n; i++ { for i := 0; i < n; i++ {

View File

@ -1,10 +0,0 @@
package local
type Target interface {
IncrGetHit()
IncrGetSuccess()
IncrGetFailed()
IncrDelHit()
IncrDelNotFound()
}