缓存就是数据交换的缓冲区(称作Cache),目的就是提高我们的接口性能,特别是那些需要大量CPU计算和I/O获取的数据。
缓存虽然能够提高应用程序的性能,但也会带来一些问题。比如:缓存失效,缓存击穿,缓存雪崩,数据一致性问题
缓存失效为什么会带来问题呢?试想一下,单个的缓存失效其实并不会引发多大的问题,问题在于当大量的Key同时失效时,在高并发的情况下,大量的请求同时到数据库层,会给数据库层带来压力,从而引发其他的问题。
既然是同时失效,那么我们只需要在Key的失效时间上再加上一个随机时间就好了,也就是失效时间 + 随机时间。go-zero 上已经有相关的代码,我简单摘抄出来看下
// A Unstable is used to generate random value around the mean value base on given deviation.
type Unstable struct {
deviation float64
r *rand.Rand
lock *sync.Mutex
}
// AroundDuration returns a random duration with given base and deviation.
func (u Unstable) AroundDuration(base time.Duration) time.Duration {
u.lock.Lock()
val := time.Duration((1 + u.deviation - 2*u.deviation*u.r.Float64()) * float64(base))
u.lock.Unlock()
return val
}
采用多级缓存,不同级别缓存设置的超时时间不同,及时某个级别缓存都过期,也有其他级别缓存兜底。代码如下,完整代码见:cache_redis.go
func (r *RedisCacheClient) Get(ctx context.Context, key string, fetch fetchFunc) (result []byte, err error) {
var byteValue []byte
fullKey := getFullKey(r.prefix, key)
fullKeyByte, _ := json.Marshal(fullKey)
if val, err := r.localCache.Get(fullKeyByte); err == nil {
r.status.IncrementLocalCacheHit()
return val, nil
}
r.status.IncrementLocalCacheMiss()
startTime := time.Now()
byteValue, err = r.client.Get(fullKey).Bytes()
elapsed := time.Since(startTime).Milliseconds()
for _, p := range r.plugins {
p.OnGetRequestEnd(ctx, cmdGet, elapsed, fullKey, err)
}
// 数据源拉取原始数据
........
}
对于某些key设置了过期时间,但是其是热点数据,如果某个key失效,可能大量的请求打过来,缓存未命中,然后去数据库访问,此时数据库访问量会急剧增加。
我们可以设置多级缓存,每一级缓存失效时间不一样,某个级别缓存过期,也有其他级别缓存兜底。而且再加上singleflight 限制,就可以做每一个服务实例只有一个请求最终到数据库源上,大大减轻了数据源压力
缓存穿透是指查询的数据在数据库是没有的,那么在缓存中自然也没有,所以,在缓存中查不到就会去数据库取查询,这样的请求一多,那么我们的数据库的压力自然会增大。
我们通常说的数据一致性指的是在程序运行过程中本地缓存、分布式缓存、mysql数据库三者之间的数据一致性
先进行缓存清除,再执行 update sql,最后(延迟 N 秒)再执行缓存清除。
上述中(延迟 N 秒)的时间要大于一次写操作的时间,一般为 3-5 秒。
1.更新 db 数据,同时写入数据到 redis
2.启动一个定时任务定时将 db 数据同步到 redis
热key是服务端的常见问题,指一段时间内某个key的访问量远远超过其他的key,导致大量访问流量落在某一个redis实例中;或者是带宽使用率集中在特定的key
以被请求频率来定义是否是热key,没有固定经验值。某个key被高频访问导致系统稳定性变差,都可以定义为热key。
提供单独的热 key 检测的接入 sdk,应用系统引入该 sdk 后,热 key 检测系统自动计
算是否热 key 并推送相关结果给应用系统,应用系统根据业务实际情况进行相应处理。
改写 Redis SDK,记录每个请求,定时把收集到的数据上报,然后由一个统一的服务进行聚合计算。
在你发现热 key 以后,把热 key 加载到系统的内存中。针对这种热 key 请求,会直接从内存中取,而不会走到 redis 层。
大key是指当redis的字符串类型占用内存过大或非字符串类型元素数量过多
生产环境中,综合衡量运维和环境的情况,给大key定义参考值如下:
我们可以通过在Redis 客户端上实时统计出大Key,直接计算出Key对应的Value值大小就可以,例如
// b 为序列化之后的数据
b, err := utils.Serialize(value, c.getSerializer())
if err != nil {
return err
}
// var b []byte
// 长度
reqSize = len(b)
// 10KB
bigKey := 1024 * 10
if reqSize > bigKey {
}
对Redis的RDB备份文件进行定制化的分析,帮助您发现实例中的大Key,掌握Key在内存中的占用和分布
Redis提供了bigkeys参数能够使redis-cli以遍历的方式分析Redis实例中的所有Key,并返回Key的整体统计信息与每个数据类型中Top1的大Key,bigkeys仅能分析并输入六种数据类型(STRING、LIST、HASH、SET、ZSET、STREAM),命令示例为redis-cli -h 127.0.0.1 -p 6379 --bigkeys
优点:可对历史备份数据进行分析,对线上服务无影响。
缺点:时效性差,RDB文件较大时耗时较长。