首页 > 其他分享 >解决方案(9) 分布式的本地缓存、二级缓存

解决方案(9) 分布式的本地缓存、二级缓存

时间:2022-11-22 23:37:16浏览次数:35  
标签:缓存 return nil errorx redis 二级缓存 MatchInfo conn 分布式


前言

我们经常使用redis作为1级缓存,它能有效帮助服务层,减少直达数据库的频率,减少查询时间。但是,redis作为缓存仍旧有几个问题:

  • 网络开销。服务节点到redis之间,通过tcp通信,这是有开销的。
  • 序列化开销。存储结构上,我们往往使用json来将需要缓存的模型序列化到redis上,使用时解析。这一块开销也非常明显。
  • 某些配置的值会变动,但是配置的频率非常低,几天几个礼拜才改一下,没有必要浪费1、2步的开销。

至此,我们仍然要为一些场景,配置二级缓存。它服务于:

  • 不常变化的键值,配置。
  • 实时服务追求极速,redis网络耗时和序列化时想要继续节省。

【二级缓存】具备以下特点:

  • 高仿redis,具备可配置的失效期,并且要很短10-30秒。
  • 并发安全,具备hash隔离。
  • 和业务模型强相关。
  • 死亡周期是redis缓存的子集(redis缓存死了,二级缓存必须死。二级缓存死了,redis缓存不一定死)。
  • 二级缓存在单点服务里,是缓存一致的

实现

​github.com/fwhezfwhez/cmap​

关键api

m := cmap.NewMapV2(nil, 16, 10*time.Minute)

m.SetEx()
m.Get()
m.Delete()

个性化定制

userCache := cmap.NewMapV2(nil, 16, 10*time.Minute)
func GetUserInfo(userId string) UserInfo{
u, exist :=userCache.Get(userId)
if exist {
return u.(UserInfo)
}
// user,_ :=GetFromURL()
// user,_ :=GetFromDB()
// user, _ :=GetFromRedis()
// 如果可以的话,在DeleteFromrRedis里,也需要调用 userCache.Delete(userId)
userCache.SetEx(userId, user, 30)
return user
}

利用自动生成来维护【2级缓存】【1级缓存】【数据库】

  • 以下代码,利用开源库 github.com/fwhezfwhez/model_convert生成。

生成用例:

func TestTableToStructWithTag(t *testing.T) {
dataSouce := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=%s password=%s", "localhost", "5432", "postgres", "game", "disable", "123")
tableName := "user_transfer_process"
fmt.Println(model_convert.TableToStructWithTag(dataSouce, tableName, map[string]interface{}{
"dialect": "postgres",
"${db_instance}": "db.DB",
"${db_instance_pkg}": "path/to/db",
}))
}

输出结果

import (
"encoding/json"
"fmt"
"github.com/fwhezfwhez/errorx"
"github.com/fwhezfwhez/cmap"
"github.com/garyburd/redigo/redis"
"github.com/jinzhu/gorm"
"path/to/db"
"io/ioutil"
"net/http"
"time"
)

// Auto-Generate Header
/*
Code is auto-generated by github.com/fwhezfwhez/model_convert.Package below might be imported:
- github.com/fwhezfwhez/errorx
- github.com/garyburd/redigo/redis
- github.com/fwhezfwhez/cmap
- github.com/jinzhu/gorm
- shangraomajiang/util/db
You can get them by:
- go get github.com/fwhezfwhez/errorx
- go get github.com/garyburd/redigo/redis
- go get github.com/jinzhu/gorm

To fulfill redis part, don't forget to set TODOs.They are:
- RedisKey() string
- RedisSecondDuration() int
*/
type MatchInfo struct{
Id int `gorm:"column:id;default:" json:"id" form:"id"`
UpdatedAt time.Time `gorm:"column:updated_at;default:" json:"updated_at" form:"updated_at"`
CreatedAt time.Time `gorm:"column:created_at;default:" json:"created_at" form:"created_at"`
ClubId int `gorm:"column:club_id;default:" json:"club_id" form:"club_id"`
Description string `gorm:"column:description;default:" json:"description" form:"description"`
GameId int `gorm:"column:game_id;default:" json:"game_id" form:"game_id"`
GameAreaId int `gorm:"column:game_area_id;default:" json:"game_area_id" form:"game_area_id"`
RangeProps json.RawMessage `gorm:"column:range_props;default:" json:"range_props" form:"range_props"`
StartAt time.Time `gorm:"column:start_at;default:" json:"start_at" form:"start_at"`
EndAt time.Time `gorm:"column:end_at;default:" json:"end_at" form:"end_at"`
}

func (o MatchInfo) TableName() string {
return "match_info"
}


func (o MatchInfo) DB() *gorm.DB {
return db.DB
}

var MatchInfoRedisKeyFormat = ""

func (o MatchInfo) RedisKey() string {
// TODO set its redis key and required args
return fmt.Sprintf(MatchInfoRedisKeyFormat, )
}


var ArrayMatchInfoRedisKeyFormat = ""

func (o MatchInfo) ArrayRedisKey() string {
// TODO set its array key and required args
return fmt.Sprintf(ArrayMatchInfoRedisKeyFormat,)
}

// 2nd-cache switch.More detail refer to '2nd-cache Header'
const (
// Whether use cache
MatchInfoCacheSwitch = false
// Whether use array cache
ArrayMatchInfoCacheSwitch = false
)

func (o MatchInfo) RedisSecondDuration() int {
// TODO set its redis duration, default 1-7 day, return -1 means no time limit
return int(time.Now().Unix() % 7 + 5) * 60
}

// TODO,set using db or not. If set false, o.MustGet() will never get its data from db.
// do not use this
func (o MatchInfo) UseDB() bool {
return false
}

func (o *MatchInfo) GetFromRedis(conn redis.Conn) error {
if o.RedisKey() == "" {
return errorx.NewFromString("object MatchInfo has not set redis key yet")
}
buf,e:= redis.Bytes(conn.Do("GET", o.RedisKey()))

if e==nil && string(buf)=="DISABLE"{
return fmt.Errorf("not found record in db nor redis")
}

if e == redis.ErrNil {
return e
}

if e != nil && e != redis.ErrNil {
return errorx.Wrap(e)
}

e = json.Unmarshal(buf, &o)

if e!=nil {
return errorx.Wrap(e)
}
return nil
}

func (o *MatchInfo) ArrayGetFromRedis(conn redis.Conn) ([]MatchInfo, error) {
if o.ArrayRedisKey() == "" {
return nil, errorx.NewFromString("object MatchInfo has not set redis key yet")
}

var list = make([]MatchInfo, 0, 10)
buf, e := redis.Bytes(conn.Do("GET", o.ArrayRedisKey()))

// avoid passing through and hit database
// When o.ArrayMustGet() not found both in redis and db, will set its key DISABLE
// and return 'fmt.Errorf("not found record in db nor redis")'
if e == nil && string(buf) == "DISABLE" {
return nil, fmt.Errorf("not found record in db nor redis")
}

// Not found in redis
if e == redis.ErrNil {
return nil, e
}

// Server error, should be logged by caller
if e != nil && e != redis.ErrNil {
return nil, errorx.Wrap(e)
}

e = json.Unmarshal(buf, &list)

if e != nil {
return nil, errorx.Wrap(e)
}
return list, nil
}

// engine should prepare its condition.
// if record not found,it will return 'var notFound = fmt.Errorf("not found record in db nor redis")'.
// If you want to ignore not found error, do it like:
// if e:= o.MustGet(conn, engine.Model(Model{}).Where("condition =?", arg)).Error;e!=nil {
// if e.Error() == "not found record in db nor redis"{
// log.Println(e)
// return
// }
// }
func (o *MatchInfo) MustGet(conn redis.Conn, engine *gorm.DB) error {
var shouldSyncToCache bool

if MatchInfoCacheSwitch {
if e := o.getFromCache(); e == nil {
return nil
}
defer func() {
if shouldSyncToCache {
fmt.Println("exec sync to cache")
o.syncToCache()
}
}()
}

e := o.GetFromRedis(conn)
// When redis key stores its value 'DISABLE', will returns notFoundError and no need to query from db any more
if e!=nil && e.Error() == "not found record in db nor redis" {
return e
}

if e == nil {
shouldSyncToCache = true
return nil
}
if e != nil {
var count int
if e2 := engine.Count(&count).Error; e2 != nil {
return errorx.GroupErrors(errorx.Wrap(e), errorx.Wrap(e2))
}
if count == 0 {
var notFound = fmt.Errorf("not found record in db nor redis")
if o.RedisSecondDuration() == -1 {
conn.Do("SET", o.RedisKey(), "DISABLE", "NX")
} else {
conn.Do("SET", o.RedisKey(), "DISABLE", "EX", o.RedisSecondDuration(), "NX")
}
return notFound
}

if e3 := engine.First(&o).Error; e3 != nil {
return errorx.GroupErrors(errorx.Wrap(e), errorx.Wrap(e3))
}
shouldSyncToCache = true

if e == redis.ErrNil {
o.SyncToRedis(conn)
return nil
}
return errorx.Wrap(e)
}
return nil
}

func (o *MatchInfo) ArrayMustGet(conn redis.Conn, engine *gorm.DB) ([]MatchInfo, error) {
var shouldSyncToCache bool
var arr []MatchInfo

if ArrayMatchInfoCacheSwitch {
if arr, e := o.ArrayGetFromCache(); e == nil {
return arr, nil
}
defer func() {
if shouldSyncToCache {
fmt.Println("exec sync to cache")
o.ArraySyncToCache(arr)
}
}()
}


list, e := o.ArrayGetFromRedis(conn)
// When redis key stores its value 'DISABLE', will returns notFoundError and no need to query from db any more
// When call ArrayDeleteFromRedis(), will activate its redis and db query
if e != nil && e.Error() == "not found record in db nor redis" {
return nil, e
}
// get from redis success.
if e == nil {
shouldSyncToCache = true
arr = list
return list, nil
}
// get from redis fail, try db
if e != nil {
var count int
if e2 := engine.Count(&count).Error; e2 != nil {
return nil, errorx.GroupErrors(errorx.Wrap(e), errorx.Wrap(e2))
}
if count == 0 {
var notFound = fmt.Errorf("not found record in db nor redis")
if o.RedisSecondDuration() == -1 {
conn.Do("SET", o.ArrayRedisKey(), "DISABLE", "NX")
} else {
conn.Do("SET", o.ArrayRedisKey(), "DISABLE", "EX", o.RedisSecondDuration(), "NX")
}
return nil, notFound
}

if e3 := engine.Find(&list).Error; e3 != nil {
return nil, errorx.GroupErrors(errorx.Wrap(e), errorx.Wrap(e3))
}

shouldSyncToCache = true
arr = list
// try sync to redis
if e == redis.ErrNil {
o.ArraySyncToRedis(conn, list)
return list, nil
}
return nil, errorx.Wrap(e)
}
return nil, nil
}

func (o MatchInfo) SyncToRedis(conn redis.Conn) error {
if o.RedisKey() == "" {
return errorx.NewFromString("object MatchInfo has not set redis key yet")
}
buf, e := json.Marshal(o)
if e != nil {
return errorx.Wrap(e)
}
if o.RedisSecondDuration() == -1 {
if _, e := conn.Do("SET", o.RedisKey(), buf); e != nil {
return errorx.Wrap(e)
}
} else {
if _, e := conn.Do("SETEX", o.RedisKey(), o.RedisSecondDuration(), buf); e != nil {
return errorx.Wrap(e)
}
}
return nil
}

func (o MatchInfo) ArraySyncToRedis(conn redis.Conn, list []MatchInfo) error {
if o.ArrayRedisKey() == "" {
return errorx.NewFromString("object MatchInfo has not set redis key yet")
}
buf, e := json.Marshal(list)
if e != nil {
return errorx.Wrap(e)
}
if o.RedisSecondDuration() == -1 {
if _, e := conn.Do("SET", o.ArrayRedisKey(), buf); e != nil {
return errorx.Wrap(e)
}
} else {
if _, e := conn.Do("SETEX", o.ArrayRedisKey(), o.RedisSecondDuration(), buf); e != nil {
return errorx.Wrap(e)
}
}
return nil
}

func (o MatchInfo) DeleteFromRedis(conn redis.Conn) error{
if o.RedisKey() != "" {
if _, e := conn.Do("DEL", o.RedisKey()); e != nil {
return errorx.Wrap(e)
}
}

if o.ArrayRedisKey() != "" {
if _, e := conn.Do("DEL", o.ArrayRedisKey()); e != nil {
return errorx.Wrap(e)
}
}

if MatchInfoCacheSwitch {
o.deleteFromCache()
}
if ArrayMatchInfoCacheSwitch {
o.ArraydeleteFromCache()
}
return nil
}
func (o MatchInfo) ArrayDeleteFromRedis(conn redis.Conn) error{
return o.DeleteFromRedis(conn)
}

// Dump data through api GET remote url generated by 'GenerateListApi()' to local database.
// This method should never used in production. It's best to to run it before app is running.
//
// mode=1, each time will delete old local data and dump from api.
// mode=2, each time will update/keep the existed data. Mode=2 is developing.
func (o MatchInfo) DumpToLocal(url string,engine *gorm.DB, mode int) error {
tableName := o.TableName()

tran := engine.Begin()
if e:=tran.Exec(fmt.Sprintf("delete from %s", tableName)).Error; e!=nil{
tran.Rollback()
return errorx.Wrap(e)
}

type Result struct{
Data []MatchInfo `json:"data"`
Count int `json:"count"`
}
var result Result
resp,e:=http.Get(url)
if e!=nil {
tran.Rollback()
return errorx.Wrap(e)
}
if resp ==nil || resp.Body ==nil {
tran.Rollback()
return errorx.NewFromString("resp or body nil")
}
defer resp.Body.Close()

buf,e := ioutil.ReadAll(resp.Body)
if e!=nil {
tran.Rollback()
return errorx.Wrap(e)
}

if resp.StatusCode != 200 {
var body string
if len(buf)<100 {
body = string(buf)
} else{
body = string(buf[:100])
}
return errorx.NewFromStringf("status not 200, got %d,body %s", resp.StatusCode, body)
}

if e:=json.Unmarshal(buf, &result);e!=nil{
tran.Rollback()
return errorx.Wrap(e)
}

for i,_ := range result.Data {
data := result.Data[i]
if e:=tran.Model(&o).Create(&data).Error; e!=nil{
tran.Rollback()
return errorx.Wrap(e)
}
}
tran.Commit()
return nil
}

// 2nd-cache Header
// 2nd-cache share RedisKey() as its key.

// self Header
var (
MatchInfoCache *cmap.MapV2

MatchInfoNotFoundErr = fmt.Errorf("not found in cache")
MatchInfoSwitchOffErr = fmt.Errorf("2nd-cache switch is off")
)

// cache expires in 15s
func (o *MatchInfo) cacheDuration() int {
return 15
}


func (o *MatchInfo) getFromCache() error {
if MatchInfoCacheSwitch == false {
return MatchInfoSwitchOffErr
}

tmp, ok := MatchInfoCache.Get(o.RedisKey())
if !ok {
return MatchInfoNotFoundErr
}
*o = tmp.(MatchInfo)
fmt.Println("get from cache")
return nil
}

func (o *MatchInfo) deleteFromCache() {
if MatchInfoCacheSwitch == false {
return
}


MatchInfoCache.Delete(o.RedisKey())
}

func (o *MatchInfo) syncToCache() {
if MatchInfoCacheSwitch == false {
return
}
MatchInfoCache.SetEx(o.RedisKey(), *o, o.cacheDuration())
}

// self Tail

// array Header
var (
ArrayMatchInfoCache *cmap.MapV2

ArrayMatchInfoNotFoundErr = fmt.Errorf("not found in cache")
ArrayMatchInfoSwitchOffErr = fmt.Errorf("2nd-cache switch is off")
)

func init() {
if MatchInfoCacheSwitch {
MatchInfoCache = cmap.NewMapV2(nil, 8, time.Second*60)
}

if ArrayMatchInfoCacheSwitch {
ArrayMatchInfoCache = cmap.NewMapV2(nil, 8, time.Second*60)
}
}



func (o *MatchInfo) ArrayGetFromCache() ([]MatchInfo, error) {
if ArrayMatchInfoCacheSwitch == false {
return nil, ArrayMatchInfoSwitchOffErr
}

tmp, ok := ArrayMatchInfoCache.Get(o.ArrayRedisKey())
if !ok {
return nil, ArrayMatchInfoNotFoundErr
}
fmt.Println("get from cache")
return tmp.([]MatchInfo), nil
}

func (o *MatchInfo) ArraydeleteFromCache() {
if ArrayMatchInfoCacheSwitch == false {
return
}

ArrayMatchInfoCache.Delete(o.ArrayRedisKey())
}

func (o *MatchInfo) ArraySyncToCache(arr []MatchInfo) {
if ArrayMatchInfoCacheSwitch == false {
return
}

ArrayMatchInfoCache.SetEx(o.ArrayRedisKey(), arr, o.cacheDuration() )
}
// array Tail

// 2nd-cache Tail

// flexible-cache Header
// func (o MatchInfo) ${cache_name}Key() string{
// // TODO-Set cache redis key
// return ""
// }
// func (o MatchInfo) ${cache_name}Duration() int{
// // TODO-Set cache redis key expire duration. Default 1-7 days
// return int(time.Now().Unix() % 7 + 5) * 60
// }
// func (o *MatchInfo) ${cache_name}MustGet(conn redis.Conn, source func(${cache_name} *${cache_type})error) (${cache_type}, error) {

// rs, e:= redis.${Cache_type}(conn.Do("GET", o.${cache_name}Key()))
// if e !=nil {
// if e == redis.ErrNil {
// if e:=source(&rs); e!=nil {
// return rs, errorx.Wrap(e)
// }
// if _, e= conn.Do("SETEX", o.${cache_name}Key(), ${cache_name}Duration(), rs),; e!=nil {
// return rs, errorx.Wrap(e)
// }
// return rs,nil
// }
// return rs, errorx.Wrap(e)
// }
// return rs,nil

// }
// flexible-cache Tail

// no-decode Header
//
// MustGetNoDecode do most similar work as MustGet do, but it will not unmarshal data from redis into 'o', in the meanwhile, will return its raw json stream as return.
// This function aims to save cost of decoding in the only case that you want to return 'o' itself and has nothing changed to inner values.
// 'engine' should prepare its condition.
// if record not found,it will return 'var notFound = fmt.Errorf("not found record in db nor redis")'.
// If you want to ignore not found error, do it like:
// if buf, e:= o.MustGetNoDecode(conn, engine.Model(Model{}).Where("condition =?", arg)).Error;e!=nil {
// if e.Error() == "not found record in db nor redis" || e == redis.ErrNil {
// log.Println(e)
// return
// }
// }
//
func (o *MatchInfo) MustGetNoDecode(conn redis.Conn, engine *gorm.DB) (json.RawMessage, error) {
var shouldSyncToCache bool

if MatchInfoCacheSwitch {
if e := o.getFromCache(); e == nil {
return nil, nil
}
defer func() {
if shouldSyncToCache {
fmt.Println("exec sync to cache")
o.syncToCache()
}
}()
}

arrBuf, e := o.GetFromRedisNoDecode(conn)
// When redis key stores its value 'DISABLE', will returns notFoundError and no need to query from db any more
if e!=nil && e.Error() == "not found record in db nor redis" {
return nil, e
}

if e == nil {
shouldSyncToCache = true
return arrBuf, nil
}
if e != nil {
var count int
if e2 := engine.Count(&count).Error; e2 != nil {
return nil, errorx.GroupErrors(errorx.Wrap(e), errorx.Wrap(e2))
}
if count == 0 {
var notFound = fmt.Errorf("not found record in db nor redis")
if o.RedisSecondDuration() == -1 {
conn.Do("SET", o.RedisKey(), "DISABLE", "NX")
} else {
conn.Do("SET", o.RedisKey(), "DISABLE", "EX", o.RedisSecondDuration(), "NX")
}
return nil, notFound
}

if e3 := engine.First(&o).Error; e3 != nil {
return nil, errorx.GroupErrors(errorx.Wrap(e), errorx.Wrap(e3))
}
shouldSyncToCache = true

if e == redis.ErrNil {
o.SyncToRedis(conn)
return nil,nil
}
return nil, errorx.Wrap(e)
}
return nil,nil
}

// GetFromRedisNoDecode will return its json raw stream and will not decode into 'o'.
// It aims to save cost of decoding if json stream is decoded slowly.
func (o *MatchInfo) GetFromRedisNoDecode(conn redis.Conn) (json.RawMessage, error) {
if o.RedisKey() == "" {
return nil, errorx.NewFromString("object MatchInfo has not set redis key yet")
}
buf,e:= redis.Bytes(conn.Do("GET", o.RedisKey()))

if e==nil && string(buf)=="DISABLE"{
return nil, fmt.Errorf("not found record in db nor redis")
}

if e == redis.ErrNil {
return nil, e
}

if e != nil && e != redis.ErrNil {
return nil, errorx.Wrap(e)
}

return buf, nil
}

// ArrayMustGetNoDecode will not unmarshal json stream to 'arr' and return json.Rawmessage as return value instead if it's found in redis,
// otherwise will return arr from cache or db.
//
// This function aims to save cost of decoding in the read-only case of 'o'. It means you should do nothing changed to its json value.
/*
arr, arrBuf, e:= o.ArrayMustGetNoDecode(conn, engine)
if e!=nil {
// handle error
}

if len(arrBuf) >0 {
c.JSON(200, gin.H{"message":"success", "data": arrBuf})
} else {
c.JSON(200, gin.H{"message":"success", "data": arr})
}
*/
func (o *MatchInfo) ArrayMustGetNoDecode(conn redis.Conn, engine *gorm.DB) ([]MatchInfo,json.RawMessage, error) {
var shouldSyncToCache bool
var arr []MatchInfo

if ArrayMatchInfoCacheSwitch {
if arr, e := o.ArrayGetFromCache(); e == nil {
return arr, nil, nil
}
defer func() {
if shouldSyncToCache {
fmt.Println("exec sync to cache")
o.ArraySyncToCache(arr)
}
}()
}


arrBuf, e := o.ArrayGetFromRedisNoDecode(conn)
// When redis key stores its value 'DISABLE', will returns notFoundError and no need to query from db any more
// When call ArrayDeleteFromRedis(), will activate its redis and db query
if e != nil && e.Error() == "not found record in db nor redis" {
return nil,nil, e
}
// get from redis success.
if e == nil {
// shouldSyncToCache = true
// arr = list
return nil, arrBuf, nil
}
// get from redis fail, try db
if e != nil {
var list = make([]MatchInfo,0, 100)
var count int
if e2 := engine.Count(&count).Error; e2 != nil {
return nil,nil, errorx.GroupErrors(errorx.Wrap(e), errorx.Wrap(e2))
}
if count == 0 {
var notFound = fmt.Errorf("not found record in db nor redis")
if o.RedisSecondDuration() == -1 {
conn.Do("SET", o.ArrayRedisKey(), "DISABLE", "NX")
} else {
conn.Do("SET", o.ArrayRedisKey(), "DISABLE", "EX", o.RedisSecondDuration(), "NX")
}
return nil, nil, notFound
}

if e3 := engine.Find(&list).Error; e3 != nil {
return nil, nil, errorx.GroupErrors(errorx.Wrap(e), errorx.Wrap(e3))
}

shouldSyncToCache = true
arr = list
// try sync to redis
if e == redis.ErrNil {
o.ArraySyncToRedis(conn, list)
return list, nil, nil
}
return nil, nil, errorx.Wrap(e)
}
return nil, nil, nil
}

func (o *MatchInfo) ArrayGetFromRedisNoDecode(conn redis.Conn) (json.RawMessage, error) {
if o.ArrayRedisKey() == "" {
return nil, errorx.NewFromString("object MatchInfo has not set redis key yet")
}

buf, e := redis.Bytes(conn.Do("GET", o.ArrayRedisKey()))

// avoid passing through and hit database
// When o.ArrayMustGet() not found both in redis and db, will set its key DISABLE
// and return 'fmt.Errorf("not found record in db nor redis")'
if e == nil && string(buf) == "DISABLE" {
return nil, fmt.Errorf("not found record in db nor redis")
}

// Not found in redis
if e == redis.ErrNil {
return nil, e
}

// Server error, should be logged by caller
if e != nil && e != redis.ErrNil {
return nil, errorx.Wrap(e)
}

return buf, nil
}
// no-decode Tail
// Auto-Generate Tail

标记缓存

var MatchInfoRedisKeyFormat = "xyx:match_info:%s:%d"

func (o MatchInfo) RedisKey() string {
// TODO set its redis key and required args
return fmt.Sprintf(MatchInfoRedisKeyFormat, config.Mode, o.ClubId)
}

func (o MatchInfo) RedisSecondDuration() int {
// TODO set its redis duration, default 1-7 day, return -1 means no time limit
return int(time.Now().Unix()%7+1) * 24 * 60 * 60
}

标记是否使用二级缓存

// 2nd-cache switch.More detail refer to '2nd-cache Header'
const (
// Whether use cache
MatchInfoCacheSwitch = false
// Whether use array cache
ArrayMatchInfoCacheSwitch = false
)

查询时:

func GetMatchInfo(clubId int, conn redis.Conn) (matchModel.MatchInfo, error) {
var m = matchModel.MatchInfo{
ClubId: clubId,
}

if conn == nil {
conn = redistool.RedisPool.Get()
defer conn.Close()
}

engine := m.DB().Model(m).Where("club_id=?", clubId)

if e := m.MustGet(conn, engine); e != nil {
if e!=redis.ErrNil && e.Error() != "not found record in db nor redis" {
return m, errorx.Wrap(e)
}

// not found this record, then do init or return errr
// - m.Init()
// - return m, errorx.Wrap(e)
return m, errorx.Wrap(e)
}

return m, nil

}

修改时

m.DB().Model(m).Where("id=?", m.Id).Updates(m)
m.DeleteFromRedis(conn)

结语

回复一些大家在使用【二级缓存】时遇到的问题。

  1. 二级缓存和redis缓存以及数据库之间的关系,在维护上会不会很困难?

如果每次都基于某一种场景,去临时添加缓存,二级缓存。那么在开发上,为了维护一致性便容易出错。出错的原因体现在,手敲代码容易敲错逻辑,缓存之间的销毁周期,销毁顺序,在考量时带来的心智负担,会让人很容易出错。新人扛不起这一个工作,老人也会有可见的出错概率。

所以,在使用缓存,不论是1级还是2级,要形成良好的1,2级缓存使用习惯。这里,我是推荐【代码生成】,将1,2级缓存的编写自动化,这样就能写一次正确,以后都不会出错了。

  1. 二级缓存注意事项有哪些?
  • 要对1,2级缓存,形成可以复制的【代码生成】方案,消除维护心智。
  • 要具备较短的生命周期,10-30秒最好。因为二级缓存是单点的,要和分布式保持最终一致。即仅允许10-30秒内不一致的问题。
  • 要和业务模型强绑定。因为不同业务模型,不应该存在race竞争、锁竞争。
  • 实现上,最好向Redis靠拢。最少具备【setex】api


标签:缓存,return,nil,errorx,redis,二级缓存,MatchInfo,conn,分布式
From: https://blog.51cto.com/u_11553781/5878734

相关文章

  • 03.大促抗住零点洪峰-缓存架构体系(3) 解决缓存雪崩,击穿,布隆过滤器,缓存一致性
                                                         ......
  • 分布式定时调度-xxl-job
    分布式定时调度-xxl-job一.定时任务概述1.定时任务认识1.1.什么是定时任务定时任务是按照指定时间周期运行任务。使用场景为在某个固定时间点执行,或者周期性的去执行......
  • 分布式事务
    首先理解事务,提供一种“要么什么都不做,要么做全套(AllorNothing)”的机制,她有ACID四大特性(原子性,一致性,隔离性,持久性) 1分布式事务分布式事务顾名思义就是要在分布式系......
  • 02.大促抗住零点洪峰-缓存架构体系(2) 抢红包
                 ......
  • MyBatis - 基础学习10 - 缓存
    一.简介1.为什么要使用缓存:我们在数据库查询数据的时候,总会不断的连接,释放,但是使用的查询语句和要查询的东西却是一摸一样的,这样反复的操作是十分浪费资源的所以,我们在......
  • 01.大促抗住零点洪峰-缓存架构体系(1)
                                         ......
  • 分布式锁
    1什么是分布式锁?分布式锁,即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体......
  • 分布式系统的特性
    互联网上有许多的应用程序和服务,它们都有可能出现故障,但在很多时候,我们几乎都不能发现这些服务中断的情况,这就是分布式系统的关键特性。分布式系统的特性包括容错性、高可扩......
  • 分布式系统的特性
    互联网上有许多的应用程序和服务,它们都有可能出现故障,但在很多时候,我们几乎都不能发现这些服务中断的情况,这就是分布式系统的关键特性。分布式系统的特性包括容错性、高可......
  • 分布式面试题
    ZooKeeper和Redis两种分布式锁区别Redis:优点:redis基于​​内存​​​,读写性能很高,因此基于redis的分布式锁效率比较高缺点:在redismaster实例宕机的时候,可能导致多个客户端......