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
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) |
|
}
|
|
|