背景
双十一大促期间, 收到客服反馈通知,说 APP 领券接口缓慢。找到一个case,通过调用链路发现,是操作redis 缓慢,并且还搜到一些redis 异常。
最后定位到原因:是发券场景下拿redis 做了一个缓存券批次的操作,记录用户当天领取的所有券批次
发券场景: key = userId, value = 券批次ID 列表, 而redis 查询发现多了许多大key,体现在 一个用户领取的几千甚至上万张优惠券,导致Redis 查询缓慢,甚至异常。至于为何有的用户会领取这么多优惠券呢。联系风控发现,这些个用户是来薅羊毛的,但是风控没有拦截到,导致服务这边出现异常。
虽说最终原因不是我的问题,但是Redis 大 key 问题还是比较有意思的,下面我们就来一起认识下 大 key 问题对 Redis 的影响。
什么是大key
所谓的大key问题是某个key的value比较大,所以本质上是大value问题。key往往是程序可以自行设置的,value往往不受程序控制,因此可能导致value很大。
设想一种场景:
在线音乐app中,某个歌单有很多用户收藏,假如有这样的数据结构:
image.png
redis的key是歌单ID,redis的value是个list,list包含了用户ID , 用户可能很多,就导致list长度不可控.
大key有什么影响
我们都知道,redis的一个典型特征就是:核心工作线程是单线程。
单线程中请求任务的处理是串行的,前面完不成,后面处理不了,同时也导致分布式架构中内存数据和CPU的不平衡。
执行大key命令的客户端本身,耗时明显增加,甚至超时
执行大key相关读取或者删除操作时,会严重占用带宽和CPU,影响其他客户端
大key本身的存储带来分布式系统中分片数据不平衡,CPU使用率也不平衡
大key有时候也是热key,读取操作频繁,影响面会很大
执行大key删除时,在低版本redis中可能阻塞线程
这样看来大key的影响还是很明显的,最典型的就是阻塞线程,并发量下降,导致客户端超时,服务端业务成功率下降。
大key是如何产生的
大key的产生往往是业务方设计不合理,没有预见vaule的动态增长问题:
一直往value塞数据,没有删除机制,迟早要爆炸
数据没有合理做分片,将大key变成小key
如何找到大key
增加内存&流量&超时等指标监控
由于大key的value很大,执行读取时可能阻塞线程,这样Redis整体的qps会下降,并且客户端超时会增加,网络带宽会上涨,配置这些报警可以让我们发现大key的存在。
bigkeys命令
使用bigkeys命令以遍历的方式分析Redis实例中的所有Key,并返回整体统计信息与每个数据类型中Top1的大Key
image.png
redis-rdb-tools
使用redis-rdb-tools离线分析工具来扫描RDB持久化文件,虽然实时性略差,但是完全离线对性能无影响。
redis-rdb-tools是由Python写的用来分析Redis的rdb快照文件用的工具,它可以把rdb快照文件生成json文件或者生成报表用来分析Redis的使用详情。
集成化可视化工具
基于某些公有云或者公司内部架构的redis一般都会有可视化的页面和分析工具,来帮助我们定位大key,当然页面底层也可能是基于bigkeys或者rdb文件离线分析的结果。
如何解决大key问题
根据大key的实际用途可以分为两种情况:可删除和不可删除。
可删除
image.png
如果发现某些大key并非热key就可以在DB中查询使用,则可以在Redis中删掉:
当Redis版本大于4.0时,可使用UNLINK命令安全地删除大Key,该命令能够以非阻塞的方式,逐步地清理传入的Key。
Redis UNLINK 命令类似与 DEL 命令,表示删除指定的 key,如果指定 key 不存在,命令则忽略。
UNLINK 命令不同与 DEL 命令在于它是异步执行的,因此它不会阻塞。
UNLINK 命令是非阻塞删除,非阻塞删除简言之,就是将删除操作放到另外一个线程去处理。
当Redis版本小于4.0时,避免使用阻塞式命令KEYS,而是建议通过SCAN命令执行增量迭代扫描key,然后判断进行删除。
Redis Scan 命令用于迭代数据库中的数据库键。
SCAN 命令是一个基于游标的迭代器,每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。
压缩和拆分key
当vaule是string时,比较难拆分,则使用序列化、压缩算法将key的大小控制在合理范围内,但是序列化和反序列化都会带来更多时间上的消耗。
当value是string,压缩之后仍然是大key,则需要进行拆分,一个大key分为不同的部分,记录每个部分的key,使用multiget等操作实现事务读取。
当value是list/set等集合类型时,根据预估的数据规模来进行分片,不同的元素计算后分到不同的片。
网友评论