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