package cachev2 import ( "crypto/md5" "fmt" "math" "math/rand" "runtime" "strings" "time" "go.uber.org/zap" ) const ( // 默认的缓存时间 defaultCacheTime = 1800 // 空数据的缓存时间 emptyDataCacheTime = 300 // 数据类型 dataTypeList = "list" dataTypeInfo = "info" ) // BaseCache 缓存类定义 // 集成到类黑料框架文件: // // copy common/cachev2/* // common/redis/redis.go +ScanKeys方法 // web/main.go | app/main.go (+初始化缓存操作中间件) // // Usage: // // cache object: cachev2.Classes().CacheTime(30*time.Minute).Key("cache-key").ResBind(&object).Cache(user_func, params...) // cache base type: cachev2.Classes().CacheTime(2*time.Hour).Key("cache-key").Cache(user_func, params...) // auto cache key(list): cachev2.Classes().CacheTime(600*time.Second).AutoListKey("table-name").Cache(user_func, params...) // auto cache key(info): cachev2.Classes().CacheTime(600*time.Second).AutoInfoKey("table-name", "id-string").Cache(user_func, params...) // // 清理缓存: // // clear cache key: cachev2.Classes().FussyClear("key-prefix*") // clear auto cache(just list cache): cachev2.Classes().Table("table-name").AutoClear(true, nil) // clear auto cache(just info cache): cachev2.Classes().Table("table-name").AutoClear(false, &idString) // clear auto cache(both of list and info): cachev2.Classes().Table("table-name").AutoClear(true, &idString) type BaseCache struct { // 缓存驱动器 driver Driver // 缓存时间 s ttl int64 // 是否随机时间 random bool // 缓存随机时间 s randomTTL int // 是否需要刷新缓存 refresh bool // 缓存key key string // 自动生成缓存key autoKey bool // 表名 table string // 数据类型 dataType string // 数据id dataID string // 缓存对象 object interface{} // 日志驱动 logger *zap.Logger } // basic 缓存基础 var basic = &BaseCache{} // Init 初始化 func Init(d Driver, l *zap.Logger) { basic.driver = d basic.logger = l } // Classes 获取操作类 func Classes() *BaseCache { c := &BaseCache{ driver: basic.driver, ttl: defaultCacheTime, random: true, logger: basic.logger, } return c } // New 新建缓存操作类 func New(d Driver) *BaseCache { rand.Seed(time.Now().UnixNano()) rnd := rand.Intn(30) c := &BaseCache{ driver: d, ttl: defaultCacheTime, random: true, randomTTL: rnd, logger: basic.logger, } return c } // Key 设置缓存键 func (c *BaseCache) Key(k string) *BaseCache { c.key = k return c } // AutoListKey 自动生成列表存储键 func (c *BaseCache) AutoListKey(table string) *BaseCache { c.autoKey = true c.table = table c.dataType = dataTypeList return c } // AutoInfoKey 自动生成单条数据键 func (c *BaseCache) AutoInfoKey(table, id string) *BaseCache { c.autoKey = true c.table = table c.dataID = id c.dataType = dataTypeInfo return c } // ResBind 设置缓存对象 func (c *BaseCache) ResBind(o interface{}) *BaseCache { c.object = o return c } // CacheTime 设定缓存时间 func (c *BaseCache) CacheTime(t time.Duration) *BaseCache { c.ttl = int64(t.Seconds()) c.random = true return c } // CacheStair 阶梯式缓存 func (c *BaseCache) CacheStair(td time.Duration) *BaseCache { second := int64(td.Seconds()) t := time.Now() timeSecond := int64(math.Min(86400, math.Max(60, float64(second)))) zeroTime := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()).Unix() over := t.Unix() - zeroTime c.ttl = timeSecond - over%timeSecond c.random = false return c } // Refresh 刷新缓存 func (c *BaseCache) Refresh() *BaseCache { c.refresh = true return c } // Cache 缓存并返回[对象]数据 func (c *BaseCache) Cache(fn interface{}, p ...interface{}) (data interface{}, err error) { // 操作完成后需要重置缓存对象 defer c.reset() // 自动缓存key if c.autoKey { c.key, err = c.generalKey(false, p...) if err != nil { return } } if c.key == "" { return nil, fmt.Errorf("缓存键未设置") } var e error if c.refresh { _, _ = c.Delete(c.key) } // 获取缓存 ok, d := c.getCache(c.key) // 存在缓在则进行反序列编码 if ok { data, e = unSerialJson(d, c.object) if e == nil { //fmt.Println("data from cache") return } c.LogError("unSerialJson error occur:", zap.Any("data", d), zap.Error(err)) } // 不存在缓存则获取锁 lockKey := c.key + "_processing" ok, e = c.driver.SetNX(lockKey, 1, 5*time.Second) if !ok || e != nil { //fmt.Println("未获取到锁,等待中:", lockKey) i := 0 expire := false var d string for expire == false { time.Sleep(200 * time.Millisecond) // 重新尝试获取缓存 ok, d = c.getCache(c.key) i++ expire = i >= 25 || ok //fmt.Printf("第 %d 次尝试获取缓存,key: %s \n", i, lockKey) } if ok { data, e = unSerialJson(d, c.object) if e == nil { //fmt.Println("data from cache2") return } } } //fmt.Println("已获取到cache锁:", lockKey) // 释放数据请求锁 defer func(driver Driver, key string) { _, _ = driver.Del(key) }(c.driver, lockKey) // 请求原始数据 data, err = funcInvoke(fn, p...) if err != nil && err.Error() != "record not found" { c.LogError("acquire source data error occur:", zap.Error(err)) return nil, fmt.Errorf("获取原数据错误:%s", err.Error()) } // 缓存原始数据后释放锁 d, e = serialJson(data) if e == nil { //fmt.Println("data form origin") // object 设置 if c.object != nil { _, e = unSerialJson(d, c.object) if e != nil { c.LogError("unmarshal data to object error occur:", zap.Error(e)) return data, fmt.Errorf("数据映射失败:%s", e.Error()) } } e = c.setCache(c.key, d) if e != nil && c.logger != nil { c.LogError("set cache data error occur:", zap.Any("data", data), zap.Error(e)) } } else { c.LogError("serialJson data error occur:", zap.Any("data", data), zap.Error(e)) } return } // generalKey 自动生成缓存键 func (c *BaseCache) generalKey(forFuzzyClear bool, p ...interface{}) (string, error) { var id, key string keyStr, e := serialJson(p) if e != nil { return "", fmt.Errorf("此类参数不支持自动生成key") } switch c.dataType { case dataTypeList: if forFuzzyClear { id = "*" } else { id = fmt.Sprintf("%x", md5.Sum([]byte(keyStr))) } key = fmt.Sprintf("table-%s:list-%s", c.table, id) case dataTypeInfo: if forFuzzyClear { id = c.dataID + ":*" } else { id = fmt.Sprintf("%s:%x", c.dataID, md5.Sum([]byte(keyStr))) } key = fmt.Sprintf("table-%s:info-%s", c.table, id) } return key, nil } // getCache 获取缓存 func (c *BaseCache) getCache(key string) (ok bool, data string) { if !c.driver.IsExist(key) { return } var err error res, err := c.driver.Get(key) if err != nil { c.LogError("get cache error:", zap.Any("cache key", key), zap.Error(err)) return } if res == nil { return } return true, *res } // setCache 设置缓存 func (c *BaseCache) setCache(key string, data string) error { if data == "" || data == "null" { c.ttl = emptyDataCacheTime } else if c.random { if c.randomTTL == 0 { rand.Seed(time.Now().UnixNano()) rnd := rand.Intn(30) c.ttl += int64(rnd) } else { c.ttl += int64(c.randomTTL) } } return c.driver.Set(key, data, time.Duration(c.ttl)*time.Second) } // Delete 清除指定缓存 func (c *BaseCache) Delete(key ...string) (int64, error) { c.refresh = false count, err := c.driver.Del(key...) if err != nil { c.LogError("delete cache key error", zap.Any("key", key), zap.Error(err)) return count, err } return count, nil } // DeleteCurrent 清除当前缓存 func (c *BaseCache) DeleteCurrent(p ...interface{}) (int64, error) { var err error if c.key == "" { c.key, err = c.generalKey(false, p...) if err != nil { return 0, err } } return c.Delete(c.key) } // FussyClear 模糊匹配删除 func (c *BaseCache) FussyClear(match string) (int64, error) { go func() { var err error var keys []string keys, err = c.driver.ScanKeys(match) if err != nil { c.LogError("scan cache key error", zap.Any("match", match), zap.Error(err)) return } // 分批删除 var batch = 10 for i := 0; i < len(keys); i += batch { if i+batch >= len(keys) { _, err = c.Delete(keys[i:]...) } else { _, err = c.Delete(keys[i : i+batch]...) } if err != nil { return } } }() return 1, nil } // Table 设置表名 func (c *BaseCache) Table(table string) *BaseCache { c.table = table return c } // AutoClear 清理自动生成的列表/详情缓存 func (c *BaseCache) AutoClear(clearList bool, id *string) (count int64, err error) { // 判断是否设置表 //if c.table == "" { // return 0, fmt.Errorf("清理表缓存失败,未设置表名") //} // //var tc int64 //// 先清理列表缓存 //if clearList { // c.dataType = dataTypeList // key, _ := c.generalKey(true) // tc, err = c.FussyClear(key) // count += tc // if err != nil { // return // } //} //// 清理详情数据 //if id != nil { // c.dataType = dataTypeInfo // c.dataID = *id // key, _ := c.generalKey(true) // tc, err = c.FussyClear(key) // count += tc // if err != nil { // return // } //} return } // LogError 日志错误记录 func (c *BaseCache) LogError(msg string, fs ...zap.Field) { if c.logger != nil { dep := 0 t := make([]string, 0, 10) for i := 1; i < 10; i++ { _, file, line, ok := runtime.Caller(i) if !ok { break } if strings.Contains(file, "/runtime/") || strings.Contains(file, "/reflect/") { continue } t = append(t, fmt.Sprintf("%s∟%s:%d", strings.Repeat(" ", dep), file, line)) dep++ } exception := fmt.Sprintf("[MSG]%s\n[Stack]\n%s", msg, strings.Join(t, "\n")) c.logger.Error(exception, fs...) } } // reset 重置缓存 func (c *BaseCache) reset() { c.ttl = defaultCacheTime c.random = true c.randomTTL = 0 c.refresh = false c.key = "" c.autoKey = false c.table = "" c.dataType = "" c.dataID = "" c.object = nil }