61.本地缓存加载与使用实践

发布时间:2024年01月04日

代码地址:https://gitee.com/lymgoforIT/golang-trick/tree/master/37-load-local-cache

一、本地缓存理论最佳实践

  1. 控制缓存大小:根据应用程序的需求和可用的存储空间,合理限制本地缓存的大小。过大会导致存储空间不足,过小可能无法有效缓存数据。
  2. 缓存过期策略:设置适当的缓存过期时间,以确保缓存的数据不会过时。根据数据的更新频率和重要性,制定合适的过期策略。
  3. 数据一致性:在使用本地缓存时,要考虑数据的一致性。如果数据在后端发生变化,需要及时更新本地缓存,以避免展示过时的数据。
  4. 缓存刷新机制:建立合适的缓存刷新机制,当后端数据发生变化时,及时更新本地缓存。可以通过定时刷新、监听后端事件或在请求数据时检查更新标志等方式实现。
  5. 缓存层次结构:考虑使用多层缓存结构,如内存缓存和磁盘缓存。将经常访问的数据放在内存中,可以提供更快的访问速度,而将不经常访问的数据放在磁盘上,以节省内存空间。
  6. 数据压缩:对于大数据量的缓存,可以考虑使用数据压缩技术,以减少存储空间的占用。
  7. 缓存失效处理:在缓存失效时,需要有适当的处理逻辑。可以返回默认值、从后端重新获取数据或进行其他适当的操作。
  8. 测试和监控:对本地缓存进行充分的测试,确保其正确性和性能。同时,监控缓存的命中率、过期时间和存储空间使用情况,以便及时调整缓存策略。
    这些最佳实践可以帮助你更好地利用本地缓存,提高应用程序的性能和用户体验。具体的实施方式需要根据你的应用程序和需求进行调整。

二、Go代码实践:

1、本地缓存设计

之前已经有博客介绍过啦!!46.go实现一个本地内存缓存系统

2、本地缓存加载

本地缓存通常用于存储不经常变动,但又需要频繁访问的数据,例如用户配置,系统设置,常用的数据查询结果等。这样可以减少对数据库或者远程服务器的访问,提高程序的运行效率。

下面是一个Go语言的例子,使用了内置的MapSlice作为本地缓存:

场景假设:
假设我们负责了一个活动系统,管理这很多的活动元信息,此时将生效中的活动信息加入本地缓存能大大提高性能。注意这里本地缓存也不是要缓存所有的活动元信息,那样占用内存可能较大,得不偿失,所以只缓存生效中的。那些其他状态的使用频率较低,查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

}

本地缓存的加载,这里提供了MapSlice两种结果,使得使用方可根据需要取用。主要是提供了本地缓存的加载和定时更新方法,此外,还提供了相关取用方法,这些取用方法可能根据实际情况增加更多方法。

注意看注释哦!!

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()
}

文章来源:https://blog.csdn.net/YouMing_Li/article/details/135375585
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。