Redis性能问题排查过程
先来看一下问题代码(Go语言实现),这段代码的含义为先从Redis当中读取数据,如果Redis里没有数据,则访问DB获取数据,获取到数据后再Set Redis缓存,便于下次访问直接从Redis 获取数据,减轻数据库压力
func GetInfoByCache(key string, expired int) interface{} { strData, errCache := GetCacheData(ctx, key, expired) if errCache == nil { return strData } ret := GetInfoFromOther(params) //访问数据库 strJson, errJson := jsoniter.MarshalToString(ret[0].Interface()) if errJson == nil { errSet := SetCacheData(ctx, key, strJson) if errSet != nil { ctx.Warning(errSet) } } return ret } //调用该方法,传参当中的Redis Key只有一个,为固定值 res:=GetInfoByCache("Redis_Key_Name",60)
问题产生原因
如果是熟悉编程的小伙伴,应该知道上述业务逻辑是运用Redis缓存很基本的操作,即使是在高并发情况下,Redis 实例一般也能扛住,那问题到底出在哪里呢?
有两个前置条件
第一个前置条件,调用GetInfoByCache方法时,使用到的Redis Key是一个大Key
在Redis中,"大Key"通常指的是存储在数据库中的一个占用相对较大内存空间的键值对。当一个键值对的值非常大时,它可能会被称为大Key。这可能会对Redis服务器的内存占用过大、影响性能等负面影响,因此需要谨慎处理。
Redis大Key不是指存储在Redis中的某个Key(键)的大小超过一定的阈值,而是该 Key(键)所对应的value(值)过大。
另外一个前置条件是,这个Redis的Key为固定值,在高并发条件下会成为一个热Key
Redis的"热Key"(Hot Key)指的是 在一个Redis数据库中,某些特定的键(Key)被频繁地访问或者执行操作,导致这些键成为数据库中的热点。这通常是因为这些键存储了特别热门的数据,被大量的读取或写入操作所影响。
热Key可能对Redis的性能和稳定性产生负面影响,因为它们引起了数据库中的高并发访问。当某个Key变得热门时,可能会导致以下一些问题:
- 性能瓶颈:大量的读取或写入操作集中在一个热Key上,导致该Key所在的分片或节点成为性能瓶颈,因为它承受了大量的请求负担。
- 响应延迟:其他未热的Key可能因为竞争数据库资源而经历响应延迟,影响整体性能。
- 内存占用:热Key可能占用大量内存,导致Redis实例的内存使用率升高。如果Redis的内存用尽,可能导致LRU(最近最少使用)策略被触发,清除部分数据,进而影响业务。
在进行压测时,在Redis 大Key和热Key的加持下,压测到达指定的QPS就会发生下面的性能问题:
首先读取Redis开始出现失败,读Redis失败必然会进行访问数据库,并写入Redis,但写Redis又是写大Key,写入超时失败,再次影响Redis读请求,越来越多的Redis读请求失败,最终造成Redis的实例都不可用。
解决方案
跟开发讨论后,制定了以下6种问题的解决方案,权衡成本和风险,最终采用了第2种方案:
将Redis 的Key进行打散,这样就能解决Redis 热Key的问题,Redis读写请求可以落到不同的Redis分片上,进而解决了Redis实例不可用的性能问题。