github.com/shuguocloud/go-zero@v1.3.0/core/stores/cache/cachenode.go (about)

     1  package cache
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math/rand"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/shuguocloud/go-zero/core/jsonx"
    11  	"github.com/shuguocloud/go-zero/core/logx"
    12  	"github.com/shuguocloud/go-zero/core/mathx"
    13  	"github.com/shuguocloud/go-zero/core/stat"
    14  	"github.com/shuguocloud/go-zero/core/stores/redis"
    15  	"github.com/shuguocloud/go-zero/core/syncx"
    16  )
    17  
    18  const (
    19  	notFoundPlaceholder = "*"
    20  	// make the expiry unstable to avoid lots of cached items expire at the same time
    21  	// make the unstable expiry to be [0.95, 1.05] * seconds
    22  	expiryDeviation = 0.05
    23  )
    24  
    25  // indicates there is no such value associate with the key
    26  var errPlaceholder = errors.New("placeholder")
    27  
    28  type cacheNode struct {
    29  	rds            *redis.Redis
    30  	expiry         time.Duration
    31  	notFoundExpiry time.Duration
    32  	barrier        syncx.SingleFlight
    33  	r              *rand.Rand
    34  	lock           *sync.Mutex
    35  	unstableExpiry mathx.Unstable
    36  	stat           *Stat
    37  	errNotFound    error
    38  }
    39  
    40  // NewNode returns a cacheNode.
    41  // rds is the underlying redis node or cluster.
    42  // barrier is the barrier that maybe shared with other cache nodes on cache cluster.
    43  // st is used to stat the cache.
    44  // errNotFound defines the error that returned on cache not found.
    45  // opts are the options that customize the cacheNode.
    46  func NewNode(rds *redis.Redis, barrier syncx.SingleFlight, st *Stat,
    47  	errNotFound error, opts ...Option) Cache {
    48  	o := newOptions(opts...)
    49  	return cacheNode{
    50  		rds:            rds,
    51  		expiry:         o.Expiry,
    52  		notFoundExpiry: o.NotFoundExpiry,
    53  		barrier:        barrier,
    54  		r:              rand.New(rand.NewSource(time.Now().UnixNano())),
    55  		lock:           new(sync.Mutex),
    56  		unstableExpiry: mathx.NewUnstable(expiryDeviation),
    57  		stat:           st,
    58  		errNotFound:    errNotFound,
    59  	}
    60  }
    61  
    62  // Del deletes cached values with keys.
    63  func (c cacheNode) Del(keys ...string) error {
    64  	if len(keys) == 0 {
    65  		return nil
    66  	}
    67  
    68  	if len(keys) > 1 && c.rds.Type == redis.ClusterType {
    69  		for _, key := range keys {
    70  			if _, err := c.rds.Del(key); err != nil {
    71  				logx.Errorf("failed to clear cache with key: %q, error: %v", key, err)
    72  				c.asyncRetryDelCache(key)
    73  			}
    74  		}
    75  	} else {
    76  		if _, err := c.rds.Del(keys...); err != nil {
    77  			logx.Errorf("failed to clear cache with keys: %q, error: %v", formatKeys(keys), err)
    78  			c.asyncRetryDelCache(keys...)
    79  		}
    80  	}
    81  
    82  	return nil
    83  }
    84  
    85  // Get gets the cache with key and fills into v.
    86  func (c cacheNode) Get(key string, v interface{}) error {
    87  	err := c.doGetCache(key, v)
    88  	if err == errPlaceholder {
    89  		return c.errNotFound
    90  	}
    91  
    92  	return err
    93  }
    94  
    95  // IsNotFound checks if the given error is the defined errNotFound.
    96  func (c cacheNode) IsNotFound(err error) bool {
    97  	return err == c.errNotFound
    98  }
    99  
   100  // Set sets the cache with key and v, using c.expiry.
   101  func (c cacheNode) Set(key string, v interface{}) error {
   102  	return c.SetWithExpire(key, v, c.aroundDuration(c.expiry))
   103  }
   104  
   105  // SetWithExpire sets the cache with key and v, using given expire.
   106  func (c cacheNode) SetWithExpire(key string, v interface{}, expire time.Duration) error {
   107  	data, err := jsonx.Marshal(v)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	return c.rds.Setex(key, string(data), int(expire.Seconds()))
   113  }
   114  
   115  // String returns a string that represents the cacheNode.
   116  func (c cacheNode) String() string {
   117  	return c.rds.Addr
   118  }
   119  
   120  // Take takes the result from cache first, if not found,
   121  // query from DB and set cache using c.expiry, then return the result.
   122  func (c cacheNode) Take(v interface{}, key string, query func(v interface{}) error) error {
   123  	return c.doTake(v, key, query, func(v interface{}) error {
   124  		return c.Set(key, v)
   125  	})
   126  }
   127  
   128  // TakeWithExpire takes the result from cache first, if not found,
   129  // query from DB and set cache using given expire, then return the result.
   130  func (c cacheNode) TakeWithExpire(v interface{}, key string, query func(v interface{},
   131  	expire time.Duration) error) error {
   132  	expire := c.aroundDuration(c.expiry)
   133  	return c.doTake(v, key, func(v interface{}) error {
   134  		return query(v, expire)
   135  	}, func(v interface{}) error {
   136  		return c.SetWithExpire(key, v, expire)
   137  	})
   138  }
   139  
   140  func (c cacheNode) aroundDuration(duration time.Duration) time.Duration {
   141  	return c.unstableExpiry.AroundDuration(duration)
   142  }
   143  
   144  func (c cacheNode) asyncRetryDelCache(keys ...string) {
   145  	AddCleanTask(func() error {
   146  		_, err := c.rds.Del(keys...)
   147  		return err
   148  	}, keys...)
   149  }
   150  
   151  func (c cacheNode) doGetCache(key string, v interface{}) error {
   152  	c.stat.IncrementTotal()
   153  	data, err := c.rds.Get(key)
   154  	if err != nil {
   155  		c.stat.IncrementMiss()
   156  		return err
   157  	}
   158  
   159  	if len(data) == 0 {
   160  		c.stat.IncrementMiss()
   161  		return c.errNotFound
   162  	}
   163  
   164  	c.stat.IncrementHit()
   165  	if data == notFoundPlaceholder {
   166  		return errPlaceholder
   167  	}
   168  
   169  	return c.processCache(key, data, v)
   170  }
   171  
   172  func (c cacheNode) doTake(v interface{}, key string, query func(v interface{}) error,
   173  	cacheVal func(v interface{}) error) error {
   174  	val, fresh, err := c.barrier.DoEx(key, func() (interface{}, error) {
   175  		if err := c.doGetCache(key, v); err != nil {
   176  			if err == errPlaceholder {
   177  				return nil, c.errNotFound
   178  			} else if err != c.errNotFound {
   179  				// why we just return the error instead of query from db,
   180  				// because we don't allow the disaster pass to the dbs.
   181  				// fail fast, in case we bring down the dbs.
   182  				return nil, err
   183  			}
   184  
   185  			if err = query(v); err == c.errNotFound {
   186  				if err = c.setCacheWithNotFound(key); err != nil {
   187  					logx.Error(err)
   188  				}
   189  
   190  				return nil, c.errNotFound
   191  			} else if err != nil {
   192  				c.stat.IncrementDbFails()
   193  				return nil, err
   194  			}
   195  
   196  			if err = cacheVal(v); err != nil {
   197  				logx.Error(err)
   198  			}
   199  		}
   200  
   201  		return jsonx.Marshal(v)
   202  	})
   203  	if err != nil {
   204  		return err
   205  	}
   206  	if fresh {
   207  		return nil
   208  	}
   209  
   210  	// got the result from previous ongoing query
   211  	c.stat.IncrementTotal()
   212  	c.stat.IncrementHit()
   213  
   214  	return jsonx.Unmarshal(val.([]byte), v)
   215  }
   216  
   217  func (c cacheNode) processCache(key, data string, v interface{}) error {
   218  	err := jsonx.Unmarshal([]byte(data), v)
   219  	if err == nil {
   220  		return nil
   221  	}
   222  
   223  	report := fmt.Sprintf("unmarshal cache, node: %s, key: %s, value: %s, error: %v",
   224  		c.rds.Addr, key, data, err)
   225  	logx.Error(report)
   226  	stat.Report(report)
   227  	if _, e := c.rds.Del(key); e != nil {
   228  		logx.Errorf("delete invalid cache, node: %s, key: %s, value: %s, error: %v",
   229  			c.rds.Addr, key, data, e)
   230  	}
   231  
   232  	// returns errNotFound to reload the value by the given queryFn
   233  	return c.errNotFound
   234  }
   235  
   236  func (c cacheNode) setCacheWithNotFound(key string) error {
   237  	return c.rds.Setex(key, notFoundPlaceholder, int(c.aroundDuration(c.notFoundExpiry).Seconds()))
   238  }