首先,要申明一下,问题的严重性。
BigKey(大key)和HotKey(热key)的问题是较常见。
这类问题不止会使服务的性能下降,还会影响用户正常使用功能,
甚至会造成大范围的服务故障,故障有时还会发生连环效应,导致更加严重的后果,发生系统的雪崩,造成巨大的经济损失,巨大的品牌损伤。
所以,在 redis 运维过程中,由于 Bigkey 的存在,DBA 也一直和业务开发方强调 Bigkey 的规避方法以及危害。
在开发的过程中,开发同学,也需要十分重视和预防这个问题。
俗称“大key”,是指redis在日常生产的过程中,某些key所占内存空间过大。
通俗来说,redis是key-value的存储方式,当一个key所对应的存储数值达到一定程度,就会出现大key的情况。
redis里有多种数据存储结构,如String、List、Hash等,每种存储结构都有能够承载的数据限值。当一个key包含的内容接近限制,或者高于平均值,大key就产生了。关注公众号:码猿技术专栏,回复关键词:1111 获取阿里内部JAVA性能调优手册
在 Redis 中,一个字符串类型最大可以到 512MB,一个二级数据结构(比如 hash、list、set、zset 等)可以存储大约 40 亿个(2^32-1)个元素,
但实际上不会达到这么大的值,一般情况下如果达到下面的情况,就可以认为它是 Bigkey 了。
俗称“热key”,一个key对应在一个redis分片上,当短时间内大量的请求打到该分片上,key被频繁访问,该key就是热key。
当大量的请求,经过分发和计算,最终集中于同一个redis实例下的某个key时,该key由于被请求频率过高,而占用掉了大量资源。
而其他分片,由于key的不合理分配导致请求量较少。
整个redis集群呈现出了资源使用不均衡的现象。
举个例子:一线女明星官宣领证结婚,短时间内该女星微博账号被访问量激增(假设该账号内容被同步在缓存,账号id作为key),微博服务瘫痪(不具备任何实时参考性,仅作为虚拟的例子)。
在该场景下,上述key被大量访问,造成热key。
总之,在某个Key接收到的访问次数、显著高于其它Key时,我们可以将其称之为HotKey,
从访问量上来说,常见的HotKey如:
我们可以通过上述两种key的特性,来简单分析可能出现的几种问题。
主要的问题是一个key所占空间太大,内存空间分配不均衡(小tips:redis是内存型key-value数据库)。那就可能引发以下问题:
1.数据请求大量超时:
redis是单线程的,当一个key数据响应的久一点,就会造成后续请求频繁超时。如果服务容灾措施考虑得不够,会引发更大的问题。
2.侵占带宽网络拥堵:
当一个key所占空间过大,多次请求就会占用较大的带宽,直接影响服务的正常运行。
3.内存溢出或处理阻塞:
当一个较大的key存在时,持续新增,key所占内存会越来越大,严重时会导致内存数据溢出;当key过期需要删除时,由于数据量过大,可能发生主库较响应时间过长,主从数据同步异常(删除掉的数据,从库还在使用)。
热key,热key的问题是单点访问频率过高。那就可能引发以下问题:
1.分片服务瘫痪:
redis集群会分很多个分片,每个分片有其要处理的数据范围。当某一个分片被频繁请求,该分片服务就可能会瘫痪。
2.Redis 分布式集群优势弱化:
如果请求不够均衡,过于单点,那么redis分布式集群的优势也必然被弱化。
3.可能造成资损:
在极端场景下,容易发生边界数据处理不及时,在订单等场景下,可能造成资损。
4.引发缓存击穿:
我们都知道,当缓存请求不到,就会去请求数据库。如果请求过于集中,redis承载不了,就会有大量请求打到数据库。此时,可能引发数据库服务瘫痪。进而引发系统雪崩。
5.cpu占用高,影响其他服务:
单个分片cpu占用率过高,其他分片无法拥有cpu资源,从而被影响。
通常需要对业务场景做分析,结合技术方案去判断是否会出现大key、热key的问题。
比如说:
(1)购物车场景,当一个购物车的key设计,没有上限,没有其他随机值约束,仅使用了mid。这个时候就要注意,如果有个购物狂,一次加购5w件商品怎么办?
(2)活动资格列表场景,当一个活动的资格查询list被放入一个key,活动期间频繁的查询和操作。这个时候就要注意,list的数据量有多少?查询资格的操作是否集中?如果集中,qps是多少?
Redis4.0 及以上版本提供了--Bigkeys, --hotkeys 命令,可以分析出实例中每种数据结构的 top 1 的 Bigkey,同时给出了每种数据类型的键值个数以及平均大小。
查看bigkey:redis-cli -a 登录密码 --bigkeys
查看hotkey:redis-cli -a 登录密码 --hotkeys
--bigkey 的使用示例
(1)可使用redis可视化工具进行查看(例如:another redis desktop manager)
可视化的工具可以明确给出redis集群当下的信息,经过简要数据分析,便可观测异常。
(2)借助市面上的开源工具(本文暂不对此深入探讨)
redis-rdb-tools(附:https://Github.com/sripathikrishnan/redis-rdb-tools)
(3)借助公司的自研工具
如果 vivo内部的 DaaS(数据即服务)平台。
通过 RDB 文件,分析 big key
解决方案
主要的方法:对 big key 进行拆分
对 big key 存储的数据 (big value)进行拆分,变成value1,value2… valueN,
如果big value 是个大json 通过 mset 的方式,将这个 key 的内容打散到各个实例中,减小big key 对数据量倾斜造成的影响。关注公众号:码猿技术专栏,回复关键词:1111 获取阿里内部Java性能调优手册
如果big value 是个大list,可以拆成将list拆成。= list_1, list_2, list3, listN
其他数据类型同理
主要的方法:使用本地缓存
在 client 端使用本地缓存,从而降低了redis集群对hot key的访问量,
但是本地缓存 ,带来两个问题:1、如果对可能成为 hot key 的 key 都进行本地缓存,那么本地缓存是否会过大,从而影响应用程序本身所需的缓存开销。
2、如何保证本地缓存和redis集群数据的有效期的一致性。
以上两个问题,具体看:聊聊 Redis+Caffeine 两级缓存
此生产实例,非常宝贵,是珍贵的一线工业级实操案例,来源于 vivo 互联网数据库团队- Du Ting
陈某仅仅是将其结构,做进一步的梳理,方便大家学习。
全网 Redis 集群有 2200 个以上,实例数量达到 4.5 万以上,
进行一次全网 Bigkey 检查,估计需要以年为时间单位,非常耗时。
Bigkey 一般都是由于程序设计不当、或者对于数据规模 预估 失策,比如以下的情况。
内存空间不均匀会不利于集群对内存的统一管理,有数据丢失风险。
下图中的三个节点是同属于一个集群,它们的 key 的数量比较接近,但内存容量相差比较多,存在 Bigkey 的实例占用的内存多了 4G 以上了。
可以使用 Daas 平台“工具集-操作项管理”,选择对应的 slave 实例执行分析,找出具体的 Bigkey。
Redis 是单线程工作的,通俗点讲就是同一时间只能处理一个 Redis 的访问命令,
操作 Bigkey 的命令通常比较耗时,这段时间 Redis 不能处理其他命令,其他命令只能阻塞等待,这样会造成客户端阻塞,导致客户端访问超时,更严重的会造成 master-slave 的故障切换。
当然,造成阻塞的操作不仅仅是业务程序的访问,还有 key 的自动过期的删除、del 删除命令,对于 Bigkey,这些操作也需要谨慎使用。
我们遇到一个这样超时阻塞的案例,业务方反映:程序访问 Redis 集群出现超时现象,hkeys 访问 Redis 的平均响应时间在 200 毫秒左右,最大响应时间达到了 500 毫秒以上,如下图。
hkeys 是获取所有哈希表中的字段的命令,分析应该是集群中某些实例存在 hash 类型的 Bigkey,导致 hkeys 命令执行时间过长,发生了阻塞现象。
1.使用 Daas 平台“服务监控-数据库实例监控”,选择 master 节点,选择 Redis 响应时间监控指标“redis.instance.latency.max”,如下图所示,从监控图中我们可以看到
(1)正常情况下,该实例的响应时间在 0.1 毫秒左右。
(2)监控指标上面有很多突刺,该实例的响应时间到了 70 毫秒左右,最大到了 100 毫秒左右,这种情况就是该实例会有 100 毫秒都在处理 Bigkey 的访问命令,不能处理其他命令。
通过查看监控指标,验证了我们分析是正确的,是这些监控指标的突刺造成了 hkeys 命令的响应时间比较大,我们找到了具体的 master 实例,然后使用 master 实例的 slave 去分析下 Bigkey 情况。
2.使用 Daas 平台“工具集-操作项管理”,选择 slave 实例执行分析,分析结果如下图,有一个 hash 类型 key 有 12102218 个 fields。
Bigkey 的 value 比较大,也意味着每次获取要产生的网络流量较大,假设一个 Bigkey 为 10MB,客户端每秒访问量为 100,那么每秒产生 1000MB 的流量,对于普通的千兆网卡(按照字节算是 128MB/s)的服务器来说简直是灭顶之灾。
而且我们现在的 Redis 服务器是采用单机多实例的方式来部署 Redis 实例的,也就是说一个 Bigkey 可能会对同一个服务器上的其他 Redis 集群实例造成影响,影响到其他的业务。
我们在运维中经常做的变更操作是水平扩容,就是增加 Redis 集群的节点数量来达到扩容的目的,这个水平扩容操作就会涉及到 key 的迁移,把原实例上的 key 迁移到新扩容的实例上。
当要对 key 进行迁移时,是通过 migrate 命令来完成的,migrate 实际上是通过 dump + restore + del 三个命令组合成原子命令完成,它在执行的时候会阻塞进行迁移的两个实例,直到以下任意结果发生才会释放:迁移成功,迁移失败,等待超时。
如果 key 的迁移过程中遇到 Bigkey,会长时间阻塞进行迁移的两个实例,可能造成客户端阻塞,导致客户端访问超时;也可能迁移时间太长,造成迁移超时导致迁移失败,水平扩容失败。
我们也遇到过一些因为 Bigkey 扩容迁移失败的案例,如下图所示,
这是一个 Redis 集群水平扩容的工单,需要进行 key 的迁移,当工单执行到 60%的时候,迁移失败了。
如何解决呢?
大概的解决流程,如下:
3.和业务沟通,这些 key 是记录用户访问系统的某个功能模块的 ip 地址的,访问该功能模块的所有 ip 都会记录到给 key 里面,随着时间的积累,这个 key 变的越来越大。同样是采用拆分的方式进行优化,可以考虑按照时间日期维度来拆分,就是一段时间段的访问 ip 记录到一个 key 中。
4.Bigkey 优化后,扩容的工单可以重试,完成集群扩容操作。
分析 Bigkey 的方法不少,这里介绍两种比较常用的方法,也是 Daas 平台分析 Bigkey 使用的两种方式,分别是 Bigkeys 命令分析法、RDB 文件分析法。
Redis4.0 及以上版本提供了--Bigkeys 命令,可以分析出实例中每种数据结构的 top 1 的 Bigkey,同时给出了每种数据类型的键值个数以及平均大小。
执行--Bigkeys 命令时候需要注意以下几点:
Daas 平台集成了基于原生--Bigkeys 代码实现的查询 Bigkey 的方式,这个方式的缺点是只能计算每种数据结构的 top1,如果有些数据结构有比较多的 Bigkey,是查找不出来的。该方式相对比较安全,已经开放出来给业务开发同学使用。
借助开源的工具,比如 rdb-tools,分析 Redis 实例的 RDB 文件,找出其中的 Bigkey,这种方式需要生成 RDB 文件,需要注意以下几点:
Daas 平台集成了基于 RDB 文件分析代码实现的查询 Bigkey 的方式,可以根据实际需求自定义填写 N,分析的 top N 个 Bigkey。该方式相对有一定风险,只有 DBA 有权限执行分析。
通过巡检,可以暴露出隐患,提前解决,避免故障的发生,进行全网 Bigkey 的巡检,是避免 Bigkey 故障的比较好的方法。
由于全网 Redis 实例数量非常大,分析的速度比较慢,使用当前的分析方法很难完成。
为了解决这个问题,存储研发组分布式数据库同学计划开发一个高效的 RDB 解析工具,然后通过大规模解析 RDB 文件来分析 Bigkey,可以提高分析速度,实现 Bigkey 的巡检。
优化 Bigkey 的原则就是 string 减少字符串长度,list、hash、set、zset 等减少元素数量。当我们知道哪些 key 是 Bigkey 时,可以把单个 key 拆分成多个 key,比如以下拆分方式可以参考。
我们全网 Redis 集群有 2200 以上,实例数量达到 4.5 万以上,有的比较大的集群的实例数量达到了 1000 以上,前面提到的两种 Bigkey 分析工具还都是实例维度分析,对于实例数量比较大的集群,进行全集群分析也是比较耗时的,为了提高分析效率,从以下几个方面进行优化:
目前情况,我们有一些 Bigkey 的发现是被动的,一些是在水平扩容时候发现的,由于 Bigkey 的存在导致扩容失败了,严重的还触发了 master-slave 的故障切换,这个时候可能已经造成业务程序访问超时,导致了可用性下降。
我们分析了 Daas 平台的水平扩容时迁移 key 的过程及影响参数,内容如下:
(1)【cluster-node-timeout】:控制集群的节点切换参数,
master 堵塞超过 cluster-node-timeout/2 这个时间,就会主观判定该节点下线 pfail 状态,如果迁移 Bigkey 阻塞时间超过 cluster-node-timeout/2,就可能会导致 master-slave 发生切换。
(2)【migrate timeout】:控制迁移 io 的超时时间
超过这个时间迁移没有完成,迁移就会中断。
(3)【迁移重试周期】:迁移的重试周期是由水平扩容的节点数决定的,
比如一个集群扩容 10 个节点,迁移失败后的重试周期就是 10 次。
(4)【一个迁移重试周期内的重试次数】:在一个起迁移重试周期内,会有 3 次重试迁移,
每一次的 migrate timeout 的时间分别是 10 秒、20 秒、30 秒,每次重试之间无间隔。
比如一个集群扩容 10 个节点,迁移时候遇到一个 Bigkey,第一次迁移的 migrate timeout 是 10 秒,10 秒后没有完成迁移,就会设置 migrate timeout 为 20 秒重试,如果再次失败,会设置 migrate timeout 为 30 秒重试,
如果还是失败,程序会迁移其他新 9 个的节点,但是每次在迁移其他新的节点之前还会分别设置 migrate timeout 为 10 秒、20 秒、30 秒重试迁移那个迁移失败的 Bigkey。
这个重试过程,每个重试周期阻塞(10+20+30)秒,会重试 10 个周期,共阻塞 600 秒。其实后面的 9 个重试周期都是无用的,每次重试之间没有间隔,会连续阻塞了 Redis 实例。
(5)【迁移失败日志】:日志的缺失
迁移失败后,记录的日志没有包括迁移节点、solt、key 信息,不能根据日志立即定位到问题 key。
我们对这个迁移过程做了优化,具体如下:
(1)【cluster-node-timeout】:延长超时时间
默认是 60 秒,在迁移之前设置为 15 分钟,防止由于迁移 Bigkey 阻塞导致 master-slave 故障切换。
(2)【migrate timeout】:减少阻塞时间
为了最大限度减少实例阻塞时间,每次重试的超时时间都是 10 秒,3 次重试之间间隔 30 秒,这样最多只会连续阻塞 Redis 实例 10 秒。
(3)【重试次数】:去掉了其他节点迁移的重试
迁移失败后,只重试 3 次(重试是为了避免网络抖动等原因造成的迁移失败),每次重试间隔 30 秒,重试 3 次后都失败了,会暂停迁移,日志记录下 Bigkey,去掉了其他节点迁移的重试。
(4)【优化日志记录】:日志记录
迁移失败日志记录迁移节点、solt、key 信息,可以立即定位到问题节点及 key。
首先是需要从源头治理,防止 Bigkey 、Hotkey形成,加强对业务开发同学 bigkey 相关问题的宣导;
其次,提升及时发现的能力,实现 Bigkey 、Hotkey 及时探测能力。
Github:rdb-tools:https://github.com/sripathikrishnan/redis-rdb-tools
(1) redis命令:Redis 命令参考 — Redis 命令参考
(2) Github: https://github.com/sripathikrishnan/redis-rdb-tools
(3) another redis desktop manager下载地址AnotherRedisDesktopManager 发行版:https://gitee.com/qishibo/AnotherRedisDesktopManager/releases