feat: add local cache for high frequency reads (#2036)

* feat: msg local cache

* feat: msg local cache

* feat: msg local cache

* feat: msg local cache

* feat: msg local cache

* feat: msg local cache

* fix: mongo

* fix: mongo

* fix: mongo

* openim.yaml

* localcache

* localcache

* localcache

* localcache

* localcache

* localcache

* localcache

* localcache

* localcache

* local cache

* local cache

* local cache

* local cache

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* feat: cache add single-flight and timing-wheel.

* feat: local cache

* feat: local cache

* feat: local cache

* feat: cache add single-flight and timing-wheel.

* feat: local cache

* feat: local cache

* feat: local cache

* feat: local cache

* feat: local cache

* feat: local cache

* feat: local cache

* feat: local cache

* feat: local cache

* feat: local cache

* feat: local cache

* feat: local cache

* feat: local cache

* feat: local cache

* feat: local cache

* feat: local cache

* feat: local cache

* feat: msg rpc local cache

* feat: msg rpc local cache

* feat: msg rpc local cache

* feat: msg rpc local cache

* feat: msg rpc local cache

* feat: msg rpc local cache

* refactor: refactor the code of push and optimization.

* cicd: robot automated Change

* refactor: rename cache.

* merge

* fix: refactor project dir avoid import cycle.

* update tools

* merge

* feat: conversation FindRecvMsgNotNotifyUserIDs

* feat: conversation FindRecvMsgNotNotifyUserIDs

* feat: conversation FindRecvMsgNotNotifyUserIDs

* merge

* merge the latest main

---------

Co-authored-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
Co-authored-by: withchao <withchao@users.noreply.github.com>
This commit is contained in:
chao
2024-03-08 16:30:47 +08:00
committed by GitHub
parent 291443dd6b
commit b9cf40034c
60 changed files with 1860 additions and 390 deletions
+20
View File
@@ -0,0 +1,20 @@
package lru
import "github.com/hashicorp/golang-lru/v2/simplelru"
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)
Del(key K) bool
Stop()
}
type Target interface {
IncrGetHit()
IncrGetSuccess()
IncrGetFailed()
IncrDelHit()
IncrDelNotFound()
}
+78
View File
@@ -0,0 +1,78 @@
package lru
import (
"github.com/hashicorp/golang-lru/v2/expirable"
"sync"
"time"
)
func NewExpirationLRU[K comparable, V any](size int, successTTL, failedTTL time.Duration, target Target, onEvict EvictCallback[K, V]) LRU[K, V] {
var cb expirable.EvictCallback[K, *expirationLruItem[V]]
if onEvict != nil {
cb = func(key K, value *expirationLruItem[V]) {
onEvict(key, value.value)
}
}
core := expirable.NewLRU[K, *expirationLruItem[V]](size, cb, successTTL)
return &ExpirationLRU[K, V]{
core: core,
successTTL: successTTL,
failedTTL: failedTTL,
target: target,
}
}
type expirationLruItem[V any] struct {
lock sync.RWMutex
err error
value V
}
type ExpirationLRU[K comparable, V any] struct {
lock sync.Mutex
core *expirable.LRU[K, *expirationLruItem[V]]
successTTL time.Duration
failedTTL time.Duration
target Target
}
func (x *ExpirationLRU[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 = &expirationLruItem[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 *ExpirationLRU[K, V]) Del(key K) bool {
x.lock.Lock()
ok := x.core.Remove(key)
x.lock.Unlock()
if ok {
x.target.IncrDelHit()
} else {
x.target.IncrDelNotFound()
}
return ok
}
func (x *ExpirationLRU[K, V]) Stop() {
}
+90
View File
@@ -0,0 +1,90 @@
package lru
import (
"github.com/hashicorp/golang-lru/v2/simplelru"
"sync"
"time"
)
type layLruItem[V any] struct {
lock sync.Mutex
expires int64
err error
value V
}
func NewLayLRU[K comparable, V any](size int, successTTL, failedTTL time.Duration, target Target, onEvict EvictCallback[K, V]) *LayLRU[K, V] {
var cb simplelru.EvictCallback[K, *layLruItem[V]]
if onEvict != nil {
cb = func(key K, value *layLruItem[V]) {
onEvict(key, value.value)
}
}
core, err := simplelru.NewLRU[K, *layLruItem[V]](size, cb)
if err != nil {
panic(err)
}
return &LayLRU[K, V]{
core: core,
successTTL: successTTL,
failedTTL: failedTTL,
target: target,
}
}
type LayLRU[K comparable, V any] struct {
lock sync.Mutex
core *simplelru.LRU[K, *layLruItem[V]]
successTTL time.Duration
failedTTL time.Duration
target Target
}
func (x *LayLRU[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 = &layLruItem[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 *LayLRU[K, V]) Del(key K) bool {
x.lock.Lock()
ok := x.core.Remove(key)
x.lock.Unlock()
if ok {
x.target.IncrDelHit()
} else {
x.target.IncrDelNotFound()
}
return ok
}
func (x *LayLRU[K, V]) Stop() {
}
+104
View File
@@ -0,0 +1,104 @@
package lru
import (
"fmt"
"hash/fnv"
"sync"
"sync/atomic"
"testing"
"time"
"unsafe"
)
type cacheTarget struct {
getHit int64
getSuccess int64
getFailed int64
delHit int64
delNotFound int64
}
func (r *cacheTarget) IncrGetHit() {
atomic.AddInt64(&r.getHit, 1)
}
func (r *cacheTarget) IncrGetSuccess() {
atomic.AddInt64(&r.getSuccess, 1)
}
func (r *cacheTarget) IncrGetFailed() {
atomic.AddInt64(&r.getFailed, 1)
}
func (r *cacheTarget) IncrDelHit() {
atomic.AddInt64(&r.delHit, 1)
}
func (r *cacheTarget) IncrDelNotFound() {
atomic.AddInt64(&r.delNotFound, 1)
}
func (r *cacheTarget) String() string {
return fmt.Sprintf("getHit: %d, getSuccess: %d, getFailed: %d, delHit: %d, delNotFound: %d", r.getHit, r.getSuccess, r.getFailed, r.delHit, r.delNotFound)
}
func TestName(t *testing.T) {
target := &cacheTarget{}
l := NewSlotLRU[string, string](100, func(k string) uint64 {
h := fnv.New64a()
h.Write(*(*[]byte)(unsafe.Pointer(&k)))
return h.Sum64()
}, func() LRU[string, string] {
return NewExpirationLRU[string, string](100, time.Second*60, time.Second, target, nil)
})
//l := NewInertiaLRU[string, string](1000, time.Second*20, time.Second*5, target)
fn := func(key string, n int, fetch func() (string, error)) {
for i := 0; i < n; i++ {
//v, err := l.Get(key, fetch)
//if err == nil {
// t.Log("key", key, "value", v)
//} else {
// t.Error("key", key, err)
//}
v, err := l.Get(key, fetch)
//time.Sleep(time.Second / 100)
func(v ...any) {}(v, err)
}
}
tmp := make(map[string]struct{})
var wg sync.WaitGroup
for i := 0; i < 10000; i++ {
wg.Add(1)
key := fmt.Sprintf("key_%d", i%200)
tmp[key] = struct{}{}
go func() {
defer wg.Done()
//t.Log(key)
fn(key, 10000, func() (string, error) {
//time.Sleep(time.Second * 3)
//t.Log(time.Now(), "key", key, "fetch")
//if rand.Uint32()%5 == 0 {
// return "value_" + key, nil
//}
//return "", errors.New("rand error")
return "value_" + key, nil
})
}()
//wg.Add(1)
//go func() {
// defer wg.Done()
// for i := 0; i < 10; i++ {
// l.Del(key)
// time.Sleep(time.Second / 3)
// }
//}()
}
wg.Wait()
t.Log(len(tmp))
t.Log(target.String())
}
+37
View File
@@ -0,0 +1,37 @@
package lru
func NewSlotLRU[K comparable, V any](slotNum int, hash func(K) uint64, create func() LRU[K, V]) LRU[K, V] {
x := &slotLRU[K, V]{
n: uint64(slotNum),
slots: make([]LRU[K, V], slotNum),
hash: hash,
}
for i := 0; i < slotNum; i++ {
x.slots[i] = create()
}
return x
}
type slotLRU[K comparable, V any] struct {
n uint64
slots []LRU[K, V]
hash func(k K) uint64
}
func (x *slotLRU[K, V]) getIndex(k K) uint64 {
return x.hash(k) % x.n
}
func (x *slotLRU[K, V]) Get(key K, fetch func() (V, error)) (V, error) {
return x.slots[x.getIndex(key)].Get(key, fetch)
}
func (x *slotLRU[K, V]) Del(key K) bool {
return x.slots[x.getIndex(key)].Del(key)
}
func (x *slotLRU[K, V]) Stop() {
for _, slot := range x.slots {
slot.Stop()
}
}