缓存设计思路
- 缓存粒度:根据函数输入参数确定缓存粒度,由于输入包含结构体且含指针类型字段,需精确到每个不同的输入组合。
- 版本控制:引入版本号机制,每次缓存数据更新时,版本号递增。不同服务通过版本号判断缓存是否为最新。
- 热更新:采用双缓存机制,一份缓存用于读取,另一份用于更新。更新完成后切换读写缓存,减少缓存更新时的服务中断。
- 跨服务一致性:使用分布式缓存(如Redis),各服务通过操作同一缓存实例保证一致性。同时引入消息队列(如Kafka),当缓存数据更新时,发布更新消息通知其他服务更新本地缓存副本(若有)。
数据结构选型
- 缓存存储:在Go中,使用
map
作为本地缓存数据结构,因为它提供快速的键值查找。对于分布式缓存,选用Redis,因其具备高并发读写能力和丰富的数据结构。
- 缓存键:将函数输入参数进行序列化(如使用
encoding/json
)后作为缓存键。对于结构体含指针类型字段,确保指针指向的数据也被序列化,以保证不同输入组合的唯一性。
- 缓存值:直接存储函数返回结果,若返回结果较大,可考虑存储结果的引用(如哈希值),实际数据存储在其他持久化存储中。
关键代码实现
- 本地缓存
package main
import (
"encoding/json"
"fmt"
)
// 假设这是复杂业务逻辑函数
func complexBusinessLogic(input interface{}) interface{} {
// 实际业务逻辑
return "result"
}
var localCache = make(map[string]interface{})
var cacheVersion = 0
func getFromCache(input interface{}) (interface{}, bool) {
key, err := json.Marshal(input)
if err != nil {
return nil, false
}
result, ok := localCache[string(key)]
return result, ok
}
func setToCache(input interface{}, result interface{}) {
key, err := json.Marshal(input)
if err != nil {
return
}
localCache[string(key)] = result
cacheVersion++
}
func callComplexFunction(input interface{}) interface{} {
if result, ok := getFromCache(input); ok {
return result
}
result := complexBusinessLogic(input)
setToCache(input, result)
return result
}
- 分布式缓存(以Redis为例)
package main
import (
"encoding/json"
"fmt"
"github.com/go-redis/redis/v8"
"context"
)
var rdb *redis.Client
var ctx = context.Background()
func init() {
rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
}
func getFromRedisCache(input interface{}) (interface{}, bool) {
key, err := json.Marshal(input)
if err != nil {
return nil, false
}
result, err := rdb.Get(ctx, string(key)).Result()
if err != nil {
if err == redis.Nil {
return nil, false
}
return nil, false
}
return result, true
}
func setToRedisCache(input interface{}, result interface{}) {
key, err := json.Marshal(input)
if err != nil {
return
}
rdb.Set(ctx, string(key), result, 0)
// 发布版本更新消息(假设使用消息队列未实现)
}
func callComplexFunctionWithRedis(input interface{}) interface{} {
if result, ok := getFromRedisCache(input); ok {
return result
}
result := complexBusinessLogic(input)
setToRedisCache(input, result)
return result
}
可能遇到的挑战及应对措施
- 序列化性能问题:输入参数序列化可能影响性能。应对措施是选择高效的序列化库(如
encoding/gob
),或对经常使用的输入参数缓存序列化结果。
- 缓存穿透:高并发下,不存在的键可能每次都穿透到后端函数。使用布隆过滤器(Bloom Filter)来快速判断键是否存在,减少无效查询。
- 缓存雪崩:大量缓存同时过期可能压垮后端服务。设置缓存过期时间时添加随机因子,避免集中过期。
- 跨服务缓存一致性延迟:消息队列通知其他服务更新缓存可能存在延迟。设置合理的缓存过期时间,在过期前允许一定的不一致,同时优化消息队列性能,减少延迟。