缓存的目的都是为了减少跟数据库的直接交互,提高可用性。常用的方法如,对热点数据缓存、对部分数据预加载、对频繁操作的数据放到缓存中操作等等。
在开发的过程中,我尝试了一种自动处理缓存的方法,记录下,以供参考。
在业务代码,和ORM之间,引入一个模块(SaSql)。将对于数据库的操作,分为单行操作、list操作,即读写单条记录,和读写多条记录。
以list为例:
SaSql获取list数据时,优先从缓存拿,没有则通过ORM从数据库拿。核心代码示意如下:
//读取缓存
cacheParams := &map[string]string{}
if ignoreCache == false {
saData.Merge(cacheParams, pk)
saData.Merge(cacheParams, ma)
if err = saCache.ListGet(tblName, cacheParams, aryPtr); err == nil && aryPtr != nil {
return nil
} else {
if onlyCache {
return err
}
}
}
//读取数据库
if err := session.Find(aryPtr); err == nil {
//写入缓存
if ignoreCache == false {
saCache.ListSet(tblName, cacheParams, aryPtr)
}
return nil
} else {
return err
}
示例中,saCache则是缓存处理,其中,将参数以k_v的形式组合成字符串,作为Redis缓存的key。假如获取同一张表的list,但是参数不同,则会缓存成两条记录。
该方案只是缓存的补充手段,对于缓存数量要有限制,因为同一条数据,可能会出现在多条缓存记录中(因为不同参数都会单独缓存),会出现较多的重复数据,浪费空间。
示例代码中,对分页数据,只缓存前2页数据,就是基于次点考虑。
以下为获取缓存Key的示意代码:
func listKey(tbl string, params *map[string]string) (key string) {
if tbl == "" || params == nil {
return ""
}
key = revel.AppName + "_" + tbl + "_list_"
var keyAry []string
for k := range *params {
keyAry = append(keyAry, k)
}
sort.Strings(keyAry)
for i, k := range keyAry {
key += saUtils.SnakeStr(k) + "=" + (*params)[k]
if i+1 < len(keyAry) {
key += "_"
}
}
return
}
此方案的优点是,可以应对突发或者意向之外的流量,作为主要缓存策略的一个补充。
再者,每次取数据,都会先走缓存,而Redis是单线程(不考虑最新版本的),对于请求会自动做排队,也会降低数据库压力。
附上基于此方案的压测结果
Server Software:
Server Hostname: xxx
Server Port: 443
SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128
Document Path: xxx
Document Length: 23 bytes
Concurrency Level: 100
Time taken for tests: 484.310 seconds
Complete requests: 250000
Failed requests: 1
(Connect: 0, Receive: 0, Length: 1, Exceptions: 0)
Write errors: 0
Non-2xx responses: 249999
Total transferred: 155999376 bytes
HTML transferred: 5749977 bytes
Requests per second: 516.20 [#/sec] (mean)
Time per request: 193.724 [ms] (mean)
Time per request: 1.937 [ms] (mean, across all concurrent requests)
Transfer rate: 314.56 [Kbytes/sec] received
最后安利一个自己写的小程序,不喜请忽略
欢迎加微信交流 yf_good134