You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

152 lines
3.4 KiB

package cache
import (
"context"
"encoding"
"errors"
"reflect"
"sonet/pkg/plugins/mq"
"sonet/pkg/utils/cache"
"sonet/pkg/utils/logger"
"time"
)
const (
flushTopicSuffix = "flush"
)
type LocalRemoteCache struct {
topic string
flushTopic string
local *cache.KVCache[string, any]
remote Cache
producer mq.Producer
consumer mq.Consumer
}
type LocalRemoteCacheOptions struct {
Topic string
LocalExpiration time.Duration
CleanupInterval time.Duration
Remote Cache
Producer mq.Producer
Consumer mq.Consumer
}
func NewLocalRemoteCache(opts LocalRemoteCacheOptions) (*LocalRemoteCache, error) {
expireStore := cache.NewExpireStore[string, any](
opts.LocalExpiration,
opts.CleanupInterval,
nil,
func(k string) string {
return k
},
)
lrc := &LocalRemoteCache{
topic: opts.Topic,
flushTopic: opts.Topic + flushTopicSuffix,
local: expireStore,
remote: opts.Remote,
producer: opts.Producer,
consumer: opts.Consumer,
}
return lrc, lrc.init()
}
func (m *LocalRemoteCache) init() error {
// subscribe mq flush cache msg
return m.consumer.SubscribeBroadcast(m.flushTopic, func(msg *mq.Message) error {
// TODO 判断不删除 local
key := string(msg.Body)
m.local.Delete(key)
logger.Infof("flush cache %s key: %s\n", m.topic, key)
return nil
})
}
func (m *LocalRemoteCache) Set(ctx context.Context, key string, value any) error {
err := m.remote.Set(ctx, key, value)
if err != nil {
return err
}
// set local cache
m.local.SetDefault(key, value)
// mq flush cache msg
return m.producer.Publish(m.flushTopic, []byte(key))
}
func (m *LocalRemoteCache) Load(ctx context.Context, key string, target any) error {
val := m.local.Get(key)
if val != nil {
err := m.copyBinaryMarshal(val, target)
if err != nil {
err2 := m.Del(ctx, key)
if err2 != nil {
return err2
}
return err
}
return nil
}
logger.Infof("%s load from remote cache: %s\n", m.topic, key)
// load from redis, TODO singleFly
err := m.remote.Load(ctx, key, target)
if err != nil {
return err
}
// cache to local
m.local.SetDefault(key, target)
return nil
}
func (m *LocalRemoteCache) ForceLoad(ctx context.Context, key string, target any) error {
err := m.remote.Load(ctx, key, target)
if err != nil {
if err == NotExists {
m.local.Delete(key)
}
return err
}
instance := reflect.New(reflect.TypeOf(target).Elem()).Interface()
err = m.copyBinaryMarshal(target, instance)
if err != nil {
return err
}
m.local.SetDefault(key, instance)
return nil
}
func (m *LocalRemoteCache) copyBinaryMarshal(origin any, target any) error {
marshaler, ok1 := origin.(encoding.BinaryMarshaler)
unmarshaler, ok2 := target.(encoding.BinaryUnmarshaler)
if ok1 && ok2 {
bytes, err := marshaler.MarshalBinary()
if err != nil {
return err
}
if err = unmarshaler.UnmarshalBinary(bytes); err != nil {
return err
}
} else {
return errors.New("value not implement encoding.BinaryMarshaler and encoding.BinaryUnmarshaler")
}
return nil
}
func (m *LocalRemoteCache) Del(ctx context.Context, keys ...string) error {
err := m.remote.Del(ctx, keys...)
if err != nil {
return err
}
var byteKeys = make([][]byte, 0, len(keys))
for _, key := range keys {
// klog.Info("del cache: ", key)
m.local.Delete(key)
byteKeys = append(byteKeys, []byte(key))
}
return m.producer.MultiPublish(m.flushTopic, byteKeys)
}