代码地址:https://gitee.com/lymgoforIT/golang-trick/tree/master/37-load-local-cache
之前已经有博客介绍过啦!!46.go实现一个本地内存缓存系统
本地缓存通常用于存储不经常变动,但又需要频繁访问的数据,例如用户配置,系统设置,常用的数据查询结果等。这样可以减少对数据库或者远程服务器的访问,提高程序的运行效率。
下面是一个Go
语言的例子,使用了内置的Map
与Slice
作为本地缓存:
场景假设:
假设我们负责了一个活动系统,管理这很多的活动元信息,此时将生效中的活动信息加入本地缓存能大大提高性能。注意这里本地缓存也不是要缓存所有的活动元信息,那样占用内存可能较大,得不偿失,所以只缓存生效中的。那些其他状态的使用频率较低,查DB就行。
model/activity.go
package model
import "time"
// Activity 实际工作中,肯定还有更多的字段,且字段可能是枚举、结构体等
type Activity struct {
Id int64 `json:"id"` // 活动ID
Name string `json:"name"` // 活动名称
Type int `json:"type"` // 活动类型
ProductId int64 `json:"product_id"` // 产品线
Desc string `json:"desc"` // 描述
Status int `json:"status"` // 活动状态
Rules string `json:"rules"` // 活动规则
StartTime time.Time // 开始时间
EndTime time.Time // 结束时间
CreateTime time.Time // 创建时间
UpdateTime time.Time // 更新时间
}
这里为了演示简单,就省去了和DB
的交互,直接模拟一下就好啦,如下:
dal/activity.go
package dal
import (
"golang-trick/37-load-local-cache/model"
"time"
)
// GetActivity 模拟从DB获取活动元信息
func GetActivity(minId int64, status []int, batchSize int) ([]*model.Activity, error) {
return []*model.Activity{
{
Id: 1,
Name: "限时返场",
Type: 1, // 枚举更好,此处就把1当成返场类型
ProductId: 1,
Desc: "返场描述",
Status: 1, // 枚举更好,此处就把1当成生效中
Rules: "",
StartTime: time.Time{},
EndTime: time.Time{},
CreateTime: time.Time{},
UpdateTime: time.Time{},
},
{
Id: 2,
Name: "极速秒杀",
Type: 2, // 秒杀类型
ProductId: 1,
Desc: "秒杀描述",
Status: 1,
Rules: "",
StartTime: time.Time{},
EndTime: time.Time{},
CreateTime: time.Time{},
UpdateTime: time.Time{},
},
}, nil
}
本地缓存的加载,这里提供了Map
和Slice
两种结果,使得使用方可根据需要取用。主要是提供了本地缓存的加载和定时更新方法,此外,还提供了相关取用方法,这些取用方法可能根据实际情况增加更多方法。
注意看注释哦!!
cache/activity.go
package cache
import (
"fmt"
"github.com/opentracing/opentracing-go/log"
"golang-trick/37-load-local-cache/dal"
"golang-trick/37-load-local-cache/model"
"math/rand"
"sync"
"time"
)
var ActivityMap map[int64]*model.Activity
var ActivityList []*model.Activity
var loadActivityLock = sync.Mutex{} // map与slice不是并发安全的,所以加锁控制
// LoadActivity 活动信息不会太多,生效中的最多上千,所以可以从头开始加载全部生效中的到本地缓存
func LoadActivity() error {
loadActivityLock.Lock()
defer loadActivityLock.Unlock()
// 每次从头分批加载
minID := int64(0)
// 加载过程使用临时变量,从而不影响之前已经在本地缓存的数据使用
newActivityMap := make(map[int64]*model.Activity)
newActivityList := make([]*model.Activity, 0)
for {
// 这里很多时候是调用一个RPC服务
activities, err := dal.GetActivity(minID, []int{1}, 50)
if err != nil {
return err
}
// 全部记录加载完毕
if len(activities) == 0 {
break
}
for _, a := range activities {
newActivityMap[a.Id] = a
newActivityList = append(newActivityList, a)
}
// 记录本轮循环加载的最后一条记录的ID,下轮循环加载从此ID处开始
minID = activities[len(activities)-1].Id
}
// 全部记录加载完毕后,更新本地缓存
ActivityMap = newActivityMap
ActivityList = newActivityList
return nil
}
func RefreshCache() {
go func() {
defer func() {
// 本地缓存加载出现panic,则需要上抛panic,否则可能引发更大的问题
if err := recover(); err != nil {
panic("RefreshCache panic")
}
}()
rand.Seed(time.Now().UnixNano())
for {
randomInt := rand.Intn(60) + 60
time.Sleep(time.Duration(randomInt) * time.Second)
err := LoadActivity()
if err != nil {
log.Error(fmt.Errorf("load activity Info err:%v", err))
}
}
}()
}
func GetActivityList() []*model.Activity {
// 如果有其他一些逻辑,便可以写到此处了,如灰度逻辑
return ActivityList
}
func GetActivityMap() map[int64]*model.Activity {
return ActivityMap
}
func GetActivityById(id int64) (*model.Activity, error) {
if ActivityMap == nil {
return nil, fmt.Errorf("ActivityMap is nil")
}
if _, ok := ActivityMap[id]; !ok {
return nil, fmt.Errorf("ActivityMap[id] not found id:%v", id)
}
return ActivityMap[id], nil
}
使用方在main
方法中加载本地缓存,并启动异步更新方法,后续就可以在需要的地方调用本地缓存提供的相应取用方法即可。
main.go
package main
import (
"golang-trick/37-load-local-cache/cache"
)
func main() {
err := cache.LoadActivity()
if err != nil {
panic(err)
}
cache.RefreshCache()
}