主从复制超时主因是大Key单次传输卡死,非网络问题;hash或zset超几MB致序列化堵塞缓冲区,引发断连与重试循环,需渐进拆分并统一命名与封装。

主从复制超时根本不是网络问题,是大Key单次传输卡死流
Redis主从复制中看到Timeout waiting for bulk read或REPL_TIMEOUT,别急着查网卡、调TCP参数——90%以上是某个hash或zset超过几MB,主节点把它整个序列化后塞进一个REPLCONF包里发出去,直接堵死复制缓冲区。从节点等不到完整包就断连,重试又重传,形成恶性循环。
典型佐证:INFO replication里slave_repl_offset长期不动,而master_repl_offset持续上涨;同时net_output_bytes突增,但其他客户端请求延迟没明显变化。
-
client-output-buffer-limit slave默认是256mb 64mb 60,一个3MB的hash在高QPS下可能1秒内就打穿缓冲区上限 - Redis 6.0+开启
repl-diskless-sync yes只解决RDB传输阶段IO,不改变大Key在增量同步(PSYNC)中的单次传输行为 - 用
redis-cli --bigkeys能扫出大Key,但它不会告诉你这个Key正在让从节点掉线
拆分必须渐进式,不能DEL也不能HDEL原Key
直接DEL或HDEL大Key会触发主线程O(N)阻塞,且该删除命令本身又作为一条“巨量操作”同步到从节点,等于二次冲击。正确做法是在主节点上用游标分批读+写新结构,全程走AOF和复制流。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 对
hash:用HSCAN myhash 0 COUNT 100拉字段,写入myhash:shard_001等新key,再用HDEL myhash field1 field2...删原字段 - 对
set:用SSCAN myset 0 COUNT 500分片,写入myset:part1~myset:partN,最后用SREM myset member1 member2...清理 - 脚本里必须加
SLEEP 0.01(如用redis-cli --eval跑Lua),否则连续扫描会压垮主节点CPU - 所有操作必须在主节点执行——从节点只读,不参与任何拆分逻辑
切片命名和写封装不统一,上线即埋雷
拆完只是开始,后续增长会让某个分片再次变大。如果命名没带版本号、没统一路由逻辑,业务代码直接硬编码myhash:shard_001,下次扩容或修复就得改全量代码。
- key命名强制带版本和分片标识,例如
user:123:orders:v2:shard_001,避免v1/v2逻辑混用 - 所有写操作必须走封装函数(如
hash_sharded_set($key, $field, $value)),内部算shard_id并路由,禁止业务层自己拼key - 上线后盯住两个监控项:
HLEN(字段数)和DEBUG OBJECT <key>返回的serializedlength(实际序列化后字节数),阈值建议设为HLEN > 600或serializedlength > 700000
监控只看replication lag,等于盲人摸象
只监控slave_repl_offset - master_repl_offset差值,会把网络抖动、从节点GC、甚至磁盘慢IO都当成大Key问题。真正要抓的是“滞后突增是否与某个大分片首次同步强相关”。
- 用
redis-cli -r 1 -i 1 info replication | grep lag每秒采样一次,观察lag是否在某个时间点阶梯式跳升 - 配合
redis-cli --bigkeys和MEMORY USAGE定期扫描,重点盯hash/zset类型,别只扫string - 大Key不一定实时影响复制——冷数据的大Key可能几个月都不触发同步,但一旦被更新,立刻引爆
最麻烦的不是发现大Key,而是它藏在低频写路径里:比如用户注销时批量清理订单历史,那个DEL user:123:orders命令在凌晨三点执行,没人盯着日志,但从节点已经断连三次了。

















