stale.go 1.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  1. package cache
  2. import (
  3. "context"
  4. "fwh-toolbox-server/cache/redisx"
  5. "go.uber.org/zap"
  6. )
  7. // GetOrSetStale: 如果缓存存在但 TTL 小于 threshold,则返回旧值并后台刷新
  8. func GetOrSetStale[T any](
  9. ctx context.Context,
  10. key string,
  11. loader LoaderFunc[T],
  12. opts ...Option,
  13. ) (*T, error) {
  14. o := defaultOptions()
  15. for _, op := range opts {
  16. op(o)
  17. }
  18. full := fullKey(o.prefix, key)
  19. // 1) 尝试读取缓存并检查 TTL
  20. var out T
  21. if err := redisx.GetJSON(ctx, full, &out); err == nil {
  22. // cache exists => check remaining TTL
  23. ttl, err := redisx.TTL(ctx, full)
  24. if err != nil {
  25. // 无法获取 TTL 则直接返回缓存值
  26. return &out, nil
  27. }
  28. if ttl <= 0 || ttl > o.staleThreshold {
  29. // TTL 足够 -> 直接返回
  30. return &out, nil
  31. }
  32. // TTL <= threshold -> 视为 stale:返回旧值并后台刷新
  33. go func() {
  34. // Use GetOrSet with lock disabled? We want one worker to refresh: use lockEnabled true
  35. _, err := GetOrSet(ctx, key, loader, append(opts, WithAsync(false))...)
  36. if err != nil {
  37. zap.L().Error("cachex: background refresh failed", zap.String("full", full), zap.String("err", err.Error()))
  38. }
  39. }()
  40. return &out, nil
  41. }
  42. // 未命中则走普通 GetOrSet(带锁)
  43. return GetOrSet(ctx, key, loader, opts...)
  44. }