github.com/unionj-cloud/go-doudou/v2@v2.3.5/toolkit/cache/cache.go (about)

     1  package cache
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"sync/atomic"
     8  	"time"
     9  
    10  	"github.com/klauspost/compress/s2"
    11  	"github.com/redis/go-redis/v9"
    12  	"github.com/vmihailenco/msgpack/v5"
    13  	"golang.org/x/sync/singleflight"
    14  )
    15  
    16  const (
    17  	compressionThreshold = 64
    18  	timeLen              = 4
    19  )
    20  
    21  const (
    22  	noCompression = 0x0
    23  	s2Compression = 0x1
    24  )
    25  
    26  var (
    27  	ErrCacheMiss          = errors.New("cache: key is missing")
    28  	errRedisLocalCacheNil = errors.New("cache: both Redis and LocalCache are nil")
    29  )
    30  
    31  type rediser interface {
    32  	Set(ctx context.Context, key string, value interface{}, ttl time.Duration) *redis.StatusCmd
    33  	SetXX(ctx context.Context, key string, value interface{}, ttl time.Duration) *redis.BoolCmd
    34  	SetNX(ctx context.Context, key string, value interface{}, ttl time.Duration) *redis.BoolCmd
    35  
    36  	Get(ctx context.Context, key string) *redis.StringCmd
    37  	Del(ctx context.Context, keys ...string) *redis.IntCmd
    38  }
    39  
    40  type Item struct {
    41  	Ctx context.Context
    42  
    43  	Key   string
    44  	Value interface{}
    45  
    46  	// TTL is the cache expiration time.
    47  	// Default TTL is 1 hour.
    48  	TTL time.Duration
    49  
    50  	// Do returns value to be cached.
    51  	Do func(*Item) (interface{}, error)
    52  
    53  	// SetXX only sets the key if it already exists.
    54  	SetXX bool
    55  
    56  	// SetNX only sets the key if it does not already exist.
    57  	SetNX bool
    58  
    59  	// SkipLocalCache skips local cache as if it is not set.
    60  	SkipLocalCache bool
    61  }
    62  
    63  func (item *Item) Context() context.Context {
    64  	if item.Ctx == nil {
    65  		return context.Background()
    66  	}
    67  	return item.Ctx
    68  }
    69  
    70  func (item *Item) value() (interface{}, error) {
    71  	if item.Do != nil {
    72  		return item.Do(item)
    73  	}
    74  	if item.Value != nil {
    75  		return item.Value, nil
    76  	}
    77  	return nil, nil
    78  }
    79  
    80  func (item *Item) ttl() time.Duration {
    81  	return item.TTL
    82  }
    83  
    84  //------------------------------------------------------------------------------
    85  type (
    86  	MarshalFunc   func(interface{}) ([]byte, error)
    87  	UnmarshalFunc func([]byte, interface{}) error
    88  )
    89  
    90  type Options struct {
    91  	Redis        rediser
    92  	LocalCache   LocalCache
    93  	StatsEnabled bool
    94  	Marshal      MarshalFunc
    95  	Unmarshal    UnmarshalFunc
    96  }
    97  
    98  type Cache struct {
    99  	opt *Options
   100  
   101  	group singleflight.Group
   102  
   103  	marshal   MarshalFunc
   104  	unmarshal UnmarshalFunc
   105  
   106  	hits   uint64
   107  	misses uint64
   108  }
   109  
   110  func New(opt *Options) *Cache {
   111  	cacher := &Cache{
   112  		opt: opt,
   113  	}
   114  
   115  	if opt.Marshal == nil {
   116  		cacher.marshal = cacher._marshal
   117  	} else {
   118  		cacher.marshal = opt.Marshal
   119  	}
   120  
   121  	if opt.Unmarshal == nil {
   122  		cacher.unmarshal = cacher._unmarshal
   123  	} else {
   124  		cacher.unmarshal = opt.Unmarshal
   125  	}
   126  	return cacher
   127  }
   128  
   129  // Set caches the item.
   130  func (cd *Cache) Set(item *Item) error {
   131  	_, _, err := cd.set(item)
   132  	return err
   133  }
   134  
   135  func (cd *Cache) set(item *Item) ([]byte, bool, error) {
   136  	value, err := item.value()
   137  	if err != nil {
   138  		return nil, false, err
   139  	}
   140  
   141  	b, err := cd.Marshal(value)
   142  	if err != nil {
   143  		return nil, false, err
   144  	}
   145  
   146  	if cd.opt.LocalCache != nil && !item.SkipLocalCache {
   147  		cd.opt.LocalCache.Set(item.Key, b)
   148  	}
   149  
   150  	if cd.opt.Redis == nil {
   151  		if cd.opt.LocalCache == nil {
   152  			return b, true, errRedisLocalCacheNil
   153  		}
   154  		return b, true, nil
   155  	}
   156  
   157  	ttl := item.ttl()
   158  
   159  	if item.SetXX {
   160  		return b, true, cd.opt.Redis.SetXX(item.Context(), item.Key, b, ttl).Err()
   161  	}
   162  	if item.SetNX {
   163  		return b, true, cd.opt.Redis.SetNX(item.Context(), item.Key, b, ttl).Err()
   164  	}
   165  	return b, true, cd.opt.Redis.Set(item.Context(), item.Key, b, ttl).Err()
   166  }
   167  
   168  // Exists reports whether value for the given key exists.
   169  func (cd *Cache) Exists(ctx context.Context, key string) bool {
   170  	_, err := cd.getBytes(ctx, key, false)
   171  	return err == nil
   172  }
   173  
   174  // Get gets the value for the given key.
   175  func (cd *Cache) Get(ctx context.Context, key string, value interface{}) error {
   176  	return cd.get(ctx, key, value, false)
   177  }
   178  
   179  // Get gets the value for the given key skipping local cache.
   180  func (cd *Cache) GetSkippingLocalCache(
   181  	ctx context.Context, key string, value interface{},
   182  ) error {
   183  	return cd.get(ctx, key, value, true)
   184  }
   185  
   186  func (cd *Cache) get(
   187  	ctx context.Context,
   188  	key string,
   189  	value interface{},
   190  	skipLocalCache bool,
   191  ) error {
   192  	b, err := cd.getBytes(ctx, key, skipLocalCache)
   193  	if err != nil {
   194  		return err
   195  	}
   196  	return cd.unmarshal(b, value)
   197  }
   198  
   199  func (cd *Cache) getBytes(ctx context.Context, key string, skipLocalCache bool) ([]byte, error) {
   200  	if !skipLocalCache && cd.opt.LocalCache != nil {
   201  		b, ok := cd.opt.LocalCache.Get(key)
   202  		if ok {
   203  			return b, nil
   204  		}
   205  	}
   206  
   207  	if cd.opt.Redis == nil {
   208  		if cd.opt.LocalCache == nil {
   209  			return nil, errRedisLocalCacheNil
   210  		}
   211  		return nil, ErrCacheMiss
   212  	}
   213  
   214  	b, err := cd.opt.Redis.Get(ctx, key).Bytes()
   215  	if err != nil {
   216  		if cd.opt.StatsEnabled {
   217  			atomic.AddUint64(&cd.misses, 1)
   218  		}
   219  		if err == redis.Nil {
   220  			return nil, ErrCacheMiss
   221  		}
   222  		return nil, err
   223  	}
   224  
   225  	if cd.opt.StatsEnabled {
   226  		atomic.AddUint64(&cd.hits, 1)
   227  	}
   228  
   229  	if !skipLocalCache && cd.opt.LocalCache != nil {
   230  		cd.opt.LocalCache.Set(key, b)
   231  	}
   232  	return b, nil
   233  }
   234  
   235  // Once gets the item.Value for the given item.Key from the cache or
   236  // executes, caches, and returns the results of the given item.Func,
   237  // making sure that only one execution is in-flight for a given item.Key
   238  // at a time. If a duplicate comes in, the duplicate caller waits for the
   239  // original to complete and receives the same results.
   240  func (cd *Cache) Once(item *Item) error {
   241  	b, cached, err := cd.getSetItemBytesOnce(item)
   242  	if err != nil {
   243  		return err
   244  	}
   245  
   246  	if item.Value == nil || len(b) == 0 {
   247  		return nil
   248  	}
   249  
   250  	if err := cd.unmarshal(b, item.Value); err != nil {
   251  		if cached {
   252  			_ = cd.Delete(item.Context(), item.Key)
   253  			return cd.Once(item)
   254  		}
   255  		return err
   256  	}
   257  
   258  	return nil
   259  }
   260  
   261  func (cd *Cache) getSetItemBytesOnce(item *Item) (b []byte, cached bool, err error) {
   262  	if cd.opt.LocalCache != nil {
   263  		b, ok := cd.opt.LocalCache.Get(item.Key)
   264  		if ok {
   265  			return b, true, nil
   266  		}
   267  	}
   268  
   269  	v, err, _ := cd.group.Do(item.Key, func() (interface{}, error) {
   270  		b, err := cd.getBytes(item.Context(), item.Key, item.SkipLocalCache)
   271  		if err == nil {
   272  			cached = true
   273  			return b, nil
   274  		}
   275  
   276  		b, ok, err := cd.set(item)
   277  		if ok {
   278  			return b, nil
   279  		}
   280  		return nil, err
   281  	})
   282  	if err != nil {
   283  		return nil, false, err
   284  	}
   285  	return v.([]byte), cached, nil
   286  }
   287  
   288  func (cd *Cache) Delete(ctx context.Context, key string) error {
   289  	if cd.opt.LocalCache != nil {
   290  		cd.opt.LocalCache.Del(key)
   291  	}
   292  
   293  	if cd.opt.Redis == nil {
   294  		if cd.opt.LocalCache == nil {
   295  			return errRedisLocalCacheNil
   296  		}
   297  		return nil
   298  	}
   299  
   300  	_, err := cd.opt.Redis.Del(ctx, key).Result()
   301  	return err
   302  }
   303  
   304  func (cd *Cache) DeleteFromLocalCache(key string) {
   305  	if cd.opt.LocalCache != nil {
   306  		cd.opt.LocalCache.Del(key)
   307  	}
   308  }
   309  
   310  func (cd *Cache) Marshal(value interface{}) ([]byte, error) {
   311  	return cd.marshal(value)
   312  }
   313  
   314  func (cd *Cache) _marshal(value interface{}) ([]byte, error) {
   315  	switch value := value.(type) {
   316  	case nil:
   317  		return nil, nil
   318  	case []byte:
   319  		return value, nil
   320  	case string:
   321  		return []byte(value), nil
   322  	}
   323  
   324  	b, err := msgpack.Marshal(value)
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  
   329  	return compress(b), nil
   330  }
   331  
   332  func compress(data []byte) []byte {
   333  	if len(data) < compressionThreshold {
   334  		n := len(data) + 1
   335  		b := make([]byte, n, n+timeLen)
   336  		copy(b, data)
   337  		b[len(b)-1] = noCompression
   338  		return b
   339  	}
   340  
   341  	n := s2.MaxEncodedLen(len(data)) + 1
   342  	b := make([]byte, n, n+timeLen)
   343  	b = s2.Encode(b, data)
   344  	b = append(b, s2Compression)
   345  	return b
   346  }
   347  
   348  func (cd *Cache) Unmarshal(b []byte, value interface{}) error {
   349  	return cd.unmarshal(b, value)
   350  }
   351  
   352  func (cd *Cache) _unmarshal(b []byte, value interface{}) error {
   353  	if len(b) == 0 {
   354  		return nil
   355  	}
   356  
   357  	switch value := value.(type) {
   358  	case nil:
   359  		return nil
   360  	case *[]byte:
   361  		clone := make([]byte, len(b))
   362  		copy(clone, b)
   363  		*value = clone
   364  		return nil
   365  	case *string:
   366  		*value = string(b)
   367  		return nil
   368  	}
   369  
   370  	switch c := b[len(b)-1]; c {
   371  	case noCompression:
   372  		b = b[:len(b)-1]
   373  	case s2Compression:
   374  		b = b[:len(b)-1]
   375  
   376  		var err error
   377  		b, err = s2.Decode(nil, b)
   378  		if err != nil {
   379  			return err
   380  		}
   381  	default:
   382  		return fmt.Errorf("unknown compression method: %x", c)
   383  	}
   384  
   385  	return msgpack.Unmarshal(b, value)
   386  }
   387  
   388  //------------------------------------------------------------------------------
   389  
   390  type Stats struct {
   391  	Hits   uint64
   392  	Misses uint64
   393  }
   394  
   395  // Stats returns cache statistics.
   396  func (cd *Cache) Stats() *Stats {
   397  	if !cd.opt.StatsEnabled {
   398  		return nil
   399  	}
   400  	return &Stats{
   401  		Hits:   atomic.LoadUint64(&cd.hits),
   402  		Misses: atomic.LoadUint64(&cd.misses),
   403  	}
   404  }