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

 更新时间:2024年05月12日 16:00:52   作者: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值

  • 约定:对于返回为Null的依然缓存,对于抛出异常的返回不进行缓存,注意不要把抛异常的也给缓存了。采用这种手段的会增加我们缓存的维护成本,需要在插入缓存的时候删除这个空缓存,当然我们可以通过设置较短的超时时间来解决这个问题。

数据过滤

  • 小数据用BitMap,大数据可以用布隆过滤器

数据一致性问题

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

本地缓存与DB保持一致

解决方案

MQ 方案

  • 应用实例1收到请求,更新 db,同时更新应用自己的本地缓存.
  • 应用实例1 发送更新 mq 广播消息.
  • 应用 实例2 和应用实例3 收到消息,查询 db,更新本地缓存.
  • 这个时候应用实例1,2,3与 DB 数据就保持一致

Redis与DB保持一致

基于 binlog 方案

  • 更新 db 数据
  • 监听 mysql binlog, 并写入到MQ
  • 启动一个数据处理应用,消费 MQ 数据并进行数据加工
  • 将加工后的数据写入 redis
  • 查询 redis 数据返回

延迟双删方案

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

基于定时任务方案

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

热key和大key问题

热key

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

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

可能造成的问题

  • 占用大量的CPU资源,影响其他请求并导致整体性能降低。
  • 集群架构下,产生访问倾斜,即某个数据分片被大量访问,而其他数据分片处于空闲状态,可能引起该数据分片的连接数被耗尽,新的连接建立请求被拒绝等问题。
  • 在抢购或秒杀场景下,可能因商品对应库存Key的请求量过大,超出Redis处理能力造成超卖。
  • 热Key的请求压力数量超出Redis的承受能力易造成缓存击穿,即大量请求将被直接指向后端的存储层,导致存储访问量激增甚至宕机,从而影响其他业务。

发现方法

开发独立的热 key 检测系统

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

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

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

解决方案

利用本地缓存

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

  • 优点:内存访问和 redis 访问的速度不在一个量级,基于本地缓存,接口性能非常好, 可以
    大大增加单实例的 QPS。
  • 缺点:受应用内存限制,容量有限,数据量非常大的时候,占用太多内存,不太适合。部分热点数据,需要提前预知。热点数据自动检测有一定的延迟,系统短时间内承受的风险比较大。

大key

大key是指当redis的字符串类型占用内存过大或非字符串类型元素数量过多

生产环境中,综合衡量运维和环境的情况,给大key定义参考值如下:

  • string类型的key超过10KB
  • hash/set/zset/list等数据结构中元素个数大于5k/整体占用内存大于10MB

可能造成的问题

  • 客户端执行命令的时长变慢。
  • Redis内存达到maxmemory参数定义的上限引发操作阻塞或重要的Key被逐出,甚至引发内存溢出(Out Of Memory)。
  • 集群架构下,某个数据分片的内存使用率远超其他数据分片,无法使数据分片的内存资源达到均衡。
  • 对大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序列化长度并不等同于它在内存空间中的真实长度,因此不够准确,仅可作为参考。

离线全量Key分析

  • 对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文件较大时耗时较长。

解决方案

  • 业务拆分,将key的含义更细粒度化,避免大key出现。
  • 数据结构上拆分。如果大key是个大json,可以通过mset的方式,将这个key的内容打散到各个实例中,减小大key对数据量倾斜的影响;如果是大list,可以拆成list_1,list_2,list_N;其他数据结构同理。(可以考虑增加单独key存储大key被拆分的个数或元数据信息)
  • 对于长文本,更建议使用文档型数据库例如MongoDB等。
  • 对一致性要求不高的场景,尝试使用客户端缓存。(只解决了redis的阻塞问题,但机器或局域网的带宽问题没有改善)
  • 对大key的压缩。相当于用cpu资源来降低网络io,其中google提出的snappy算法较常用。
  • 对于hash等数据结构,需要注意业务是否可以引入定期清理无效field的机制。
    • Hash 结构不建议使用,没有办法对具体的Key做过期时间设置,只能再额外开发功能去做,增加开发成本

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

相关文章

  • 利用Redis实现SQL伸缩的方法

    利用Redis实现SQL伸缩的方法

    本文主要介绍了如何通过锁和时间序列等方面来提升传统数据库的性能等方法,利用Redis实现SQL伸缩,供有需要的朋友们参考。
    2015-09-09
  • 如何自定义redis工具jar包供其他SpringBoot项目直接使用

    如何自定义redis工具jar包供其他SpringBoot项目直接使用

    这篇文章主要介绍了如何自定义redis工具jar包供其他SpringBoot项目直接使用,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • redis客户端连接错误 NOAUTH Authentication required

    redis客户端连接错误 NOAUTH Authentication required

    本文主要介绍了redis客户端连接错误 NOAUTH Authentication required,详细的介绍了解决方法,感兴趣的可以了解一下
    2021-07-07
  • CentOS系统中Redis数据库的安装配置指南

    CentOS系统中Redis数据库的安装配置指南

    Redis是一个基于主存存储的数据库,性能很强,这里我们就来看一下CentOS系统中Redis数据库的安装配置指南,包括将Redis作为系统服务运行的技巧等,需要的朋友可以参考下
    2016-06-06
  • Redis数据结构原理浅析

    Redis数据结构原理浅析

    这篇文章主要为大家介绍了Redis数据结构原理浅析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • redis实现session共享的方法

    redis实现session共享的方法

    本文主要介绍了redis实现session共享的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • WINDOWS中REDIS主从配置实现代码解析

    WINDOWS中REDIS主从配置实现代码解析

    这篇文章主要介绍了WINDOWS中REDIS主从配置实现代码解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • Redis 搭建主从集群的操作指南

    Redis 搭建主从集群的操作指南

    单节点的 Redis 并发能力有限,要进一步提高 Redis 的并发能力,就需要搭建主从集群,实现读写分离,这篇文章主要给大家介绍了Redis搭建主从集群的操作指南,需要的朋友可以参考下
    2023-08-08
  • redis protocol通信协议及使用详解

    redis protocol通信协议及使用详解

    这篇文章主要为大家介绍了redis protocol通信协议及使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • Redis执行Lua脚本的好处与示例代码

    Redis执行Lua脚本的好处与示例代码

    Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。下面这篇文章主要给大家介绍了关于Redis执行Lua脚本的好处与示例代码,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2018-10-10

最新评论