github.com/wfusion/gofusion@v1.1.14/cache/cache.go (about)

     1  package cache
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"syscall"
     8  	"time"
     9  
    10  	"github.com/pkg/errors"
    11  	"github.com/spf13/cast"
    12  
    13  	"github.com/wfusion/gofusion/common/constraint"
    14  	"github.com/wfusion/gofusion/common/utils"
    15  	"github.com/wfusion/gofusion/common/utils/clone"
    16  	"github.com/wfusion/gofusion/common/utils/compress"
    17  	"github.com/wfusion/gofusion/common/utils/inspect"
    18  	"github.com/wfusion/gofusion/common/utils/serialize"
    19  	"github.com/wfusion/gofusion/config"
    20  	"github.com/wfusion/gofusion/log"
    21  
    22  	pd "github.com/wfusion/gofusion/internal/util/payload"
    23  )
    24  
    25  type provider interface {
    26  	get(ctx context.Context, keys ...string) (cached map[string]any, missed []string)
    27  	set(ctx context.Context, kvs map[string]any, expired map[string]time.Duration) (failure []string)
    28  	del(ctx context.Context, keys ...string) (failure []string)
    29  }
    30  
    31  type parsedConf[K constraint.Sortable, T any] struct {
    32  	size    int
    33  	expired time.Duration
    34  	version int
    35  
    36  	cacheType      cacheType
    37  	remoteType     remoteType
    38  	remoteInstance string
    39  	localEvictType string
    40  	serializeType  serialize.Algorithm
    41  	compressType   compress.Algorithm
    42  
    43  	log      log.Loggable
    44  	callback callback[K, T]
    45  }
    46  
    47  type initOption struct {
    48  	appName string
    49  }
    50  
    51  func AppName(name string) utils.OptionFunc[initOption] {
    52  	return func(o *initOption) {
    53  		o.appName = name
    54  	}
    55  }
    56  
    57  func New[K constraint.Sortable, T any, TS ~[]T](name string, opts ...utils.OptionExtender) Cachable[K, T, TS] {
    58  	opt := utils.ApplyOptions[initOption](opts...)
    59  
    60  	instance := &cache[K, T, TS]{
    61  		name:    name,
    62  		appName: opt.appName,
    63  		prefix:  fmt.Sprintf("%s:%s", config.Use(opt.appName).AppName(), name),
    64  		visited: utils.NewSet[string](),
    65  	}
    66  
    67  	conf := instance.getConfig()
    68  	switch conf.cacheType {
    69  	case cacheTypeLocal:
    70  		instance.provider = newGCache(conf.size, conf.localEvictType, conf.log)
    71  	case cacheTypeRemote:
    72  		if conf.remoteType != remoteTypeRedis {
    73  			panic(UnknownRemoteType)
    74  		}
    75  		if !conf.serializeType.IsValid() && !conf.compressType.IsValid() {
    76  			panic(UnknownSerializeType)
    77  		}
    78  		instance.provider = newRedis(opt.appName, conf.remoteInstance, conf.log)
    79  	case cacheTypeRemoteLocal:
    80  		panic(ErrNotImplement)
    81  	default:
    82  		panic(UnknownCacheType)
    83  	}
    84  
    85  	return instance
    86  }
    87  
    88  type cache[K constraint.Sortable, T any, TS ~[]T] struct {
    89  	appName  string
    90  	name     string
    91  	prefix   string
    92  	provider provider
    93  	visited  *utils.Set[string]
    94  }
    95  
    96  func (c *cache[K, T, TS]) Get(ctx context.Context, keys []K, cb callback[K, T]) (ts TS) {
    97  	conf := c.getConfig()
    98  	innerKeys := c.convKeysToInner(keys, conf.version)
    99  	cached, missed := c.provider.get(ctx, innerKeys...)
   100  	kvs, _ := c.convInnerToMap(ctx, cached, conf)
   101  	defer c.visited.Insert(innerKeys...)
   102  
   103  	if cb == nil {
   104  		cb = conf.callback
   105  	}
   106  	if len(missed) > 0 && cb != nil {
   107  		keys := c.convInnerToKeys(missed, conf.version)
   108  		if conf.log != nil {
   109  			conf.log.Debug(ctx, "%v [Gofusion] %s call callback function because we do not hit the cache "+
   110  				"when get [keys%+v]", syscall.Getpid(), config.ComponentCache, keys)
   111  		}
   112  
   113  		callbackKVs, opts := cb(ctx, keys)
   114  		kvs = utils.MapMerge(kvs, callbackKVs)
   115  		innerVals, _ := c.convMapToInner(ctx, callbackKVs, conf)
   116  		_ = c.provider.set(ctx, innerVals, c.parseCallbackOption(kvs, conf, opts...))
   117  		// innerFailureKeys = append(innerFailureKeys, convInnerFailureKeys...)
   118  	}
   119  
   120  	// order by param -> keys
   121  	ts = make(TS, 0, len(kvs))
   122  	missedKeys := make([]K, 0, len(keys))
   123  	for _, k := range keys {
   124  		v, ok := kvs[k]
   125  		if !ok {
   126  			missedKeys = append(missedKeys, k)
   127  			continue
   128  		}
   129  
   130  		ts = append(ts, v)
   131  	}
   132  	if len(missedKeys) > 0 && conf.log != nil {
   133  		conf.log.Info(ctx, "%v [Gofusion] %s we still get missing keys after callback when cache get [keys%v]",
   134  			syscall.Getpid(), config.ComponentCache, missedKeys)
   135  	}
   136  
   137  	return
   138  }
   139  
   140  func (c *cache[K, T, TS]) GetAll(ctx context.Context, cb callback[K, T]) (ts TS) {
   141  	conf := c.getConfig()
   142  	allInnerKeys := c.visited.Items()
   143  	cached, missed := c.provider.get(ctx, allInnerKeys...)
   144  	kvs, _ := c.convInnerToMap(ctx, cached, conf)
   145  	if len(missed) > 0 && (cb != nil || conf.callback != nil) {
   146  		keys := c.convInnerToKeys(missed, conf.version)
   147  		if conf.log != nil {
   148  			conf.log.Info(ctx, "%v [Gofusion] %s call callback function because we do not hit the cache "+
   149  				"when get all [keys%+v]", syscall.Getpid(), config.ComponentCache, keys)
   150  		}
   151  
   152  		var (
   153  			callbackKVs map[K]T
   154  			opts        []utils.OptionExtender
   155  		)
   156  		if cb != nil {
   157  			callbackKVs, opts = cb(ctx, keys)
   158  		} else {
   159  			callbackKVs, opts = conf.callback(ctx, keys)
   160  		}
   161  
   162  		kvs = utils.MapMerge(kvs, callbackKVs)
   163  
   164  		innerVals, _ := c.convMapToInner(ctx, callbackKVs, conf)
   165  		c.provider.set(ctx, innerVals, c.parseCallbackOption(kvs, conf, opts...))
   166  	}
   167  
   168  	// order by param -> keys
   169  	ts = make(TS, 0, len(kvs))
   170  	missedKeys := make([]K, 0, len(allInnerKeys))
   171  	for _, k := range c.convInnerToKeys(allInnerKeys, conf.version) {
   172  		v, ok := kvs[k]
   173  		if !ok {
   174  			missedKeys = append(missedKeys, k)
   175  			continue
   176  		}
   177  
   178  		ts = append(ts, v)
   179  	}
   180  	c.visited.Remove(c.convKeysToInner(missedKeys, conf.version)...)
   181  	if len(missedKeys) > 0 && conf.log != nil {
   182  		conf.log.Warn(ctx, "%v [Gofusion] %s index key value failed when cache get all [keys%v]",
   183  			syscall.Getpid(), config.ComponentCache, missedKeys)
   184  	}
   185  
   186  	return
   187  }
   188  
   189  func (c *cache[K, T, TS]) Set(ctx context.Context, kvs map[K]T, opts ...utils.OptionExtender) (failure []K) {
   190  	conf := c.getConfig()
   191  	innerVals, innerFailureKeys := c.convMapToInner(ctx, kvs, conf)
   192  	defer func() {
   193  		innerKeys := utils.MapKeys(innerVals)
   194  		c.visited.Insert(innerKeys...)
   195  	}()
   196  
   197  	innerFailureKeys = append(
   198  		innerFailureKeys,
   199  		c.provider.set(ctx, innerVals, c.parseCallbackOption(kvs, conf, opts...))...,
   200  	)
   201  
   202  	if failure = c.convInnerToKeys(innerFailureKeys, conf.version); len(failure) > 0 && conf.log != nil {
   203  		conf.log.Info(ctx, "%v [Gofusion] %s set some kvs failed when set [keys%+v vals%+v]",
   204  			syscall.Getpid(), config.ComponentCache, failure, utils.MapValuesByKeys(kvs, failure))
   205  	}
   206  
   207  	return
   208  }
   209  
   210  func (c *cache[K, T, TS]) Del(ctx context.Context, keys ...K) (failure []K) {
   211  	conf := c.getConfig()
   212  	innerKeys := c.convKeysToInner(keys, conf.version)
   213  	innerFailureKeys := c.provider.del(ctx, innerKeys...)
   214  	defer c.visited.Remove(innerKeys...)
   215  
   216  	if failure = c.convInnerToKeys(innerFailureKeys, conf.version); len(failure) > 0 && conf.log != nil {
   217  		conf.log.Info(ctx, "%v [Gofusion] %s del some kvs failed when del [keys%+v]",
   218  			syscall.Getpid(), config.ComponentCache, failure)
   219  	}
   220  	return
   221  }
   222  
   223  func (c *cache[K, T, TS]) Clear(ctx context.Context) (failureKeys []K) {
   224  	conf := c.getConfig()
   225  	innerKeys := c.visited.Items()
   226  	innerFailureKeys := c.provider.del(ctx, innerKeys...)
   227  	defer c.visited.Remove(innerKeys...)
   228  
   229  	if failureKeys = c.convInnerToKeys(innerFailureKeys, conf.version); len(failureKeys) > 0 && conf.log != nil {
   230  		conf.log.Info(ctx, "%v [Gofusion] %s del some kvs failed when clear [keys%+v]",
   231  			syscall.Getpid(), config.ComponentCache, failureKeys)
   232  	}
   233  	return
   234  }
   235  
   236  func (c *cache[K, T, TS]) convKeysToInner(keys []K, version int) (inner []string) {
   237  	return utils.SliceMapping(keys, func(k K) string {
   238  		return c.convKeyToInner(k, version)
   239  	})
   240  }
   241  
   242  func (c *cache[K, T, TS]) convKeyToInner(k K, ver int) (inner string) {
   243  	return fmt.Sprintf("%s:%v:%s", c.prefix, ver, cast.ToString(k))
   244  }
   245  
   246  func (c *cache[K, T, TS]) convInnerToKeys(innerKeys []string, ver int) (keys []K) {
   247  	return utils.SliceMapping(innerKeys, func(inner string) K {
   248  		return c.convInnerToKey(inner, ver)
   249  	})
   250  }
   251  
   252  func (c *cache[K, T, TS]) convInnerToKey(inner string, ver int) (k K) {
   253  	key := strings.TrimPrefix(inner, fmt.Sprintf("%s:%v:", c.prefix, ver))
   254  	return utils.SortableToGeneric[string, K](key)
   255  }
   256  
   257  func (c *cache[K, T, TS]) convMapToInner(ctx context.Context, kvs map[K]T, conf *parsedConf[K, T]) (
   258  	inner map[string]any, innerFailureKeys []string) {
   259  	inner = make(map[string]any, len(kvs))
   260  	for k, v := range kvs {
   261  		innerKey := c.convKeyToInner(k, conf.version)
   262  		innerVal, err := c.convValToInner(v, conf)
   263  		if err != nil {
   264  			if conf.log != nil {
   265  				conf.log.Info(ctx, "%v [Gofusion] %s convert value to inner failed [err[%s] key[%+v] val[%+v]]",
   266  					syscall.Getpid(), config.ComponentCache, err, k, v)
   267  			}
   268  			innerFailureKeys = append(innerFailureKeys, innerKey)
   269  			continue
   270  		}
   271  		inner[innerKey] = innerVal
   272  	}
   273  	return
   274  }
   275  
   276  func (c *cache[K, T, TS]) convValToInner(src T, conf *parsedConf[K, T]) (dst any, err error) {
   277  	if !conf.serializeType.IsValid() && !conf.compressType.IsValid() {
   278  		return c.cloneVal(src), nil
   279  	}
   280  
   281  	return c.seal(src, conf)
   282  }
   283  
   284  func (c *cache[K, T, TS]) convInnerToMap(ctx context.Context, inner map[string]any, conf *parsedConf[K, T]) (
   285  	kvs map[K]T, innerFailureKeys []string) {
   286  	kvs = make(map[K]T, len(inner))
   287  	for k, v := range inner {
   288  		innerKey := c.convInnerToKey(k, conf.version)
   289  		innerVal, err := c.convInnerToVal(v)
   290  		if err != nil {
   291  			if conf.log != nil {
   292  				conf.log.Info(ctx, "%v [Gofusion] %s convert inner to value failed [err[%s] key[%+v] val[%+v]]",
   293  					syscall.Getpid(), config.ComponentCache, err, innerKey, v)
   294  			}
   295  			innerFailureKeys = append(innerFailureKeys, k)
   296  			continue
   297  		}
   298  		kvs[innerKey] = innerVal
   299  	}
   300  	return
   301  }
   302  
   303  func (c *cache[K, T, TS]) convInnerToVal(src any) (dst T, err error) {
   304  	srcBytes, ok1 := src.([]byte)
   305  	srcString, ok2 := src.(string)
   306  	if !ok1 && !ok2 {
   307  		return c.cloneVal(src.(T)), nil
   308  	}
   309  	if ok2 {
   310  		buffer, cb := utils.BytesBufferPool.Get(nil)
   311  		defer cb()
   312  		buffer.WriteString(srcString)
   313  		srcBytes = buffer.Bytes()
   314  	}
   315  	dst, ok, err := c.unseal(srcBytes)
   316  	if err != nil {
   317  		return
   318  	}
   319  	if !ok {
   320  		return c.cloneVal(src.(T)), nil
   321  	}
   322  	return
   323  }
   324  
   325  func (c *cache[K, T, TS]) parseCallbackOption(kvs map[K]T, conf *parsedConf[K, T], opts ...utils.OptionExtender) (
   326  	exp map[string]time.Duration) {
   327  	opt := utils.ApplyOptions[option[K]](opts...)
   328  	exp = make(map[string]time.Duration, len(kvs))
   329  
   330  	// opt.keyExpired > opt.expired > conf.expired
   331  	if conf.expired > 0 {
   332  		for k := range kvs {
   333  			innerKey := c.convKeyToInner(k, conf.version)
   334  			exp[innerKey] = conf.expired
   335  		}
   336  	}
   337  
   338  	if opt.expired > 0 {
   339  		for k := range kvs {
   340  			innerKey := c.convKeyToInner(k, conf.version)
   341  			exp[innerKey] = conf.expired
   342  		}
   343  	}
   344  
   345  	for k, e := range opt.keyExpired {
   346  		exp[c.convKeyToInner(k, conf.version)] = e
   347  	}
   348  
   349  	return
   350  }
   351  
   352  func (c *cache[K, T, TS]) seal(src T, conf *parsedConf[K, T]) (dst []byte, err error) {
   353  	return pd.Seal(src, pd.Serialize(conf.serializeType), pd.Compress(conf.compressType))
   354  }
   355  
   356  func (c *cache[K, T, TS]) unseal(src []byte) (dst T, ok bool, err error) {
   357  	_, dst, ok, err = pd.UnsealT[T](src)
   358  	return
   359  }
   360  
   361  func (c *cache[K, T, TS]) cloneVal(src T) (dst T) {
   362  	if cl, ok := any(src).(clone.Clonable[T]); ok {
   363  		dst = cl.Clone()
   364  		return
   365  	}
   366  	return clone.Slowly(src)
   367  }
   368  
   369  func (c *cache[K, T, TS]) getConfig() (conf *parsedConf[K, T]) {
   370  	var cfgs map[string]*Conf
   371  	_ = config.Use(c.appName).LoadComponentConfig(config.ComponentCache, &cfgs)
   372  	if len(cfgs) == 0 {
   373  		panic(ErrCacheNotFound)
   374  	}
   375  
   376  	cfg, ok := cfgs[c.name]
   377  	if !ok {
   378  		panic(ErrCacheNotFound)
   379  	}
   380  
   381  	conf = &parsedConf[K, T]{
   382  		size:           cfg.Size,
   383  		localEvictType: cfg.LocalEvictType,
   384  		cacheType:      cfg.CacheType,
   385  		remoteType:     cfg.RemoteType,
   386  		remoteInstance: cfg.RemoteInstance,
   387  		version:        cfg.Version,
   388  	}
   389  	if utils.IsStrNotBlank(cfg.Expired) {
   390  		conf.expired = utils.Must(time.ParseDuration(cfg.Expired))
   391  	}
   392  	if utils.IsStrNotBlank(cfg.LogInstance) {
   393  		conf.log = log.Use(cfg.LogInstance, log.AppName(c.appName))
   394  	}
   395  
   396  	if utils.IsStrNotBlank(cfg.SerializeType) {
   397  		conf.serializeType = serialize.ParseAlgorithm(cfg.SerializeType)
   398  	}
   399  
   400  	if utils.IsStrNotBlank(cfg.Compress) {
   401  		conf.compressType = compress.ParseAlgorithm(cfg.Compress)
   402  		if !conf.compressType.IsValid() {
   403  			panic(UnknownCompress)
   404  		}
   405  	}
   406  	// default serialize type when need compress
   407  	if conf.compressType.IsValid() && !conf.serializeType.IsValid() {
   408  		conf.serializeType = serialize.AlgorithmGob
   409  	}
   410  
   411  	if utils.IsStrNotBlank(cfg.Callback) {
   412  		callbackFn := inspect.FuncOf(cfg.Callback)
   413  		if callbackFn == nil {
   414  			panic(errors.Errorf("not found callback function: %s", cfg.Callback))
   415  		}
   416  		conf.callback = *(*func(ctx context.Context, missed []K) (rs map[K]T, opts []utils.OptionExtender))(callbackFn)
   417  	}
   418  
   419  	return
   420  }