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 }