> 文章列表 > go实现一个本地缓存

go实现一个本地缓存

go实现一个本地缓存

本地缓存

一、为什么要使用本地缓存

提升性能,快速响应。
通常我们对于持久化数据都会放在数据库中,一般来说,我们都是直接访问数据库的,数据的性能瓶颈在于
1、网络连接
2、磁盘io,取决于sql 性能与数据量等等。
一般我们针对我们的应用程序会增加一层缓存,在选择缓存时,我们也会有很多选择、包括redis、memcache、这些都是作为进程独立部署的,可以用于分布式缓存。
如果从业务量出发,同时成本低,我们可以采用本地缓存的方式,本地缓存具有的优点
1、访问快,不需要网络连接
2、易于实现
缺点
1、不能分布式存储,也就是每台服务器都会利用一段内存数据。
缓存通常用于存储我们的热点数据,通常缓存数据具有的特点:访问频率高、不易修改、数据量有限。

二、如何设计一个本地缓存

基于不同的业务场景,我们可以选择设计一个符合自己业务场景的缓存。
对于我需要的而言,我需要是
1、能够定时从数据库中自动加载数据。
2、支持并发安全
3、支持过期淘汰
4、便于扩展、较为通用

三、本地缓存的简单实现

接口定义

// CacheInterface 提供对于缓存 get set del
type CacheInterface interface {SetVal(key string, response *Value) errorGetVal(key string) *ValueDelVal(key string)loadFunc(ctx context.Context, cache *ICache)
}
// cache 实现
//ICache
// cache 缓存
// callback 执行函数
// executeInterval 定期刷新时间
// key 唯一key
// params 请求参数
// reTry 执行异常重试次数
// reTryInterVal 执行异常重试间隔
// reSetCountDay 统计重置函数更新的次数,用来判断改数据的操作是否频繁 包含day、week、month 维度
// reSetCountWeek week 维度次数统计
// reSetCountMonth 月统计维度
type ICache struct {callback        CallBackFuncexecuteInterval time.Durationkey             stringparams          map[string]interface{}reTry           intreTryInterval   time.DurationreSetCountDay   intreSetCountWeek  intreSetCountMonth int
}
//构造器
func NewCache() *ICache {return &ICache{}
}
// CallBackFunc 执行回调函数
type CallBackFunc func(map[string]interface{}) *Valuevar ErrLargeKey = errors.New("the key is larger than 65535")
var maxKeyLen = 65535
var reTry = 3
var reTryInterval = time.Second * 10func Set(request *SingleRequest) error {return NewCache().SetVal(request.Key, request.Value)
}func (c *ICache) SetVal(key string, response *Value) error {if len(key) > maxKeyLen {return ErrLargeKey}setVal(key, response)return nil
}func Get(key string) *Value {return getVal(key)
}func Del(key string) {delVal(key)return
}func (c *ICache) GetVal(key string) *Value {return getVal(key)
}func (c *ICache) DelVal(key string) {delVal(key)return
}// LoadFunc 提供加载
func LoadFunc(request *LoadRequest) {ctx := context.Background()if request.ReTry == 0 {request.ReTry = reTry}if request.ReTryInterval == 0 {request.ReTryInterval = reTryInterval}cache := &ICache{callback:        request.Callback,executeInterval: request.TTL,key:             request.Key,params:          request.CallBackParams,reTry:           request.ReTry,reTryInterval:   request.ReTryInterval,reSetCountDay:   request.ReSetCountDay,reSetCountWeek:  request.ReSetCountWeek,reSetCountMonth: request.ReSetCountMonth,}go cache.loadFunc(ctx, cache)
}func (c *ICache) loadFunc(ctx context.Context, cache *ICache) {defer func() {if x := recover(); x != nil {log.Println("x--->", string(debug.Stack()))}}()
SyncLoop:for {res := cache.callback(cache.params)//	fmt.Println("totalCount-->", res.TotalCount, "one-->", res.ResultOne, "list-->", res.ResultList)temp := 3var err errorfor temp > 0 {res = cache.callback(cache.params)if res.error == nil {break}err = res.errortime.Sleep(time.Second * 3)fmt.Println("每隔3秒重试一次:", cache.key)temp--}if temp == 0 && err != nil {ctx.Done()fmt.Println("重试失败,结束重试!!!", err.Error())break}if res.error != nil {log.Printf("查询失败")ctx.Done()break}if err := c.SetVal(cache.key, res); err != nil {ctx.Done()break}select {case <-time.After(cache.executeInterval):log.Default().Printf("%s", "下一次循环")case <-ctx.Done():log.Printf("协程处理失败")break SyncLoop}}
}func setVal(key string, response *Value) {cacheLock.Lock()LocalCache[key] = responsecacheLock.Unlock()
}func getVal(key string) *Value {return LocalCache[key]
}func delVal(key string) {cacheLock.Lock()delete(LocalCache, key)cacheLock.Unlock()
}

protol

// LoadRequest 缓存设置包结构
// CallBack 执行加载函数
// TTL 过期时间
// Key 缓存key
// CallBackParams callback 请求参数
// ReTry 重试次数
// ReTryInterval 重试时间间隔
// ReTryInterVal 执行异常重试间隔
// ReSetCountDay 统计重置函数更新的次数,用来判断改数据的操作是否频繁 包含day、week、month 维度
// ReSetCountWeek week 维度次数统计
// ReSetCountMonth 月统计维度
type LoadRequest struct {Callback        CallBackFuncTTL             time.DurationKey             stringCallBackParams  map[string]interface{}ReTry           intReTryInterval   time.DurationReSetCountDay   intReSetCountWeek  intReSetCountMonth int
}// SingleRequest 不需要定时加载,直接set 缓存
type SingleRequest struct {Key   string        `json:"key"`Value *Value         `json:"value"`TTL   time.Duration `json:"ttl"`
}// Value 设置缓存结构体
type Value struct {TotalCount int64ResultList []map[string]interface{}ResultOne  map[string]interface{}error      error
}

test 测试

func TestSetAndGet(t *testing.T) {err := Set(&SingleRequest{Key: "key1", Value: &Value{TotalCount: 1, ResultList: nil, ResultOne: map[string]interface{}{"1": 1}, error: nil}})if err != nil {fmt.Println(err.Error())return}fmt.Println("key1缓存设置成功")res := NewCache().GetVal("key1")fmt.Println("key1最终结果", res.TotalCount, "  ", res.ResultOne, "  ", res.ResultList)
}func TestLoad(t *testing.T) {LoadFunc(&LoadRequest{Callback: NewPerson().QueryPersonInfo,Key:      "person",})res := Get("person")fmt.Println("person--------->",res)time.Sleep(1 * time.Second)res = Get("person")fmt.Println("person--------->",res)
}type Person struct {
}func NewPerson() *Person {return &Person{}
}
func (person *Person) QueryPersonInfo(params map[string]interface{}) *Value {//db.Queryp := map[string]interface{}{"id": 1, "name": "zdm", "age": 18, "sex": "man"}return &Value{TotalCount: 1, ResultOne: p}
}

测试效果
go实现一个本地缓存
go实现一个本地缓存