本地+redis多级缓存
由一次线上问题引发的思考,本地缓存+redis缓存的多级缓存方案。 一次线上发生OOM, CPU占用高排查,之前所有的缓存都是放在redis中,并发高时,大量请求去redis,服务反序列化,导致CPU占用高,内存占用高,最终达到资源上线被k8s杀掉。 后来想到了使用本地缓存,这种公共对象保存一份数据,不用反复序列化,减少CPU占用,减少内存占用,但是本地缓存有一个问题,多个副本在同一时间可能缓存数据不一致,虽然在我们这个场景下,这份公共数据更新不频繁,但是也有可能发生这个情况,所以想到了使用多级缓存,本地缓存+redis缓存,本地缓存作为一级缓存,redis作为二级缓存,当redis更新时会设置数据版本号(时间戳),本地获取时会比对版本号,如果相同redis就不返回数据,如果不同就返回数据,这样就可以保证数据一致性。 用go描述一下取值和设置值的逻辑,其他逻辑都比较简单,这里使用lua脚本来实现,版本和数据的原子性和比较版本和返回相应的返回值,还可以减少网络开销 package main import ( "fmt" "time" "github.com/go-redis/redis" ) //这段脚本是用来设置值的,设置值的时候会设置版本号,设置过期时间 const setval = ` local key_val = KEYS[1] local key_version = key_val .. "_version" local val = ARGV[1] local expire = ARGV[2] local version = ARGV[3] redis.call('SET', key_version, version) redis.call('EXPIRE', key_version, expire) redis.call('SET', key_val, val) redis.call('EXPIRE', key_val, expire) return nil ` //这段脚本是用来取值的,取值的时候会比较版本号,如果版本号不一致就返回值 const getval = ` local key_val = KEYS[1] local key_version = key_val .. "_version" local givenVersion = ARGV[1] local version = redis.call('GET', key_version) if not version then return nil end if version ~= givenVersion then local value1 = redis.call('GET', key_val) return {version, value1} end return {version} ` func main() { redisClient := redis.NewClient(&redis.Options{ Addr: "xxx", Password: "xxx", DB: 0, }) cmd := redisClient.Eval(getval, []string{"ab"}, "1") if cmd.Err() != nil { fmt.Println(cmd.Err()) } val := cmd.Val() fmt.Println(val) cmd = redisClient.Eval(setval, []string{"ab"}, time.Now().UnixNano(), 100, time.Now().Unix()) if cmd.Err() != nil && cmd.Err() != redis.Nil { fmt.Println(cmd.Err()) } } 说一下总体的思路 使用一个localstorage, redis, redis lua, singleflight ...