Redis

关注公众号 jb51net

关闭
首页 > 数据库 > Redis > Redis热key与大key

一文了解发现并解决Redis热key与大key问题

作者:CoLiuRs

热key是服务端的常见问题,本文主要介绍Redis热key与大key问题的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

使用缓存

为什么使用缓存

缓存就是数据交换的缓冲区(称作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

我们可以设置多级缓存,每一级缓存失效时间不一样,某个级别缓存过期,也有其他级别缓存兜底。而且再加上singleflight 限制,就可以做每一个服务实例只有一个请求最终到数据库源上,大大减轻了数据源压力

缓存穿透

缓存穿透是指查询的数据在数据库是没有的,那么在缓存中自然也没有,所以,在缓存中查不到就会去数据库取查询,这样的请求一多,那么我们的数据库的压力自然会增大。

解决方案

设置Null值

数据过滤

数据一致性问题

我们通常说的数据一致性指的是在程序运行过程中本地缓存、分布式缓存、mysql数据库三者之间的数据一致性

本地缓存与DB保持一致

解决方案

MQ 方案

Redis与DB保持一致

基于 binlog 方案

延迟双删方案

先进行缓存清除,再执行 update sql,最后(延迟 N 秒)再执行缓存清除。
上述中(延迟 N 秒)的时间要大于一次写操作的时间,一般为 3-5 秒。

基于定时任务方案

1.更新 db 数据,同时写入数据到 redis
2.启动一个定时任务定时将 db 数据同步到 redis

热key和大key问题

热key

热key是服务端的常见问题,指一段时间内某个key的访问量远远超过其他的key,导致大量访问流量落在某一个redis实例中;或者是带宽使用率集中在特定的key

以被请求频率来定义是否是热key,没有固定经验值。某个key被高频访问导致系统稳定性变差,都可以定义为热key。

可能造成的问题

发现方法

开发独立的热 key 检测系统

提供单独的热 key 检测的接入 sdk,应用系统引入该 sdk 后,热 key 检测系统自动计
算是否热 key 并推送相关结果给应用系统,应用系统根据业务实际情况进行相应处理。

改写 redis 客户端收集上报数据

改写 Redis SDK,记录每个请求,定时把收集到的数据上报,然后由一个统一的服务进行聚合计算。

解决方案

利用本地缓存

在你发现热 key 以后,把热 key 加载到系统的内存中。针对这种热 key 请求,会直接从内存中取,而不会走到 redis 层。

大key

大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 {
    
}

离线全量Key分析

解决方案

到此这篇关于一文了解发现并解决Redis热key与大key问题的文章就介绍到这了,更多相关Redis热key与大key内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

您可能感兴趣的文章:
阅读全文