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 }