github.com/qiniu/go-sdk/v7@v7.27.0/internal/cache/cache.go (about)

     1  package cache
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"sort"
    12  	"sync"
    13  	"sync/atomic"
    14  	"time"
    15  
    16  	"github.com/gofrs/flock"
    17  	"golang.org/x/sync/singleflight"
    18  )
    19  
    20  type (
    21  	CacheValue interface {
    22  		IsEqual(CacheValue) bool
    23  		ShouldRefresh() bool
    24  		IsValid() bool
    25  	}
    26  
    27  	Cache struct {
    28  		compactInterval time.Duration
    29  		cacheMap        map[string]cacheValue
    30  		cacheMapMutex   sync.Mutex
    31  		lastCompactTime time.Time
    32  		persistentFile  *persistentFile
    33  		group           singleflight.Group
    34  		flushing        uint32
    35  	}
    36  
    37  	persistentFile struct {
    38  		valueType          reflect.Type
    39  		cacheFilePath      string
    40  		persistentDuration time.Duration
    41  		lastPersistentTime time.Time
    42  		handleError        func(error)
    43  	}
    44  
    45  	cacheValue struct {
    46  		Value     CacheValue `json:"value"`
    47  		CreatedAt time.Time  `json:"created_at"`
    48  	}
    49  
    50  	unmarshalledCacheEntry struct {
    51  		Key       string          `json:"key"`
    52  		Value     json.RawMessage `json:"value"`
    53  		CreatedAt time.Time       `json:"created_at"`
    54  	}
    55  )
    56  
    57  func NewCache(compactInterval time.Duration) *Cache {
    58  	return &Cache{
    59  		compactInterval: compactInterval,
    60  		cacheMap:        make(map[string]cacheValue),
    61  		lastCompactTime: time.Now(),
    62  	}
    63  }
    64  
    65  func NewPersistentCache(
    66  	valueType reflect.Type,
    67  	persistentFilePath string,
    68  	compactInterval time.Duration,
    69  	persistentDuration time.Duration,
    70  	handleError func(error),
    71  ) (*Cache, error) {
    72  	c := &Cache{
    73  		persistentFile: &persistentFile{
    74  			valueType:          valueType,
    75  			cacheFilePath:      persistentFilePath,
    76  			persistentDuration: persistentDuration,
    77  			lastPersistentTime: time.Now(),
    78  			handleError:        handleError,
    79  		},
    80  		cacheMap:        nil,
    81  		compactInterval: compactInterval,
    82  		lastCompactTime: time.Now(),
    83  	}
    84  	// 为了兼容上层接口,此处允许加载本地缓存文件失败
    85  	_ = c.loadLocalCache()
    86  	return c, nil
    87  }
    88  
    89  type GetResult uint8
    90  
    91  const (
    92  	GetResultFromCache                GetResult = 0
    93  	GetResultFromCacheAndRefreshAsync GetResult = 1
    94  	GetResultFromFallback             GetResult = 2
    95  	GetResultFromInvalidCache         GetResult = 3
    96  	NoResultGot                       GetResult = 4
    97  )
    98  
    99  func (cache *Cache) loadLocalCache() error {
   100  	defer func() {
   101  		if cache.cacheMap == nil {
   102  			cache.cacheMap = make(map[string]cacheValue)
   103  		}
   104  	}()
   105  
   106  	persistentFilePath := cache.persistentFile.cacheFilePath
   107  	err := os.MkdirAll(filepath.Dir(persistentFilePath), 0700)
   108  	if err != nil {
   109  		return err
   110  	}
   111  	unlockFunc, err := lockCachePersistentFile(persistentFilePath, false, cache.persistentFile.handleError)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	defer unlockFunc()
   116  
   117  	file, closeFunc, err := openCachePersistentFile(persistentFilePath, cache.persistentFile.handleError)
   118  	if err != nil {
   119  		return err
   120  	}
   121  	defer closeFunc()
   122  
   123  	cacheMap, err := loadCacheMapFrom(cache.persistentFile.valueType, file)
   124  	if err != nil {
   125  		return err
   126  	}
   127  
   128  	cache.cacheMap = cacheMap
   129  	return nil
   130  }
   131  
   132  func (cache *Cache) Get(key string, fallback func() (CacheValue, error)) (CacheValue, GetResult) {
   133  	cache.cacheMapMutex.Lock()
   134  	value, ok := cache.cacheMap[key]
   135  	cache.cacheMapMutex.Unlock()
   136  
   137  	defer func() {
   138  		go cache.flush()
   139  	}()
   140  
   141  	if ok && value.Value.IsValid() {
   142  		if value.Value.ShouldRefresh() {
   143  			cache.doFallbackAsync(key, fallback)
   144  			return value.Value, GetResultFromCacheAndRefreshAsync
   145  		} else {
   146  			return value.Value, GetResultFromCache
   147  		}
   148  	}
   149  
   150  	newValue, err := cache.doFallback(key, fallback)
   151  	if err != nil {
   152  		if ok {
   153  			return value.Value, GetResultFromInvalidCache
   154  		} else {
   155  			return nil, NoResultGot
   156  		}
   157  	}
   158  	cache.set(key, newValue, false)
   159  	return newValue, GetResultFromFallback
   160  }
   161  
   162  func (cache *Cache) doFallback(key string, fallback func() (CacheValue, error)) (CacheValue, error) {
   163  	newValue, err, _ := cache.group.Do(key, func() (interface{}, error) { return fallback() })
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  	return newValue.(CacheValue), nil
   168  }
   169  
   170  func (cache *Cache) doFallbackAsync(key string, fallback func() (CacheValue, error)) {
   171  	go func() {
   172  		newValue, err := cache.doFallback(key, fallback)
   173  		if err == nil {
   174  			cache.Set(key, newValue)
   175  		}
   176  	}()
   177  }
   178  
   179  func (cache *Cache) Set(key string, value CacheValue) {
   180  	cache.set(key, value, true)
   181  }
   182  
   183  func (cache *Cache) set(key string, value CacheValue, willFlushAsync bool) {
   184  	cache.checkType(value)
   185  	if !value.IsValid() {
   186  		return
   187  	}
   188  
   189  	now := time.Now()
   190  	cache.cacheMapMutex.Lock()
   191  	cache.cacheMap[key] = cacheValue{Value: value, CreatedAt: now}
   192  	cache.cacheMapMutex.Unlock()
   193  
   194  	if willFlushAsync {
   195  		go cache.flush()
   196  	}
   197  }
   198  
   199  func (cache *Cache) checkType(cacheValue CacheValue) {
   200  	if pf := cache.persistentFile; pf != nil {
   201  		if cacheValueType := reflect.TypeOf(cacheValue); !cacheValueType.AssignableTo(pf.valueType) {
   202  			panic(fmt.Sprintf("cannot assign %s to %s", cacheValueType, pf.valueType))
   203  		}
   204  	}
   205  }
   206  
   207  func (cache *Cache) flush() {
   208  	if !atomic.CompareAndSwapUint32(&cache.flushing, 0, 1) {
   209  		return
   210  	}
   211  	defer atomic.StoreUint32(&cache.flushing, 0)
   212  
   213  	if cache.lastCompactTime.Add(cache.compactInterval).Before(time.Now()) {
   214  		cache.doCompact()
   215  		cache.lastCompactTime = time.Now()
   216  	}
   217  
   218  	if pf := cache.persistentFile; pf != nil {
   219  		if pf.lastPersistentTime.Add(pf.persistentDuration).Before(time.Now()) {
   220  			cache.doPersistent()
   221  			pf.lastPersistentTime = time.Now()
   222  		}
   223  	}
   224  }
   225  
   226  func (cache *Cache) doCompact() {
   227  	cache.cacheMapMutex.Lock()
   228  	defer cache.cacheMapMutex.Unlock()
   229  
   230  	var toDeleted []string
   231  	for key, value := range cache.cacheMap {
   232  		if !value.Value.IsValid() {
   233  			toDeleted = append(toDeleted, key)
   234  		}
   235  	}
   236  	for _, toDeletedKey := range toDeleted {
   237  		delete(cache.cacheMap, toDeletedKey)
   238  	}
   239  }
   240  
   241  func (cache *Cache) doPersistent() {
   242  	var (
   243  		cacheFilePath = cache.persistentFile.cacheFilePath
   244  		handleError   = cache.persistentFile.handleError
   245  		valueType     = cache.persistentFile.valueType
   246  	)
   247  
   248  	unlockFunc, err := lockCachePersistentFile(cacheFilePath, true, handleError)
   249  	if err != nil {
   250  		return
   251  	}
   252  	defer unlockFunc()
   253  
   254  	cache.cacheMapMutex.Lock()
   255  	defer cache.cacheMapMutex.Unlock()
   256  
   257  	file, closeFunc, err := openCachePersistentFile(cacheFilePath, handleError)
   258  	if err != nil {
   259  		return
   260  	}
   261  	defer closeFunc()
   262  
   263  	newCacheMap, err := loadCacheMapFrom(valueType, file)
   264  	if err != nil {
   265  		if handleError != nil {
   266  			handleError(err)
   267  		}
   268  		return
   269  	}
   270  	if isCacheMapEqual(cache.cacheMap, newCacheMap) {
   271  		return
   272  	}
   273  	mergeCacheMap(cache.cacheMap, newCacheMap)
   274  
   275  	if _, err = file.Seek(0, io.SeekStart); err != nil {
   276  		if handleError != nil {
   277  			handleError(err)
   278  		}
   279  		return
   280  	}
   281  	if err = file.Truncate(0); err != nil {
   282  		if handleError != nil {
   283  			handleError(err)
   284  		}
   285  		return
   286  	}
   287  	if err = saveCacheMapTo(file, cache.cacheMap); err != nil && handleError != nil {
   288  		handleError(err)
   289  	}
   290  }
   291  
   292  func loadCacheMapFrom(valueType reflect.Type, r io.Reader) (map[string]cacheValue, error) {
   293  	decoder := json.NewDecoder(r)
   294  	cacheMap := make(map[string]cacheValue)
   295  	for decoder.More() {
   296  		var entry unmarshalledCacheEntry
   297  		if err := decoder.Decode(&entry); err != nil {
   298  			return nil, err
   299  		}
   300  		ptrValue := reflect.New(valueType)
   301  		if err := json.Unmarshal(entry.Value, ptrValue.Interface()); err != nil {
   302  			return nil, err
   303  		}
   304  		value := ptrValue.Elem().Interface().(CacheValue)
   305  		if value.IsValid() {
   306  			cacheMap[entry.Key] = cacheValue{Value: value, CreatedAt: entry.CreatedAt}
   307  		}
   308  	}
   309  	return cacheMap, nil
   310  }
   311  
   312  func isCacheMapEqual(left, right map[string]cacheValue) bool {
   313  	if len(left) != len(right) {
   314  		return false
   315  	}
   316  	leftKeys := sort.StringSlice(getCacheMapKeys(left))
   317  	leftKeys.Sort()
   318  	rightKeys := sort.StringSlice(getCacheMapKeys(right))
   319  	rightKeys.Sort()
   320  	if !reflect.DeepEqual(leftKeys, rightKeys) {
   321  		return false
   322  	}
   323  	for _, key := range leftKeys {
   324  		leftValue := left[key]
   325  		rightValue := right[key]
   326  		if !leftValue.Value.IsEqual(rightValue.Value) {
   327  			return false
   328  		}
   329  	}
   330  	return true
   331  }
   332  
   333  func getCacheMapKeys(m map[string]cacheValue) []string {
   334  	keys := make([]string, 0, len(m))
   335  	for k := range m {
   336  		keys = append(keys, k)
   337  	}
   338  	return keys
   339  }
   340  
   341  func mergeCacheMap(left, right map[string]cacheValue) {
   342  	for newKey, newValue := range right {
   343  		existedCacheValue, exists := left[newKey]
   344  		if exists && existedCacheValue.CreatedAt.Before(newValue.CreatedAt) || !exists {
   345  			left[newKey] = newValue
   346  		}
   347  	}
   348  }
   349  
   350  func saveCacheMapTo(w io.Writer, m map[string]cacheValue) error {
   351  	encoder := json.NewEncoder(w)
   352  	for k, v := range m {
   353  		rawMessage, err := json.Marshal(v.Value)
   354  		if err != nil {
   355  			return err
   356  		}
   357  		if err = encoder.Encode(unmarshalledCacheEntry{
   358  			Key:       k,
   359  			Value:     rawMessage,
   360  			CreatedAt: v.CreatedAt,
   361  		}); err != nil {
   362  			return err
   363  		}
   364  	}
   365  	return nil
   366  }
   367  
   368  func lockCachePersistentFile(cacheFilePath string, ex bool, handleError func(error)) (context.CancelFunc, error) {
   369  	var (
   370  		lockFilePath = cacheFilePath + ".lock"
   371  		lockFile     = flock.New(lockFilePath)
   372  		err          error
   373  	)
   374  	if ex {
   375  		err = lockFile.Lock()
   376  	} else {
   377  		err = lockFile.RLock()
   378  	}
   379  	if err != nil {
   380  		if handleError != nil {
   381  			handleError(err)
   382  		}
   383  		return nil, err
   384  	}
   385  	return func() {
   386  		if err := lockFile.Unlock(); err != nil && handleError != nil {
   387  			handleError(err)
   388  		}
   389  	}, nil
   390  }
   391  
   392  func openCachePersistentFile(cacheFile string, handleError func(error)) (*os.File, context.CancelFunc, error) {
   393  	file, err := os.OpenFile(cacheFile, os.O_CREATE|os.O_RDWR, 0600)
   394  	if err != nil {
   395  		if handleError != nil {
   396  			handleError(err)
   397  		}
   398  		return nil, nil, err
   399  	}
   400  	return file, func() {
   401  		if err := file.Close(); err != nil && handleError != nil {
   402  			handleError(err)
   403  		}
   404  	}, nil
   405  }