v1.3.1 add UseExpiredCache feature
This commit is contained in:
		
							parent
							
								
									0db6e87b75
								
							
						
					
					
						commit
						4ce4df224c
					
				
							
								
								
									
										105
									
								
								cache.go
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								cache.go
									
									
									
									
									
								
							@ -4,6 +4,7 @@ import (
 | 
				
			|||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gomodule/redigo/redis"
 | 
						"github.com/gomodule/redigo/redis"
 | 
				
			||||||
@ -11,6 +12,7 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
 | 
						ErrExpired        = errors.New("expired")
 | 
				
			||||||
	ErrNotFound       = errors.New("not found")
 | 
						ErrNotFound       = errors.New("not found")
 | 
				
			||||||
	OptWithoutGetter  = optWithoutGetter{}  // for get only
 | 
						OptWithoutGetter  = optWithoutGetter{}  // for get only
 | 
				
			||||||
	OptWithCreateTime = optWithCreateTime{} // for set & get
 | 
						OptWithCreateTime = optWithCreateTime{} // for set & get
 | 
				
			||||||
@ -26,6 +28,13 @@ type Getter[T any] interface {
 | 
				
			|||||||
	GetById(id string) (dat *T, err error)
 | 
						GetById(id string) (dat *T, err error)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type cacher[T any] struct {
 | 
				
			||||||
 | 
						cfg        Config
 | 
				
			||||||
 | 
						getter     Getter[T]
 | 
				
			||||||
 | 
						localCache *localCacher[T]
 | 
				
			||||||
 | 
						redisCache *redisCacher[T]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type (
 | 
					type (
 | 
				
			||||||
	Option            any
 | 
						Option            any
 | 
				
			||||||
	optWithoutGetter  struct{}                        // for get only
 | 
						optWithoutGetter  struct{}                        // for get only
 | 
				
			||||||
@ -42,12 +51,13 @@ type Config struct {
 | 
				
			|||||||
	UseLocalCache            bool        // use localcache or not
 | 
						UseLocalCache            bool        // use localcache or not
 | 
				
			||||||
	LocalCacheLifetimeSecond int64       // local cache lifetime(t<0: forever; t=0: default; t>0: seconds)
 | 
						LocalCacheLifetimeSecond int64       // local cache lifetime(t<0: forever; t=0: default; t>0: seconds)
 | 
				
			||||||
	UseRedisCache            bool        // use redis or not
 | 
						UseRedisCache            bool        // use redis or not
 | 
				
			||||||
	RedisCacheConn           redis.Conn  // redis.Conn
 | 
						RedisCacheConn           redis.Conn  `json:"-"` // redis.Conn
 | 
				
			||||||
	RedisCacheConnPool       *redis.Pool // redis.Pool
 | 
						RedisCacheConnPool       *redis.Pool `json:"-"` // redis.Pool
 | 
				
			||||||
	RedisCacheKeyPrefix      string      // redis key prefix
 | 
						RedisCacheKeyPrefix      string      // redis key prefix
 | 
				
			||||||
	RedisCacheLifetimeSecond int64       // redis cache lifetime(t<0: forever; t=0: default; t>0: seconds)
 | 
						RedisCacheLifetimeSecond int64       // redis cache lifetime(t<0: forever; t=0: default; t>0: seconds)
 | 
				
			||||||
	UseGetter                bool        // not used
 | 
						UseGetter                bool        // not used
 | 
				
			||||||
	GetterNoWarning          bool        // no warning if no getter
 | 
						GetterNoWarning          bool        // no warning if no getter
 | 
				
			||||||
 | 
						UseExpiredCache          *bool       // use expired cache or not (true: &[]bool{true}[0], false: &[]bool{true}[0])
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewCache[T any](getter Getter[T], cfg Config) Cacher[T] {
 | 
					func NewCache[T any](getter Getter[T], cfg Config) Cacher[T] {
 | 
				
			||||||
@ -59,6 +69,7 @@ func NewCache[T any](getter Getter[T], cfg Config) Cacher[T] {
 | 
				
			|||||||
	// Local cache
 | 
						// Local cache
 | 
				
			||||||
	if cfg.UseLocalCache {
 | 
						if cfg.UseLocalCache {
 | 
				
			||||||
		c.localCache = new(localCacher[T])
 | 
							c.localCache = new(localCacher[T])
 | 
				
			||||||
 | 
							c.localCache.cacher = c
 | 
				
			||||||
		c.localCache.cacheItems = map[string]*cacheItem[T]{}
 | 
							c.localCache.cacheItems = map[string]*cacheItem[T]{}
 | 
				
			||||||
		c.localCache.cacheDuration = time.Duration(cfg.LocalCacheLifetimeSecond) * time.Second
 | 
							c.localCache.cacheDuration = time.Duration(cfg.LocalCacheLifetimeSecond) * time.Second
 | 
				
			||||||
		if c.localCache.cacheDuration.Nanoseconds() == 0 {
 | 
							if c.localCache.cacheDuration.Nanoseconds() == 0 {
 | 
				
			||||||
@ -72,6 +83,7 @@ func NewCache[T any](getter Getter[T], cfg Config) Cacher[T] {
 | 
				
			|||||||
			panic("redis cache's key prefix must not be null")
 | 
								panic("redis cache's key prefix must not be null")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		c.redisCache = new(redisCacher[T])
 | 
							c.redisCache = new(redisCacher[T])
 | 
				
			||||||
 | 
							c.redisCache.cacher = c
 | 
				
			||||||
		c.redisCache.rds = cfg.RedisCacheConn
 | 
							c.redisCache.rds = cfg.RedisCacheConn
 | 
				
			||||||
		c.redisCache.rdsPool = cfg.RedisCacheConnPool
 | 
							c.redisCache.rdsPool = cfg.RedisCacheConnPool
 | 
				
			||||||
		c.redisCache.cacheDuration = time.Duration(cfg.RedisCacheLifetimeSecond) * time.Second
 | 
							c.redisCache.cacheDuration = time.Duration(cfg.RedisCacheLifetimeSecond) * time.Second
 | 
				
			||||||
@ -83,6 +95,13 @@ func NewCache[T any](getter Getter[T], cfg Config) Cacher[T] {
 | 
				
			|||||||
		panic("want to user redis cache, but redis.Conn is nil")
 | 
							panic("want to user redis cache, but redis.Conn is nil")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//UseExpiredCache
 | 
				
			||||||
 | 
						if cfg.UseExpiredCache == nil {
 | 
				
			||||||
 | 
							cfg.UseExpiredCache = &[]bool{true}[0]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						name := reflect.TypeOf(*new(T)).String()
 | 
				
			||||||
 | 
						log.PrintPretty("new cache '"+name+"' by config:", cfg)
 | 
				
			||||||
	return c
 | 
						return c
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -140,45 +159,55 @@ type cacheItem[T any] struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type localCacher[T any] struct {
 | 
					type localCacher[T any] struct {
 | 
				
			||||||
 | 
						cacher        *cacher[T]
 | 
				
			||||||
	cacheItems    map[string]*cacheItem[T]
 | 
						cacheItems    map[string]*cacheItem[T]
 | 
				
			||||||
	cacheDuration time.Duration
 | 
						cacheDuration time.Duration
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type redisCacher[T any] struct {
 | 
					type redisCacher[T any] struct {
 | 
				
			||||||
	rds           redis.Conn
 | 
						cacher        *cacher[T]
 | 
				
			||||||
	rdsPool       *redis.Pool
 | 
					 | 
				
			||||||
	cachePrefix   string
 | 
						cachePrefix   string
 | 
				
			||||||
	cacheDuration time.Duration
 | 
						cacheDuration time.Duration
 | 
				
			||||||
}
 | 
						rds           redis.Conn
 | 
				
			||||||
 | 
						rdsPool       *redis.Pool
 | 
				
			||||||
type cacher[T any] struct {
 | 
					 | 
				
			||||||
	cfg        Config
 | 
					 | 
				
			||||||
	getter     Getter[T]
 | 
					 | 
				
			||||||
	localCache *localCacher[T]
 | 
					 | 
				
			||||||
	redisCache *redisCacher[T]
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *localCacher[T]) GetFromCache(id string, options ...Option) (t *T, err error) {
 | 
					func (c *localCacher[T]) GetFromCache(id string, options ...Option) (t *T, err error) {
 | 
				
			||||||
 | 
						// Step 1. get from map
 | 
				
			||||||
	var a, ok = c.cacheItems[id]
 | 
						var a, ok = c.cacheItems[id]
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return nil, ErrNotFound
 | 
							return nil, ErrNotFound
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Step 2. check is expired or not
 | 
				
			||||||
	if c.cacheDuration.Nanoseconds() > 0 {
 | 
						if c.cacheDuration.Nanoseconds() > 0 {
 | 
				
			||||||
		var earliestCreateTime = time.Now().Add(-c.cacheDuration)
 | 
							var earliestCreateTime = time.Now().Add(-c.cacheDuration)
 | 
				
			||||||
		if a.CreateTime.Before(earliestCreateTime) {
 | 
							if a.CreateTime.Before(earliestCreateTime) {
 | 
				
			||||||
			log.Infof("cache(%s) is in local cache but expired", id)
 | 
								if *c.cacher.cfg.UseExpiredCache {
 | 
				
			||||||
			//TODO: cocurrent
 | 
									log.Infof("cache(%s) in local is expired, "+
 | 
				
			||||||
			delete(c.cacheItems, id)
 | 
										"we will use it when all other caches are missing", id)
 | 
				
			||||||
			return nil, ErrNotFound
 | 
									err = ErrExpired
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									log.Infof("cache(%s) is in local cache but expired, we delete it", id)
 | 
				
			||||||
 | 
									//TODO: cocurrent
 | 
				
			||||||
 | 
									delete(c.cacheItems, id)
 | 
				
			||||||
 | 
									return nil, ErrNotFound
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Step 3. get cache creattime if need
 | 
				
			||||||
	var opt = optionParser(options...)
 | 
						var opt = optionParser(options...)
 | 
				
			||||||
	if isOptWithCreateTimeForGetter(opt) {
 | 
						if isOptWithCreateTimeForGetter(opt) {
 | 
				
			||||||
		*opt.withCreateTime.createtime = a.CreateTime
 | 
							*opt.withCreateTime.createtime = a.CreateTime
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Step 4. return
 | 
				
			||||||
 | 
						if err == ErrExpired {
 | 
				
			||||||
 | 
							return a.Data, ErrExpired
 | 
				
			||||||
 | 
						} else if err != nil {
 | 
				
			||||||
 | 
							panic("unreachable code")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return a.Data, nil
 | 
						return a.Data, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -228,8 +257,14 @@ func (c *redisCacher[T]) GetFromCache(id string, options ...Option) (*T, error)
 | 
				
			|||||||
	if c.cacheDuration.Nanoseconds() > 0 {
 | 
						if c.cacheDuration.Nanoseconds() > 0 {
 | 
				
			||||||
		var earliestCreateTime = time.Now().Add(-c.cacheDuration)
 | 
							var earliestCreateTime = time.Now().Add(-c.cacheDuration)
 | 
				
			||||||
		if a.CreateTime.Before(earliestCreateTime) {
 | 
							if a.CreateTime.Before(earliestCreateTime) {
 | 
				
			||||||
			log.Infof("app(%s) is in redis cache but expired", id)
 | 
								if *c.cacher.cfg.UseExpiredCache {
 | 
				
			||||||
			return nil, ErrNotFound
 | 
									log.Infof("cache(%s) in redis is expired, "+
 | 
				
			||||||
 | 
										"we will use it when all other caches are missing", id)
 | 
				
			||||||
 | 
									err = ErrExpired
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									log.Infof("app(%s) is in redis cache but expired", id)
 | 
				
			||||||
 | 
									return nil, ErrNotFound
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -238,6 +273,12 @@ func (c *redisCacher[T]) GetFromCache(id string, options ...Option) (*T, error)
 | 
				
			|||||||
		*opt.withCreateTime.createtime = a.CreateTime
 | 
							*opt.withCreateTime.createtime = a.CreateTime
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Step 5. return
 | 
				
			||||||
 | 
						if err == ErrExpired {
 | 
				
			||||||
 | 
							return a.Data, ErrExpired
 | 
				
			||||||
 | 
						} else if err != nil {
 | 
				
			||||||
 | 
							panic("unreachable code")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return a.Data, nil
 | 
						return a.Data, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -287,13 +328,18 @@ func (c *cacher[T]) GetFromCache(id string, options ...Option) (dat *T, err erro
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var opt = optionParser(options...)
 | 
						var opt = optionParser(options...)
 | 
				
			||||||
 | 
						var lastExpiredDat *T = nil
 | 
				
			||||||
	// Step 2. get from [local]
 | 
						// Step 2. get from [local]
 | 
				
			||||||
	if c.localCache != nil {
 | 
						if c.localCache != nil {
 | 
				
			||||||
		if dat, err := c.localCache.GetFromCache(id, options...); err == nil {
 | 
							if dat, err := c.localCache.GetFromCache(id, options...); err == nil {
 | 
				
			||||||
			log.Infof("get cache(id:%s) from localCacher success", id)
 | 
								log.Infof("get cache(id:%s) from localCacher success", id)
 | 
				
			||||||
			return dat, nil
 | 
								return dat, nil
 | 
				
			||||||
 | 
							} else if err == ErrExpired {
 | 
				
			||||||
 | 
								log.Infof("get cache(id:%s) from localCacher success(but expired), need try next", id)
 | 
				
			||||||
 | 
								lastExpiredDat = dat
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								log.Infof("get cache(id:%s) from localCacher failed, need try next", id)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		log.Infof("get cache(id:%s) from localCacher failed, try next", id)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Step 3. get from [redis]
 | 
						// Step 3. get from [redis]
 | 
				
			||||||
@ -308,16 +354,28 @@ func (c *cacher[T]) GetFromCache(id string, options ...Option) (dat *T, err erro
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
			log.Infof("get cache(id:%s) from redisCacher success", id)
 | 
								log.Infof("get cache(id:%s) from redisCacher success", id)
 | 
				
			||||||
			return dat, nil
 | 
								return dat, nil
 | 
				
			||||||
 | 
							} else if err == ErrExpired {
 | 
				
			||||||
 | 
								if c.localCache != nil {
 | 
				
			||||||
 | 
									c.localCache.SetIntoCache(id, dat, options...) //set create time from redis
 | 
				
			||||||
 | 
									log.Infof("set cache(id:%s) to localCache by redisCacher done", id)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								log.Infof("get cache(id:%s) from redisCacher success(but expired), need try next", id)
 | 
				
			||||||
 | 
								lastExpiredDat = dat
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								log.Infof("get cache(id:%s) from redisCacher failed, need try next", id)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		log.Infof("get cache(id:%s) from redisCacher failed, try next", id)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var getter = c.getter
 | 
						var getter = c.getter
 | 
				
			||||||
	if opt.withoutGetter != nil {
 | 
						if opt.withoutGetter != nil {
 | 
				
			||||||
		log.Infof("all cache failed, and option 'withnogetter' is set, return ErrNotFound")
 | 
							log.Infof("all cache failed, and option 'withoutgetter' is set, return ErrNotFound")
 | 
				
			||||||
		return nil, ErrNotFound
 | 
							return nil, ErrNotFound
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if getter == nil {
 | 
					
 | 
				
			||||||
 | 
						if getter == nil && lastExpiredDat != nil {
 | 
				
			||||||
 | 
							log.Infof("all cache failed(and getter is nil) but expired cache is avaliable, we use it")
 | 
				
			||||||
 | 
							return lastExpiredDat, nil
 | 
				
			||||||
 | 
						} else if getter == nil {
 | 
				
			||||||
		log.Infof("all cache failed, and getter is nil, return ErrNotFound")
 | 
							log.Infof("all cache failed, and getter is nil, return ErrNotFound")
 | 
				
			||||||
		if c.cfg.GetterNoWarning == false {
 | 
							if c.cfg.GetterNoWarning == false {
 | 
				
			||||||
			log.Warningf("cache 'getter' is nil, did you save right config in database?")
 | 
								log.Warningf("cache 'getter' is nil, did you save right config in database?")
 | 
				
			||||||
@ -327,7 +385,10 @@ func (c *cacher[T]) GetFromCache(id string, options ...Option) (dat *T, err erro
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Step 4. get from [storager(database or somewhere)]
 | 
						// Step 4. get from [storager(database or somewhere)]
 | 
				
			||||||
	dat, err = getter.GetById(id)
 | 
						dat, err = getter.GetById(id)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil && lastExpiredDat != nil {
 | 
				
			||||||
 | 
							log.Infof("all cache failed and getter failed, expired cache is avaliable, we will use it")
 | 
				
			||||||
 | 
							return lastExpiredDat, nil
 | 
				
			||||||
 | 
						} else if err != nil {
 | 
				
			||||||
		log.Errorf("cache(id:%s) is not in database, something error: %s", id, err)
 | 
							log.Errorf("cache(id:%s) is not in database, something error: %s", id, err)
 | 
				
			||||||
		return nil, ErrNotFound
 | 
							return nil, ErrNotFound
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user