Redis--14--BigKey 和 热点Key

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


BigKey

1.什么是bigkey

bigkey是指key对应的value所占的内存空间比较大,例如一个字符串类型的value可以最大存到512MB,一个列表类型的value最多可以存储23-1个元素。

如果按照数据结构来细分的话,一般分为字符串类型bigkey和非字符串类型bigkey。

字符串类型:体现在单个value值很大,一般认为超过10KB就是bigkey,但这个值和具体的OPS相关。

非字符串类型:哈希、列表、集合、有序集合,体现在元素个数过多。

bigkey无论是空间复杂度和时间复杂度都不太友好,下面我们将介绍它的危害。

2.bigkey的危害

bigkey的危害体现在三个方面:

  1. 内存空间不均匀.(平衡):例如在Redis Cluster中,bigkey 会造成节点的内存空间使用不均匀。

  2. 超时阻塞:由于Redis单线程的特性,操作bigkey比较耗时,也就意味着阻塞Redis可能性增大。

  3. 网络拥塞:每次获取bigkey产生的网络流量较大

假设一个bigkey为1MB,每秒访问量为1000,那么每秒产生1000MB 的流量,对于普通的千兆网卡(按照字节算是128MB/s)的服务器来说简直是灭顶之灾,而且一般服务器会采用单机多实例的方式来部署,也就是说一个bigkey可能会对其他实例造成影响,其后果不堪设想。

bigkey的存在并不是完全致命的:

如果这个bigkey存在但是几乎不被访问,那么只有内存空间不均匀的问题存在,相对于另外两个问题没有那么重要紧急,但是如果bigkey是一个热点key(频繁访问),那么其带来的危害不可想象,所以在实际开发和运维时一定要密切关注bigkey的存在。

3.发现bigkey

redis-cli --bigkeys可以命令统计bigkey的分布。

但是在生产环境中,开发和运维人员更希望自己可以定义bigkey的大小,而且更希望找到真正的bigkey都有哪些,这样才可以去定位、解决、优化问题。

判断一个key是否为bigkey,只需要执行debug object key查看serializedlength属性即可,它表示 key对应的value序列化之后的字节数。

如果是要遍历多个,则尽量不要使用keys的命令,可以使用scan的命令来减少压力。

scan

Redis 从2.8版本后,提供了一个新的命令scan,它能有效的解决keys命令存在的问题。和keys命令执行时会遍历所有键不同,scan采用渐进式遍历的方式来解决 keys命令可能带来的阻塞问题,但是要真正实现keys的功能,需要执行多次scan。可以想象成只扫描一个字典中的一部分键,直到将字典中的所有键遍历完毕。scan的使用方法如下:

复制代码
scan cursor [match pattern] [count number]

cursor :是必需参数,实际上cursor是一个游标,第一次遍历从0开始,每次scan遍历完都会返回当前游标的值,直到游标值为0,表示遍历结束。

Match pattern :是可选参数,它的作用的是做模式的匹配,这点和keys的模式匹配很像。

Count number :是可选参数,它的作用是表明每次要遍历的键个数,默认值是10,此参数可以适当增大。

可以看到,第一次执行scan 0,返回结果分为两个部分:

第一个部分9就是下次scan需要的cursor

第二个部分是10个键。接下来继续

直到得到结果cursor变为0,说明所有的键已经被遍历过了。

除了scan 以外,Redis提供了面向哈希类型、集合类型、有序集合的扫描遍历命令,解决诸如hgetall、smembers、zrange可能产生的阻塞问题,对应的命令分别是hscan、sscan、zscan,它们的用法和scan基本类似,请自行参考Redis官网。

渐进式遍历可以有效的解决keys命令可能产生的阻塞问题,但是scan并非完美无瑕,如果在scan 的过程中如果有键的变化(增加、删除、修改),那么遍历效果可能会碰到如下问题:新增的键可能没有遍历到,遍历出了重复的键等情况,也就是说scan并不能保证完整的遍历出来所有的键,这些是我们在开发时需要考虑的。

如果键值个数比较多,scan + debug object会比较慢,可以利用Pipeline机制完成。对于元素个数较多的数据结构,debug object执行速度比较慢,存在阻塞Redis的可能,所以如果有从节点,可以考虑在从节点上执行。

4.解决bigkey

主要思路为拆分

对 big key 存储的数据 (big value)进行拆分,变成value1,value2... valueN等等。

  • 例如big value 是个大json 通过 mset 的方式,将这个 key的内容打散到各个实例中,
  • 或者一个hash,每个field代表一个具体属性,通过hget、hmget获取部分value,hset、hmset来更新部分属性。
  • 例如big value 是个大list,可以拆成将list拆成。= list_1, list_2, list3, ...listN

其他数据类型同理。

什么是热点Key?该如何解决

在Redis中,访问频率高的key称为热点key。

1. 产生原因和危害

原因

热点问题产生的原因大致有以下两种:

用户消费的数据远大于生产的数据(热卖商品、热点新闻、热点评论、明星直播)。

在日常工作生活中一些突发的事件,例如:双十一期间某些热门商品的降价促销,当这其中的某一件商品被数万次点击浏览或者购买时,会形成一个较大的需求量,这种情况下就会造成热点问题。同理,被大量刊发、浏览的热点新闻、热点评论、明星直播等,这些典型的读多写少的场景也会产生热点问题。

请求分片集中,超过单Server的性能极限。在服务端读数据进行访问时,往往会对数据进行分片切分,此过程中会在某一主机Server上对相应的Key进行访问,当访问超过Server极限时,就会导致热点Key问题的产生。

危害

1、流量集中,达到物理网卡上限。

2、请求过多,缓存分片服务被打垮。

3、DB击穿,引起业务雪崩。

2.发现热点key

预估发现

针对业务提前预估出访问频繁的热点key,例如秒杀商品业务中,秒杀的商品都是热点key。

当然并非所有的业务都容易预估出热点key,可能出现漏掉或者预估错误的情况。

客户端发现

客户端其实是距离key"最近"的地方,因为Redis命令就是从客户端发出的,以Jedis为例,可以在核心命令入口,使用这个Google Guava中的AtomicLongMap进行记录,如下所示。

使用客户端进行热点key的统计非常容易实现,但是同时问题也非常多:

(1) 无法预知key的个数,存在内存泄露的危险。

(2) 对于客户端代码有侵入,各个语言的客户端都需要维护此逻辑,维护成本较高。

(3) 规模化汇总实现比较复杂。

Redis发现
monitor命令

monitor命令可以监控到Redis执行的所有命令,利用monitor的结果就可以统计出一段时间内的热点key排行榜,命令排行榜,客户端分布等数据。

Facebook开源的redis-faina正是利用上述原理使用Python语言实现的,例如下面获取最近10万条命令的热点key、热点命令、耗时分布等数据。为了减少网络开销以及加快输出缓冲区的消费速度,monitor尽可能在本机执行。

此种方法会有两个问题:

1、monitor命令在高并发条件下,内存暴增同时会影响Redis的性能,所以此种方法适合在短时间内使用。

2、只能统计一个Redis节点的热点key,对于Redis集群需要进行汇总统计。

可以参考的框架:Facebook开源的redis-faina正是利用上述原理使用Python语言实现的

hotkeys

Redis在4.0.3中为redis-cli提供了--hotkeys,用于找到热点key。

如果有错误,需要先把内存逐出策略设置为allkeys-lfu或者volatile-lfu,否则会返回错误。

但是如果键值较多,执行较慢,和热点的概念的有点背道而驰,同时热度定义的不够准确。

抓取TCP包发现

Redis客户端使用TCP协议与服务端进行交互,通信协议采用的是RESP。如果站在机器的角度,可以通过对机器上所有Redis端口的TCP数据包进行抓取完成热点key的统计

此种方法对于Redis客户端和服务端来说毫无侵入,是比较完美的方案,但是依然存在3个问题:

(1) 需要一定的开发成本

(2) 对于高流量的机器抓包,对机器网络可能会有干扰,同时抓包时候会有丢包的可能性。

(3) 维护成本过高。

对于成本问题,有一些开源方案实现了该功能,例如ELK(ElasticSearch Logstash Kibana)体系下的packetbeat[2] 插件,可以实现对Redis、MySQL等众多主流服务的数据包抓取、分析、报表展示

3. 解决热点key

发现热点key之后,需要对热点key进行处理。

使用二级缓存
  • 可以使用guava-cache或hcache,发现热点key之后,将这些热点key加载到JVM中作为本地缓存。访问这些key时直接从本地缓存获取即可,不会直接访问到redis层了,有效的保护了缓存服务器。
key分散
  • 将热点key分散为多个子key,然后存储到缓存集群的不同机器上,这些子key对应的value都和热点key是一样的。当通过热点key去查询数据时,通过某种hash算法随机选择一个子key,然后再去访问缓存机器,将热点分散到了多个子key上。
相关推荐
数据智能老司机2 小时前
CockroachDB权威指南——SQL调优
数据库·分布式·架构
数据智能老司机2 小时前
CockroachDB权威指南——应用设计与实现
数据库·分布式·架构
数据智能老司机2 小时前
CockroachDB权威指南——CockroachDB 模式设计
数据库·分布式·架构
数据智能老司机21 小时前
CockroachDB权威指南——CockroachDB SQL
数据库·分布式·架构
数据智能老司机21 小时前
CockroachDB权威指南——开始使用
数据库·分布式·架构
松果猿1 天前
空间数据库学习(二)—— PostgreSQL数据库的备份转储和导入恢复
数据库
Kagol1 天前
macOS 和 Windows 操作系统下如何安装和启动 MySQL / Redis 数据库
redis·后端·mysql
无名之逆1 天前
Rust 开发提效神器:lombok-macros 宏库
服务器·开发语言·前端·数据库·后端·python·rust
s9123601011 天前
rust 同时处理多个异步任务
java·数据库·rust
数据智能老司机1 天前
CockroachDB权威指南——CockroachDB 架构
数据库·分布式·架构