ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Redis Big Key排查与优化:从定义、发现到预防的完整实战指南

Redis Big Key排查与优化:从定义、发现到预防的完整实战指南 今天聊一个Redis面试题里出场率特别高的家伙——Big Key。这个题在“每日面试题分享”系列里已经排到第155篇了但每次拿到新的对话还是有不少人把它讲浅了。很多人张口就是“大key就是value很大的key”然后没了。我当年第一次被问到“那你遇到Big Key怎么处理”也差点没接住。这里说的Big Key不只是某个key占的内存大它背后牵出的是一整套问题单线程阻塞、网络带宽打满、集群数据倾斜、删除时卡顿、持久化文件膨胀。对准备面试的同学来说这一题几乎是面试官区分“用过Redis”和“背过Redis”的分水岭。这篇我按自己的实战经验把Big Key从定义、发现、处理到面试话术完整捋一遍争取你看完就能直接拿去用。1. Big Key到底怎么定义面试别只说“值很大”1.1 面试题的第一层什么算Big KeyRedis官网上其实没有一条“超过XX字节就是Big Key”的硬规定但业界在实践中形成了一套比较统一的参考标准。我自己平时判断主要看两条String类型的value超过10KB就要开始警觉了。Hash、List、Set、ZSet这类集合型key元素个数超过5000个或者整体编码后的数据量超过10MB就要重点排查。这个标准不是我拍脑袋定的它和Redis的底层编码、网络模型都有关联。Redis 3.2之后集合类默认在元素少的时候会用压缩编码存储比如ziplist、intset性能和内存都很好。但元素一多就会升级成hashtable、skiplist每个操作的时间复杂度虽然仍然是O(1)或O(logn)级别可一个key里存几十万field执行HGETALL这种一次取全部的命令Redis要把所有数据遍历一遍再序列化扔给网络这个动作是极慢的。我们在面试的时候除了说出这些数字最好再加一句“判断标准不能只看绝对值核心是看这个key的读写耗时、内存占用、网络传输大小是否已经影响了Redis的正常服务。”这句话能直接把你的层次拉上去。面试官问的其实不是“10KB哪里来的”而是你到底懂不懂Redis的单线程工作方式懂不懂慢查询的本质是什么。1.2 这些Big Key到底是怎么来的我排查过不少线上问题Big Key的来源其实很有规律基本逃不出下面这几种。第一类是“数据无脑累积型”。最常见的就是一个Hash里面存用户行为数据、埋点日志业务方为了查询方便把所有数据往同一个key里塞日积月累field数量从几千涨到几十万。还有一个典型是List用来做消息队列的消费者消费速度跟不上生产速度list越积越长等发现的时候已经几百万条了。第二类是“序列化不当型”。有些同学把大对象直接序列化成字符串塞进Redis。比如把一整页搜索结果塞进一个String或者把一张base64编码的图片塞进去动辄几百KB甚至几MB。这种key在读写的时候Redis主线程要处理的时间就长网络传输也要占很大带宽。第三类是“设计阶段没有容量评估”。这种在小团队特别多。当时设计缓存结构的时候没想过这个key以后会存多少数据或者根本没预估过QPS。上线三个月后数据量上来了big key也就出现了。我强调这个是因为在面试里被问到“Big Key为什么产生”你如果能分这几类讲出来并且举一个自己实际开发中的例子比单纯背“因为存的数据太多”要有说服力得多。面试官很吃“你见过、你踩过”这一套。1.3 为什么Big Key这么危险很多人不理解就觉得Redis不是号称几万QPS吗一个key大点又能怎样。实际上Big Key的破坏力是系统性的我从几个维度拆开讲。阻塞Redis主线程。Redis是单线程执行命令一个操作如果耗时几百毫秒后面排队的几千个请求都得等待。比如对一个几百万元素的List执行LRANGE 0 -1或者对一个几十万field的Hash执行HGETALLRedis会花大量时间遍历和序列化这段时间内所有其他请求都卡着。网络拥塞和带宽打满。大key一次get返回的数据包可能达到MB级别在高并发下瞬间把内网带宽打满影响同集群其他业务。集群数据倾斜。在Redis Cluster中key按slot分布一个大key的slot会占很大内存其他节点内存可能只用了10%这个节点已经90%了很容易触发内存上限导致整个集群容量没法均匀利用。删除操作造成长时间停顿。这个最坑。直接DEL一个大keyRedis要释放对应的内存如果key内部是个大的hashtable释放成千上万个元素也是耗时的期间主线程照样卡。4.0之前这个问题没有好的解法4.0之后有了UNLINK异步删除才缓解。持久化影响。RDB做快照时需要把大key完整写入文件fork子进程后的写时复制如果大key被修改了就会复制整页内存导致内存翻倍。AOF重写也是同理大key会让持久化文件膨胀得厉害。面试的时候你不需要把这些全背出来但你至少要能说出“阻塞、网络、倾斜、删除卡顿”这四个关键词并且能稍微展开一两个。这一小节的内容其实就是回答“Big Key有什么危害”这个追问的最佳素材。2. 一线排查怎么把Big Key揪出来2.1 最快的方法redis-cli --bigkeys但别被它误导很多文章一说到发现Big Key第一反应就是redis-cli --bigkeys。这个命令确实方便它在Redis内部用的是SCAN命令渐进式遍历所有key而不是KEYS *所以不会阻塞线上实例。执行完之后会给你一份报告列出扫描过程中发现的最大String、最大List、最大Hash等。那为什么我说“别被它误导”因为这个命令是抽样扫描不是精确统计。它每扫到一定数量的key就从中挑出当前最大的那个记录扫描结束后展示的是“采样中的最大”而不是全局精确最大的key。如果你的大key在扫描采样区间里没有被选中它就会被漏掉。另外它只能告诉你每种类型最大的那个key如果线上存在20个大key它可能只给你报一个。所以我的建议是--bigkeys适合做快速粗筛发现问题后不要停在这里要继续用精确手段确认。比如命令里还能看到每个大key的内存估算值但这个估算对Hash这种复杂结构也不一定准它看的是内部编码的字节数跟实际内存占用会有偏差。2.2 想精确看某个key有多大拿到疑似目标之后可以用MEMORY USAGE命令看精确内存占用Redis 4.0。这个命令会计算key和value的整体内存占用包括Redis对象头、底层数据结构、dictEntry等所有开销。示例redis-cli MEMORY USAGE user:profile:1001返回的是字节数。如果返回(nil)说明这个key不存在。要注意的是MEMORY USAGE对于很大的聚合结构本身也会有耗时但因为它是内存计算不涉及网络传输通常比直接访问快很多在低峰期跑是安全的。还有一个老命令是DEBUG OBJECT key它会返回这个key的编码方式、序列化长度、lru时间等信息。比如redis-cli DEBUG OBJECT user:photo:88你会看到类似Value at:0x... refcount:1 encoding:raw serializedlength:1234567 lru:...这样的输出其中serializedlength是序列化后的长度可以大致推断value的大小。DEBUG OBJECT在超大key上使用时要小心它本身有阻塞风险最好只在低峰期执行。2.3 真正可用的生产环境扫描方案如果线上集群有几百上千个key一个一个MEMORY USAGE显然不现实。我更推荐自己写一个巡检脚本。核心思路就是用SCAN增量遍历key然后对不同类型的key做不同的“大小估算”String类型用STRLEN命令看value长度。Hash类型用HLEN看field数量。List类型用LLEN看元素数量。Set类型用SCARD看成员数量。ZSet类型用ZCARD看成员数量。把这些数量指标跑出来之后按之前说的阈值过滤超过的再单独用MEMORY USAGE做精确确认。这套方案的好处是不会阻塞Redis而且能找到所有可疑key而不是只找“最大”的那一个。还有一个隐藏技巧如果开启了慢查询日志可以通过SLOWLOG GET查看耗时特别高的命令很多慢命令背后基本都是Big Key在作怪。比如你发现一条HGETALL执行了200毫秒顺着这个key去查基本就能揪出来。慢日志是线上排查大key最省力的线索来源很多同学不知道用。线上执行这些扫描操作时务必选在业务低峰期。同时尽量避免直接连主节点优先连从节点跑把影响降到最低。这些执行细节在面试过程中讲出来会很有“实战感”。3. 处理Big Key的正确姿势关键是别把线上搞挂3.1 集合类大key的拆分思路发现Big Key后最主流的处理方式就是拆。拆的思路说白了就是“把一个大key变成多个小key”。比如业务里用Hash存用户画像结构是user:profile:{userId}里面有几十万用户的画像数据。你可以按业务维度拆也可以按用户维度拆。按用户维度拆最简单粗暴原来是user:profile:batch1现在改成user:profile:userId:xxxx每个用户一个小key。访问的时候业务侧多拼一个userId就行了。这种拆分需要注意的是一致性写的时候也要按同样规则扩散到多个key不然拆完只有读改了写没改反而出问题。Hash还可以按field做二次哈希分片。比如user:hobby:1001里存了用户1001的所有关注关系有几十万条。你可以把它拆成user:hobby:1001:{0}到user:hobby:1001:{9}这10个key存之前对field的key做一次哈希取模之后落到对应的分片上。查询的时候只需要知道这个哈希规则就能定位到正确的分片key。这个思路在面试里很加分因为它不是单纯的“拆”而是有设计在里面。List类型的大key也类似。一个几十万元素的队列要么用LTRIM定期裁剪历史数据要么就按业务类型拆成多个队列比如queue:order、queue:pay避免所有消息挤在一个List里。3.2 压缩、编码、数据结构层面的优化不是所有场景都需要拆也可以从“如何存”这个角度做优化。如果你是Java后端直接往Redis塞对象的时候用的序列化方案很关键。JDK原生序列化出来的一坨东西很大换成Kryo、Protobuf之后体积能小很多。有的团队还会开启value压缩比如在业务层先用Gzip压缩一下再写入Redis读的时候再解压。这里有个代价压缩和解压会消耗CPU如果读写QPS特别高压缩性价比可能不高。我做过一个场景value是一段JSON文本Gzip压缩后从20KB变成3KB左右读取的时候解压一次大概几毫秒对整体QPS影响不大但内存和带宽都省了一大截。这个取舍要在面试里讲清楚说明你不是无脑压。还有一种情况是用错数据结构。比如把大量UUID放进一个Set其实这种去重集合如果只需要判断存在与否可以考虑用布隆过滤器内存占用小很多。再比如有些人用String存一个标志位却存了完整对象这种属于编码意识问题不是Redis的问题。面试如果被问到“你有没有优化过存储”这些都可以作为案例。3.3 删除大key的正确时机和方式该删的大key还是得删但“怎么删”是有讲究的这里坑特别多。Redis 4.0之后提供了UNLINK命令它和DEL最大的区别是DEL是同步删除删除大key会阻塞主线程UNLINK是异步删除主线程先把key从命名空间中摘掉回收内存在后台线程慢慢做。所以删除大key第一原则就是不要用DEL用UNLINK。redis-cli UNLINK user:profile:batch1如果Redis版本低于4.0有一种曲线救国的办法对需要删除的大key先设置一个极短的过期时间比如EXPIRE key 1让Redis自动过期删除。但这里也有隐患过期删除在4.0之前同样是同步操作一样会卡。所以最稳的老版本方案是利用LREM、HDEL、SREM这类命令分批移除元素等元素少了再DEL。这个方案虽然慢但至少不会让Redis卡死。另外Redis 4.0之后还有一组lazy free配置lazyfree-lazy-expire yes lazyfree-lazy-server-del yes lazyfree-lazy-user-del yes把过期删除、隐式删除、用户删除都设置成异步能规避不少删除卡顿问题。不过要留意异步删除意味着内存释放慢一点在内存压力大的实例上配置会出现释放延迟需要结合监控观察。我遇到过开启lazy free后内存短期内不降反升的情况其实是后台线程在慢慢回收一般几秒内就会恢复正常不用慌。4. 面试官的连环追问你怎么接4.1 从“怎么治”到“怎么防”面试到了这一步通常候选人已经能说出Big Key是啥、怎么发现了但能不能拿高分看的其实是预防手段。面试官常问的是“你现在知道它有问题那你从开始设计的时候怎么避免它出现”。我一般会从三个层面回答。第一是业务设计阶段估算单个key的规模明确“所有String value控制在10KB以内集合型key元素控制在5000以内”这种硬规矩超过就必须拆分。第二是代码review阶段把HGETALL、LRANGE 0 -1这种一次拉全量的命令列成黑名单开发人员如果敢写review直接打回。第三是监控阶段用前面说的巡检脚本定期扫描并且把大key的发现接入告警比如发现一个Hash超过5000个field就推送告警给责任人。这三个层面答出来面试官基本就满意了因为他看到的不再是一个被问题推着走的开发而是一个有主动治理意识的人。这也是“经验”和“背题”的核心区别。4.2 一个真实的生产事故复盘说到预防我忍不住讲一个我实际遇到的案例。有一年我做活动系统某个活动页的人数统计用了Redis的Hashkey是stat:activityIdfield是userIdvalue是用户操作次数。活动上线第一天数据量还正常第二天冲到几万人这个Hash的field数瞬间涨到十几万。当时最先出问题的是监控Redis内存占用明显比同集群其他节点高形成数据倾斜。然后活动页开始变慢查了下慢日志全是HGETALL这个命令一次执行几百毫秒。当时我第一反应不是去改代码而是先用HLEN确认了这个key的规模然后带着业务同学做了一个紧急方案先单独把这个大key“冻结”新写入的数据落到新的临时key上接着在低峰期用UNLINK把它删掉最后再把统计逻辑改成按userId分桶存储也就是每个用户一个独立field的粒度改成按日期用户分片。这个案例里我想强调的是“先止血再优化”的思路。遇到线上出问题你不能立即跑去改代码因为改代码要发布发布要时间线上还在被打爆。你要先用Redis提供的手段把当前的风险解除比如把读取切成新的小key、把大key异步删掉然后再上线新代码。这种处理顺序在面试里讲出来特别能体现你的应变能力。4.3 面试回答的框架和话术最后给你一个面试可以直接套用的回答框架我管它叫“三步定位法”。第一步定位定义先说明Big Key指的是String value过大常见超过10KB或集合元素过多常见超过5000个并点出核心危害是阻塞单线程、网络带宽、数据倾斜、删除卡顿。第二步定位发现说自己会用SCAN或redis-cli --bigkeys做初筛用MEMORY USAGE做精确确认再配合慢日志定位具体key。第三步定位解决大Key按业务拆分value过大用压缩和更优序列化删除用UNLINK异步处理最后强调从设计、review、监控三方面做预防。按照这个框架哪怕遇到没做过实操的候选人也能给出一个逻辑自洽的回答。如果面试官深挖“你说的10KB和5000是哪来的”你就说这是业界实践总结出来的经验阈值不同业务可以有不同标准判断核心是看key是否已经开始影响Redis性能。这样既不硬背也有理有据。写到这里我想起自己当年面一个中厂岗位就是栽在“Big Key怎么发现”这个追问上。当时我能说出定义但说不出排查命令面试官就很温和地问了一句“那线上出了事你怎么定位”我支吾半天没答上来。现在回头看这个问题的价值不只是应付面试而是它逼着你去理解单线程模型、内存回收、结构编码这些Redis底层机制理解了这些你写的代码自然就更靠谱。面试也好日常开发也好遇到Big Key别怕按“发现-拆分-优化-预防”这一套走下来问题基本都能落地解决。
返回列表