首页 / 资讯中心 / 文章详情

Redis大key排查与拆分实战:从阻塞原理到集群治理

Redis大key排查与拆分实战:从阻塞原理到集群治理 ★ FEATURED ARTICLE
1. 面试官为什么盯着大key问从一道题看真实生产痛点这道题我见过太多次了从初级岗到资深岗几乎都会绕到Redis大key这个话题上。很多人第一反应是背概念——大key就是value很大的key然后背几条危害就完事了。但我做过面试官也做过候选人很清楚这里面的门道面试官问大key问的从来不是定义而是你有没有在真实流量下被它折腾过。一个没处理过大key的人回答会停留在会阻塞要拆分这种结论层面而真正踩过坑的人能说出拆分之后数据结构怎么设计、迁移期间怎么保证一致性、聚合读怎么把性能补回来。这两种回答的差距就是这道题的全部意义。先把话说明白Redis大key也叫bigkey指的是单个key对应的value体积过大或者集合类型Hash、List、Set、ZSet里元素数量过多。它本身不是Redis的缺陷而是使用方式与数据增长之间失衡的结果。这道题背后真正考察的是三件事你能不能准确定位大key、能不能设计出合理的拆分方案、能不能处理拆分带来的副作用。前两件是八股能覆盖的第三件才是分水岭。我在一个模拟项目里遇到过最典型的一幕某业务线把用户的行为流水直接塞进一个Listkey按用户ID命名跑了大半年没出事直到某个重度用户的行为记录堆到八十多万条那台实例的慢查询日志开始刷屏接口P99从二十毫秒涨到一秒多。排查了两天才锁定是这个key。这件事让我对大key是慢性病这句话有了切身体会——它不会立刻崩但会在某个不可预测的时刻集中爆发而且爆发时往往不是单点问题是连锁反应。这篇文章适合谁看如果你是准备面试的Java后端这里有可以直接拿去用的答题思路和落地细节如果你正在线上被大key困扰第4节到第6节的拆分方案和聚合读取方案可以直接照着改如果你只是听说过这个词想搞明白前两节会把判定标准和扫描手段讲清楚。我会尽量用从业者之间的说法把每一步的为什么讲透而不是丢一堆结论让你自己悟。2. 先把大key定义清楚判定标准与扫描手段2.1 多大的key才算大没有绝对数字只有相对阈值这个问题我被问过无数次标准答案是没有统一标准看业务。但只回答这个等于没回答你得给出可操作的判断维度。我一般从三个角度看第一是字节体积。业内比较通行的经验线是String类型 value 超过 10KB 就要警惕超过 100KB 基本可以判定为大key集合类型的整体序列化体积超过 1MB 同样算大。为什么卡在10KB这个量级因为Redis单次网络往返、内存分配、以及主从复制的传输单元都在这个尺度附近超过之后单次操作的耗时开始明显偏离均值。第二是元素数量。Hash、List、Set、ZSet这类结构元素个数超过 5000 就该进入观察名单超过 1万 基本要处理。这里要注意元素数量大和字节体积大是两回事一个Hash存了十万个小整数字节体积可能才几百KB但你做一次HGETALL照样能把实例打满因为遍历成本是按元素个数算的不是按字节算的。第三是访问模式。同样的key如果每次只取一个field那体积大一点也还能忍如果业务代码里清一色HGETALL、LRANGE 0 -1、SMEMBERS全量拉取那即使没到大key的线也是高危对象。我在评审代码时看到全量拉取就会标红原因很简单大key的危害本质上是大操作的危害key大只是让大操作更容易出现。类型观察阈值危险阈值主要风险点String10KB100KB网络传输、复制积压Hash5000 field1万 fieldHGETALL阻塞、删除耗时List5000 元素1万 元素LRANGE全量、LTRIM开销Set5000 元素1万 元素SMEMBERS、SINTER运算ZSet5000 元素1万 元素ZRANGE全量、ZREMRANGEBYRANK这张表建议记下来面试时能报出具体量级比含糊地说很大有说服力得多。但一定要补一句这些是经验值实际要结合实例规格、QPS水平、平均value大小来定小规格实例的容忍度要往下调。2.2 线上怎么把大key揪出来三条扫描路径光定标准没用得能找到。我常用的手段有三个从轻到重排列。第一条是自带命令redis-cli --bigkeys。这条命令的原理是SCAN遍历所有key对每个key用TYPE判断类型再用STRLEN、HLEN、LLEN、SCARD、ZCARD取长度或体积最后按类型排序输出最大的几个。优点是安全用的是SCAN游标遍历不会阻塞主线程缺点是只统计元素个数和字符串长度不统计实际内存占用而且对一个有几千万key的实例跑一遍可能要几分钟到几十分钟。使用时要加-i 0.1控制扫描频率避免把CPU打高redis-cli -h 实例地址 -p 端口 -a 密码 --bigkeys -i 0.1第二条是MEMORY USAGE命令。这个才是真正按内存占用算的能拿到某个key实际占多少字节。适合在已经圈定嫌疑key之后做精确测量redis-cli -h 实例地址 -p 端口 MEMORY USAGE 业务key名 SAMPLES 0SAMPLES 0表示全量采样结果最准但耗时最长不写这个参数默认采样5个元素做估算速度快但误差可能到百分之几十。我在生产上一般是先用--bigkeys圈出候选再用MEMORY USAGE逐个精测。第三条是离线分析RDB文件。用redis-rdb-tools这类工具解析快照文件能输出每个key的类型、大小、元素数量还能按前缀聚合统计。这条路最重需要一份RDB副本但胜在不影响线上实例而且能做全量精确分析。适合做季度性的容量治理盘点rdb -c memory dump.rdb --bytes 10240 -f bigkey_report.csv上面这条命令会把所有超过10KB的key导成CSV后面接一个排序就拿到排行榜了。注意--bigkeys在生产高峰期不要跑即使有-i限速SCAN本身也会产生额外负载。我一般安排在业务低峰期或者直接在从节点上跑。2.3 别只盯单个key前缀聚合才是治理的抓手这里有个很多人忽略的点找到单个大key只是第一步真正有价值的是按业务前缀做聚合统计。因为大key往往是批量产生的你发现一个用户流水List有八十万条那大概率有几百个用户都到了十几万条的量级只是还没触发告警。只修那一个key过两周又会冒出来。我的做法是把--bigkeys或者RDB分析的结果按key的命名前缀分组统计每组的总内存占比和平均元素数量。比如user:behavior:{uid}这个前缀下如果有一百万个key、总占用30GB、平均每个五千条那这个业务结构本身就需要重新设计而不是修修补补。这个视角能让你从救火转到治理也是面试时能拉开差距的地方——大多数候选人只会说用bigkeys找出来说不出找出来之后怎么系统性处理。3. 大key的杀伤力拆解为什么非要拆分3.1 单线程模型下的阻塞放大效应要理解大key为什么危险得先记住Redis处理命令是单线程的指命令执行主线程网络IO在6.0之后有多线程但命令执行还是串行。这意味着任何一个慢命令都会把后面排队的命令全部堵住这就是所谓的队头阻塞。举个具体例子一个Hash有五十万个field你执行一次HGETALLRedis需要遍历整个哈希表、把五十万个field-value序列化进回复缓冲区。这个过程大概要几百毫秒甚至更久。在这几百毫秒里这台实例上其他所有请求都在排队。假设这个实例平时QPS是五万那这几百毫秒里就积压了几万个请求客户端的超时、重试、雪崩就全来了。更隐蔽的是DEL操作。删除一个大keyRedis需要逐个释放内部元素的内存五十万个field的Hash删一次可能要几十毫秒到上百毫秒同样是阻塞。很多团队在清理历史数据时用DEL批量删大key结果把实例搞出一次抖动这个坑我见过不止一次。正确做法是用UNLINK做异步删除把内存释放放到后台线程# 错误示范同步删除阻塞主线程 DEL user:behavior:10086 # 正确做法异步删除主线程只做摘链 UNLINK user:behavior:10086对于集合类型的大key还可以用HSCANHDEL分批删或者LTRIM逐步截断把一次大操作拆成多次小操作。这些细节面试时能说出来基本就说明你动过手。3.2 集群场景的内存倾斜与迁移卡顿单机实例的大key问题还只是性能层面上了Cluster之后问题会放大成架构层面。Redis Cluster按槽位slot分片一个key落在哪个节点由CRC16计算决定。大key的问题在于它的全部数据都压在一个节点上而这个节点的内存可能因此远超其他节点。我见过一个集群八个节点其中两个节点的内存使用率到85%其余六个才40%原因就是几个大key恰好落在这两个槽上。这种倾斜的直接后果是内存告警阈值被迫按最高节点设整体资源利用率被拉低而且这个节点一旦内存打满触发淘汰淘汰的可能是不该淘汰的热点数据。迁移场景更糟。集群扩容或者做槽位重分配时需要把槽上的key搬到新节点。迁移单个key用的是MIGRATE命令这个命令是同步阻塞的——搬一个几十MB的key源节点和目标节点都会卡住。如果这个key还在被高频写入迁移还可能失败重试形成恶性循环。所以集群环境下的大key治理优先级比单机更高因为它影响的不只是性能还有运维弹性。3.3 持久化与主从同步的连锁反应第三个容易被忽略的点是持久化和复制。RDB持久化时fork出的子进程要遍历所有key写入磁盘。大key会让这个遍历时间变长fork期间的写时复制COW内存页也会增多极端情况下可能触发内存翻倍。AOF重写同理大key的一次重写会产生巨大的写入量。主从同步方面全量同步时大key要整体传输增量同步时对大key的一次修改可能产生很大的复制积压。如果从节点因为网络抖动断连、需要部分重同步而复制积压缓冲区里正好卡着一个大key的变更就可能被迫降级为全量同步又是一轮带宽和CPU的消耗。这三层危害叠在一起就解释了为什么大key治理要做成常态化的事情而不是等出事了再救。面试时如果能把阻塞—倾斜—复制这三条链路讲清楚比单说会变慢要高一个层次。4. 拆分方案的核心思路从一刀切到分而治之4.1 按业务维度拆分先想清楚数据该不该放一起很多人一上来就想技术方案我觉得顺序反了。拆分的第一步是问业务这些数据真的需要放在同一个key里吗以用户行为流水为例原始设计是user:behavior:{uid}一个List装下所有行为。但业务上真的需要一次取出该用户全部行为吗绝大多数场景是分页查最近N条、按时间段查、按行为类型查。既然是分片访问那数据结构就该按访问模式来切。常见的切法有几种按时间切key变成user:behavior:{uid}:{yyyyMM}一个月一个List天然带过期查询按月份路由按类型切user:behavior:{uid}:{actionType}不同行为分开存按序号切user:behavior:{uid}:{bucket}bucket由记录ID对分片数取模得到。这三种切法没有优劣取决于查询模式选错了要么查询要合并多个key要么分片不均。我倾向用时间维度优先原因是它天然解决了数据增长问题——老数据可以整体过期不用写清理逻辑而且时间维度的查询边界清晰路由计算简单。缺点是按时间范围跨月查询时需要聚合多个key这个在6.1节讲怎么处理。4.2 Hash结构按field分片把一个大桶拆成多个小桶如果原结构是Hash且业务上确实需要一个逻辑实体对应一个Hash的语义那拆法是把field分散到多个物理key上。核心是设计一个稳定的路由函数。最常用的是对field做哈希后取模public class ShardedHash { private final int shardCount; private final String keyPrefix; public ShardedHash(String keyPrefix, int shardCount) { this.keyPrefix keyPrefix; this.shardCount shardCount; } // 计算field落在哪个分片 private String shardKey(String field) { int idx Math.floorMod(field.hashCode(), shardCount); return keyPrefix : idx; } public void hset(String field, String value) { // 单field操作直接路由无需广播 } }分片数怎么定我一般按预估总量 / 单分片目标元素数来算然后向上取2的幂。比如预估某实体最多十万个field单分片希望控制在五千以内那 100000/500020取32。取2的幂的好处是可以用位运算代替取模而且扩容时可以成倍扩、做一致性迁移。这里有个必须提前想清楚的问题分片数一旦定下后期扩容很麻烦。从32扩到64原来hash % 32落在1号分片的field扩完之后可能要去33号分片等于全量数据要重分布。所以我的经验是一次性预留余量宁可多分几个空分片也别定得太紧。真的需要扩容就走双倍扩容 后台迁移的路子读的时候先查新分片、miss再查老分片迁移完成后切掉老逻辑。4.3 大value的压缩与结构替换不一定要拆可以先瘦身还有一种情况是元素数量不多但单value特别大比如一个String存了几百KB的JSON。这种key拆起来意义不大更有效的是先瘦身。瘦身手段按投入产出排序一是字段裁剪很多JSON里塞了大量前端不用、后端也不查的冗余字段砍掉能省一半二是换序列化JSON换成Protobuf或者MessagePack体积通常能降到原来的三分之一到一半三是冷热分离把大JSON里高频访问的少数字段抽出来单独存一个小Hash完整数据放到低频存储四是压缩后再存用GZIP或者LZ4压缩后以二进制形式存Redis读的时候解压。压缩的代价是CPU得权衡如果这个key的读取QPS很高压缩解压的开销可能比省下的带宽更贵。我一般不建议一上来就压缩因为排障时会变得很难看——在Redis客户端里看到一堆乱码排查问题很痛苦。除非体积实在降不下来压缩是最后手段。4.4 三种方案怎么选一张决策表场景特征推荐方案理由代价按时间/类型分片查询业务维度拆分查询边界清晰可整体过期跨片聚合逻辑实体必须整体存在Hash field分片保持语义分摊压力路由逻辑、扩容难元素少但单value大瘦身/换序列化不改结构见效快字段裁剪需业务配合历史数据只读归档到冷存储Redis只留热数据需要归档链路选方案的时候我有个原则优先改数据结构其次改存储介质最后才动分片逻辑。因为分片逻辑一旦引入所有读写路径都要跟着改代码复杂度上升出bug的概率也上升。能用业务切分解决的就别引入shard。5. 落地实操一次完整的拆分改造5.1 拆分前的数据盘点与容量估算改造前必须先把账算清楚否则拆完之后要么分片不均要么容量不够。第一步统计现有数据分布。用RDB分析工具导出所有同前缀的key统计总元素数、单个key的元素数分布最大值、P99、中位数、总内存占用。这一步决定了分片数的下限。第二步估算增长速率。取最近30天的数据增量算出日均增长再乘上业务规划的生命周期比如数据保留90天得到目标容量。这一步决定了分片数的上限。第三步反推分片数。分片数 目标容量 / 单分片目标元素数然后向上取2的幂并预留30%余量。举个实际算过的例子某业务线当前有八百万条记录日增五万条保留180天那么目标容量是 800万 5万×180 1700万单分片目标控制在一万以内1700万/1万 1700取2048个分片。2048听起来多但每个分片才一万条单分片内存不到1MB完全可控。注意分片数不是越多越好。分片太多会导致key数量爆炸Redis的key元数据本身也占内存每个key大概几十到上百字节的开销2048个分片对应2048个key这个量级没问题但如果拆到十万个分片就要重新算了。5.2 代码层面的读写改造封装成统一入口拆分之后最怕的是业务代码到处散落着拼key的逻辑改一处漏一处。我的做法是在DAO层封装一个ShardedCollection组件把路由、批量、聚合全部收进去业务层只看到往这个逻辑集合里写一条记录。写路径相对简单路由到具体分片直接写public class BehaviorRepository { private static final int SHARDS 2048; public void add(long uid, BehaviorRecord record) { // 先按时间维度定主key再按记录ID做哈希定分片 String month record.getMonthKey(); int shard Math.floorMod(Long.hashCode(record.getRecordId()), SHARDS); String key String.format(user:behavior:%d:%s:%d, uid, month, shard); // 写入时用LPUSH LTRIM保证单个分片不超过上限 jedis.lpush(key, record.toJson()); jedis.ltrim(key, 0, MAX_PER_SHARD - 1); jedis.expire(key, EXPIRE_SECONDS); } }这段代码有几个细节值得说。一是LTRIM兜底防止某个分片因为哈希不均无限增长二是每个key都设过期靠Redis自己回收而不是靠定时任务清理三是过期时间要比业务保留期长一点留出容错空间。读路径复杂一些因为要判断查哪些分片。如果是按记录ID精确定位直接算哈希就行如果是按时间范围查最近N条就得遍历该时间段内所有分片每个分片取N条再归并排序。这个开销要提前评估如果N很大、分片很多就得考虑在分片之上再加一层最近N条的汇总List。5.3 双写与平滑迁移不能停机也不能丢数据生产环境改造最怕的是切换瞬间丢数据或者读到不一致。我的标准流程是四步第一步双写。新代码同时写老key和新分片读还是读老key。这一步验证新逻辑的正确性跑一周左右。第二步对账。写一个离线任务按天对比老key和新分片的记录数与关键字段确认双写没有漏。这一步不能省我见过双写逻辑里对某些边界记录漏写的案例。第三步切读。把读路径切到新分片老key只写不读。观察几天确认读逻辑没问题尤其要压测聚合查询的性能。第四步停写老key并清理。写路径完全切到新结构老key用UNLINK异步删掉或者直接等过期。整个过程的核心原则是任何一步都可回滚。切读之后如果发现问题把开关切回老key读就行数据还在。这个开关建议做成配置中心的一个布尔值不要写死在代码里。public ListBehaviorRecord query(long uid, String month, int limit) { if (config.useNewShard()) { return queryFromShards(uid, month, limit); } return queryFromLegacy(uid, month, limit); }5.4 拆分后的聚合读取把多key查询的成本算清楚拆分换来的代价是查询要合并多个key。这里必须算一笔账一次查询要访问多少个key、总耗时多少、能不能接受。假设2048个分片查某用户某月最近20条最坏情况要查2048个key每个keyLRANGE 0 19。用Pipeline批量发一次网络往返搞定但服务端要执行2048次命令。如果单次命令耗时0.5毫秒串行就是1秒Pipeline能压到几十毫秒但2048这个量级还是偏重。优化思路有两条。一是减少分片范围如果记录ID本身带时间信息比如雪花ID可以按ID区间缩小候选分片二是引入汇总层写入的时候除了写分片再往一个最近N条的List里写一份查询直接读汇总层分片只用于翻历史。汇总层用LPUSH LTRIM维护固定长度写入成本很低查询成本是O(1)。我一般推荐第二种因为它把复杂度放在了写路径写入多一次操作而写路径通常可以异步化读路径的延迟才是用户能感知的。这是我踩过坑之后的取舍一开始想省写入成本只做分片查询结果读延迟上去了后来老老实实加了汇总层。6. 多key带来的新问题聚合查询怎么扛6.1 MGET与Pipeline的取舍别用错场景拆分之后必然要批量读多个key这里有两个常用手段很多人分不清什么时候用哪个。MGET是Redis原生命令一次请求带多个key服务端逐个取值后打包返回。它的限制是只能用于String类型而且这些key必须落在同一个节点——在Cluster模式下MGET的多个key如果跨槽客户端会报CROSSSLOT错误。Pipeline是客户端能力把多条命令攒着一次发出去服务端按顺序执行后一起返回。它不限制命令类型也不限制key的数量但Cluster模式下需要客户端自己按节点分组每个节点发一个Pipeline。选型很简单同节点、同类型、key数量固定用MGET跨节点、混合类型、需要动态拼命令用Pipeline。我在实际项目里基本都是Pipeline因为拆分后的key天然跨槽MGET用不上。// Pipeline 批量读取多个分片 public ListString batchGet(ListString keys) { Pipeline p jedis.pipelined(); ListResponseString responses new ArrayList(keys.size()); for (String key : keys) { responses.add(p.get(key)); } p.sync(); return responses.stream().map(Response::get).collect(Collectors.toList()); }注意Pipeline一次塞太多命令会出问题。客户端发送缓冲区暴涨、服务端回复缓冲区暴涨都可能触发内存告警。我一般控制在单批500到1000条超过就分批。6.2 跨槽问题的绕法hash tag是把双刃剑Cluster模式下想让多个key落在同一个槽标准做法是hash tag——用花括号把决定槽位的部分包起来比如user:{10086}:behavior:1和user:{10086}:behavior:2因为花括号里的内容相同CRC16算出来的槽位就一样。这招能解决MGET和事务的跨槽问题但用之前必须想清楚后果同一个tag的所有key都压在一个节点上。如果你的tag是用户ID那没问题天然分散如果tag是某个全局维度比如业务线ID那就等于把所有数据又堆到一个节点拆分白做了。所以hash tag的正确用法是tag跟着分片维度走不跟着逻辑实体走。比如按用户分片就用用户ID做tag按月份分片就用月份做tag绝不要用固定字符串。6.3 路由表方案分片规则可能变化时的兜底还有一种情况分片规则本身可能会变或者业务需要按多种维度查询。这时候硬编码路由函数就不够用了得引入一张路由表。路由表本身可以存Redis用一个小Hash也可以存本地缓存加定期刷新。内容是逻辑标识 → 物理key列表的映射。查询时先查路由表再按列表批量取。代价是多一次查询收益是路由规则可以动态调整不用改代码。这套方案我只在分片维度特别复杂的场景用过一般业务用固定哈希就够了。引入路由表会增加一致性问题路由表和实际数据不同步非必要不上。7. 常见问题与排查实录7.1 拆分之后反而变慢了这是最常见的反馈。原因通常有三类。一是分片太多导致批量查询开销大。前面算过2048个分片全扫一遍的成本不低。解决办法是加汇总层或者把分片数降下来、单分片容量提上去找一个平衡点。二是Pipeline批次太大。一次发几千条命令网络往返是少了但客户端和服务端的缓冲区压力上去了反而增加了GC和内存拷贝。分批到500左右通常是最优区间。三是路由计算本身成了瓶颈。如果每次查询都要遍历所有分片算一遍key名字符串拼接的开销也不小。优化办法是预先计算好key模板、用StringBuilder代替format、把分片列表缓存起来。7.2 迁移过程中数据对不上双写阶段最怕这个。排查顺序建议是先确认双写的两个分支是不是都执行了有些代码里异常被吞了只写成功一个再确认路由函数在两个分支里算出来的是不是同一个分片我遇到过一次老代码用hashCode新代码用CRC32导致对不上最后确认时间边界比如跨月的那条记录到底算上个月还是下个月。对账脚本要能定位到具体记录而不是只报总数差异。我的做法是按天、按用户维度做分层对账先看哪个用户对不上再看这个用户的哪条记录对不上逐层收敛。7.3 热点key从大key变成了热key拆完大key有时候会发现某个分片的QPS特别高——因为哈希不均或者某个用户的数据特别集中。这时候要再引入一层打散比如在分片号上再加一个随机后缀写多份或者写随机一份读的时候随机挑一个。这个方案的代价是空间翻倍或者读可能miss得看场景值不值。我的经验是拆分前先看分布别拍脑袋定分片数。用真实数据跑一遍哈希分布看看最大分片和平均分片的比值超过3倍就调整分片数或者换哈希函数。7.4 排查速查表现象可能原因排查手段处理方式实例周期性卡顿大key的DEL或全量读--bigkeys 慢查询日志拆分 UNLINK集群节点内存不均大key落在少数槽MEMORY USAGE逐key测拆分或迁移槽扩容迁移超时单key过大导致MIGRATE阻塞观察迁移日志先拆再迁拆分后读延迟升高分片过多或批次过大打点统计Pipeline耗时加汇总层/分批双写对账不一致路由函数不一致/异常吞掉分层对账脚本统一路由异常上报某分片QPS偏高哈希不均或数据倾斜统计各分片访问量加盐打散8. 面试作答模板与经验收尾8.1 怎么回答才显得有实战如果让我组织这道题的答案我会按这个顺序讲先给判定标准体积和元素数两个维度报出具体量级再讲危害阻塞、倾斜、复制三条链路每条举一个具体现象然后讲拆分方案业务维度、field分片、瘦身三条路说明各自的适用场景最后讲落地细节双写迁移、聚合读取、热点处理。关键是在最后一部分多花时间。因为前三部分背一背就有最后一部分只有真做过才讲得出来。比如你可以说我们在切换时用了配置开关做双读切读之后观察了一周才停老key的写。这种细节不需要编做过的人自然有。还有一个小技巧主动提一个你踩过的坑。面试官对我遇到过一次xxx后来改成yyy这种叙述的兴趣远大于对标准答案的兴趣。我通常会讲DEL大key导致抖动、改用UNLINK这件事因为它具体、可验证、有明确的改进动作。8.2 我在实际项目里的几点体会最后说几句掏心窝的。大key治理这件事预防的成本远低于治理。设计阶段多想一步——这个List会不会无限增长、这个Hash的field会不会越来越多——比上线后补救省事得多。我现在评审任何涉及Redis的存储设计第一句话就是问这个key的上限是多少怎么保证不超。第二个体会是别追求一次拆到位。我见过有团队想一步到位设计一套完美的分片方案结果方案评审了两个月线上还在被大key折磨。正确做法是先止血改全量读为分页读、DEL改UNLINK再按优先级逐个拆边拆边观察。第三个体会是监控要跟上。拆分完之后如果没有对单key大小、单分片QPS的监控过半年又会冒出新的问题。我现在会在写入路径上加一个轻量的长度检查超过阈值就打点告警不阻塞业务但能提前发现问题。这个成本很低收益很高。这道题的价值不在于背答案而在于它串起了Redis的模型认知、容量设计、迁移方案、性能排查一整套东西。真把这一套理顺了其他类似的某个组件在某个场景下怎么优化的题思路都是通的。
阅读完成 · 觉得有帮助?
咨询建站