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 }