大家好,我是洋子,今天分享一下在做压测时遇到的很有意思的性能问题以及对应的排查解决方案。这个性能问题的现象为,Redis线上实例不可用,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
,另外这个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实例不可用的性能问题