Redis常见问题和解决

Published: 01 Sep 2020 Category: redis

#一、Redis大key问题

1.1 Redis大key的一些场景及问题

大key场景

Redis使用者应该都遇到过大key相关的场景,比如:

1、热门话题下评论、答案排序场景。

2、大V的粉丝列表。

3、使用不恰当,或者对业务预估不准确、不及时进行处理垃圾数据等。

大key问题

由于Redis主线程为单线程模型,大key也会带来一些问题,如:

1、集群模式在slot分片均匀情况下,会出现数据和查询倾斜情况,部分有大key的Redis节点占用内存多,QPS高。

2、大key相关的删除或者自动过期时,会出现qps突降或者突升的情况,极端情况下,会造成主从复制异常,Redis服务阻塞无法响应请求。大key的体积与删除耗时可参考下表:

key类型  field数量 耗时
Hash 100万 1000ms
List 100万 1000ms
Set 100万 1000ms
Sorted Set 100万 1000ms

操作大key的时候(创建和删除),会引发阻塞。

Redis 4.0之前的大key的发现与删除方法:

1、redis-rdb-tools工具。redis实例上执行bgsave,然后对dump出来的rdb文件进行分析,找到其中的大KEY。(实测发现,不管是执行时间还是准确度都是很高的,一个3G左右的rdb文件,执行完大概两三分钟,直接导出到csv文件,方便查看,个人推荐使用该工具去查找大key。)

工具地址: https://github.com/weiyanwei412/rdb_bigkeys

2、redis-cli –bigkeys命令。可以找到某个实例5种数据类型(String、hash、list、set、zset)的最大key。

redis-cli –bigkeys的优点是可以在线扫描,不阻塞服务;缺点是信息较少,内容不够精确。扫描结果中只有string类型是以字节长度为衡量标准的。List、set、zset等都是以元素个数作为衡量标准,元素个数多不能说明占用内存就一定多。(很明显,这并不能帮助我们去发现整个数据里的大key,所以一般不使用,执行后如下图:)

3、自定义的扫描脚本,以Python脚本居多,方法与redis-cli –bigkeys类似。

4、debug object key命令。可以查看某个key序列化后的长度,每次只能查找单个key的信息。官方不推荐。

Redis 4.0之后的大key的发现与删除方法

Redis 4.0引入了memory usage命令和lazyfree机制,不管是对大key的发现,还是解决大key删除或者过期造成的阻塞问题都有明显的提升。

memory usage是通过调用objectComputeSize来计算key的大小。

我们可以通过Python脚本在集群低峰时扫描Redis,用较小的代价去获取所有key的内存大小。以下为部分伪代码,可根据实际情况设置大key阈值进行预警。

forkeyinr.scan_iter(count=1000):redis-cli =’/usr/bin/redis-cli’configcmd =’%s -h %s -p %s memory usage %s’% (redis-cli, rip,rport,key)        keymemory = commands.getoutput(configcmd)

Lazyfree的原理是在删除的时候只进行逻辑删除,把key释放操作放在bio(Background I/O)单独的子线程处理中,减少删除大key对redis主线程的阻塞,有效地避免因删除大key带来的性能问题。在此提一下bio线程,很多人把Redis通常理解为单线程内存数据库, 其实不然。Redis将最主要的网络收发和执行命令等操作都放在了主工作线程,然而除此之外还有几个bio后台线程,从源码中可以看到有处理关闭文件和刷盘的后台线程,以及Redis4.0新增加的lazyfree线程。

2. 避免

2.1 分拆

每次都是整存整取——

这种操作一般都是每次整存整取,这种情况可以尝试将对象拆分成多个key-value,使用multiGet获取值,这样分拆意义在于分拆操作的压力,将操作压力平摊到多个redis实例,降低对于单个redis的io压力。

每次只存取部分数据——

同样可以拆成几个key-value,也可以将这些存储在一个hash中,每个field代表具体属性,使用hget,hmget来获取部分value,使用hset,hmset来更新部分属性。

hash,set,zset,list中存储过多数据——

同样可以将这部分元素拆分,以hash为例,正常的流程是:hget(hashKey, field);hset(hashKey, field, value)。 现在可以固定一个桶数量,比如1w,每次存取的时候,先在本地计算field的hash值,对1w取模,确定field落在哪个key上。

newHashKey = hashKey + ( hash(field) % 10000); hset (newHashKey, field, value) ; hget(newHashKey, field)

set,zset,list做法类似。

一个集群存储了上亿的key——

如果key本身没有相关性,可以预估总量,对一些场景预分固定的桶数量。

比如有2亿key,按照一个hash存储100个field来算,需要200w个桶,这样可以按照200w个固定桶数量做取模,hash(123456) % 200w,比如算出3个key的桶分别是1,2,3。调用存储api hset(key, field, value),读取时用hget(key,field)。 建议分桶数量100合适。

二、缓存雪崩

当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。

如何避免? 1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。 2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期 3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。 4:定时更新缓存策略:实效性要求不高的缓存,容器启动初始化加载,采用定时任务更新或移除缓存

三、缓存穿透

一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。

如何避免? 1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。 2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。

四、Redis的同步机制了解么?

Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。

3.2 缓存击穿

在平常高并发的系统中,大量的请求同时查询一个key时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去。这种现象我们称为缓存击穿。

会造成某一时刻数据库请求量过大,压力剧增。

如何解决?

上面的现象是多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它。

其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。

五、Keys和Scan命令

假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?

使用keys指令可以扫出指定模式的key列表。

对方接着追问:如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?

这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

六、redis通讯协议RESP

RESP 是redis客户端和服务端之前使用的一种通讯协议; RESP 的特点:实现简单、快速解析、可读性好

For Simple Strings the first byte of the reply is "+" 回复
For Errors the first byte of the reply is "-" 错误
For Integers the first byte of the reply is ":" 整数
For Bulk Strings the first byte of the reply is "$" 字符串
For Arrays the first byte of the reply is "*" 数组

#REF

如何解决Redis大key问题,看这一篇就够了!
Redis 常见问题