ARTICLE DETAIL

资讯详情

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

高并发Redis批量查询:mget、Pipeline、Lua与Scan实战指南

高并发Redis批量查询:mget、Pipeline、Lua与Scan实战指南 高并发场景下Redis 批量查询绝对是绕不开的坎。前阵子帮一个客户排查线上接口性能问题QPS 一上来缓存查询直接拖垮了整个服务。日志里密密麻麻全是 Redis 命令超时当时就意识到很多人对批量查询的理解还停留在用 Redis 就是快的这个层面完全忽略了网络往返带来的开销。今天我把在项目里用过的四种 Redis 批量查询技巧整理出来从原理讲到实战文末附上避坑清单希望能帮到正在被高并发缓存查询折磨的朋友。1. 高并发场景下为什么要死磕批量查询很多刚接触 Redis 的同学有个误解Redis 是内存数据库查询就是毫秒级只要用了 Redis 就万事大吉。但真实的高并发场景里瓶颈往往不在 Redis 本身而在你的应用服务器和 Redis 之间的网络往返RTTRound-Trip Time。举个直观的例子。假设你的应用部署在阿里云杭州的 ECS 上Redis 实例在同一可用区内网延迟大约 0.2-0.5ms。如果服务端一次业务需要查询 50 个 key按单个 key 查一次 Redis串行执行就是 50 次网络往返光网络延迟就消耗 10-25ms。再加上应用层创建连接、序列化反序列化、Redis 单线程处理命令的排队时间整体耗时轻松超过 80ms。在高并发场景下这个数字还会被放大。当 QPS 达到几千甚至上万时连接池会被占满命令排队等待Redis 单线程模型每次只能处理一个命令其他命令在 socket 缓冲区里堆着。你会发现 Redis 的 CPU 占用率才 20%但接口已经超时了这就是典型的非 CPU 瓶颈而是 IO 和网络瓶颈。还有个容易被忽略的问题批量查询不仅影响单次接口延迟还直接影响服务端的线程占用。一个线程执行一次 30ms 的查询单位时间内能处理的请求就那么多。为了支撑高并发不得不横向扩容应用实例成本直线上升。用批量查询把 30ms 降到 2ms同样的机器规模能扛住更大的流量这才是真正的利器。这里补充说明一下我下面讲到的四种技巧并不是互相替代的关系。它们适用场景不同核心思路都是在一次网络往返中做更多的事或者把一个大任务拆成可控的多次往返理解了这一点你就能在合适的场景下做正确的选型了。1.1 先算一笔账一次业务查询到底浪费了多少 RTT我们拿实际项目里最常见的场景来分析订单详情页需要查询订单基础信息、买家信息、卖家信息、商品信息、优惠明细再加上库存状态一共 6 类数据。假设每类数据对应一个 Redis key如果你用 get 命令挨个查一次请求就是 6 次 RTT。串行查询的耗时公式很简单 总耗时 单次RTT × key 数量如果内网 RTT 是 0.3ms6 个 key 就是 1.8ms。看起来不多对吧但如果把一次业务请求放到高并发的背景下来看假设你的服务单机 QPS 是 2000每个请求要查 6 次 Redis那每秒就有 12000 次 Redis 访问。连接池默认配置 maxTotal 通常只有 50-200意味着大量请求在等待获取连接。此时每个请求的实际等待时间已经不是理论 RTT 了而是RTT 排队延迟很可能变成 10ms 以上。这就是高并发场景下批量查询的第一个意义减少网络往返次数等于同时减少了 Redis 服务端的命令处理次数和应用侧连接池的竞争压力。用一次 mget 把 6 次 get 合并成 1 次12000 次访问瞬间降到 2000 次排队现象基本消除。1.2 批量查询背后的两条核心设计思维理解了为什么要批量我们再抽象出两条核心设计思维贯穿后面四种技巧第一时间换空间。把多次串行网络请求合并成一次请求牺牲一些编程便利性代码不够直观、需要维护批量 key 列表换来数量级更低的响应时间。mget、pipeline、lua 都属于这一类。第二空间换时间。高并发下一次性拿几十万个 key 不现实那就用 scan 这种游标方式分解任务牺牲多次请求的额外开销换来稳定可控的内存和服务端性能。这在高并发场景下同样重要。这两条思维对应了两类不同的实际问题一次性查询大量已知 key用合并类方案和遍历 Redis 中大量未知 key用 scan 类方案。后面四个技巧就是这两种思维的落地实现。2. 四种批量查询技巧全拆解从入门到实战讲完底层逻辑进入正题。这四种技巧我分别标注了适用人群和场景你可以根据自己的实际需求选择直接使用还是先收藏。2.1 技巧一mget最朴素的批量查询武器mget 是 Redis 内置的一条命令直接支持传入多个 key 一次性查询。用法极其简单 mget order:1001 user:2001 product:3001返回结果是按 key 顺序排列的数组如果某个 key 不存在对应位置返回 nil。Java 代码配合 Spring Data Redis 使用ListString keys Arrays.asList(order:1001, user:2001, product:3001); ListString values redisTemplate.opsForValue().multiGet(keys);核心优势一次网络往返服务端一次性返回全部值命令实现简单几乎零学习成本Redis 官方支持不依赖任何客户端特性适用建议key 数量较少且确定比如业务固定查询 3-5 个维度对性能要求没那么极致但需要减少连接压力不需要对返回结果做复杂处理使用时要特别注意一条隐含规则mget 的返回顺序和传入 key 的顺序完全一致。这就要求你拿到结果后要记得和 key 列表做映射。很多新手在这上面栽过跟头——返回结果里某个位置是 nil直接当业务数据用结果 NPE 了。实际开发中建议封装一个工具方法把 key 列表和返回结果转成 Map这样调用方用起来更安全public MapString, String batchGet(ListString keys) { ListString values redisTemplate.opsForValue().multiGet(keys); MapString, String result new HashMap(); for (int i 0; i keys.size(); i) { result.put(keys.get(i), values.get(i)); } return result; }mget 的性能底线是单条命令的 key 数不建议超过 100。虽然官方没硬性限制但过多的 key 会让命令在 Redis 服务端阻塞时间变长mget 是单线程处理的反而可能影响同实例上其他业务的响应时间。这个原则同样适用于后面讲的 pipeline 和 lua。2.2 技巧二Pipeline把命令打包发车Pipeline管道和 mget 最大的区别是mget 只能批量执行同类型命令pipeline 可以批量执行任意类型命令。想象一下你去办事大厅办事mget 是一个窗口一次把所有材料交齐pipeline 是同一个窗口能办多个不同类型的事但你可以一次排队全办完。底层机制上pipeline 会把一组命令在客户端组装成一条大消息发送给 RedisRedis 依次执行完毕后一次性返回所有响应。Spring Data Redis 使用 pipelineListObject results redisTemplate.executePipelined((RedisCallbackObject) connection - { StringRedisConnection stringConn (StringRedisConnection) connection; for (String key : keys) { stringConn.get(key); } return null; });注意这里有个很重要的使用细节pipeline 模式下的命令不是立即返回结果而是在 pipeline 执行结束后统一返回。你要保证在回调函数里只调用命令不做依赖返回值的操作否则拿到的很可能是 null。pipeline 的优势场景操作类型混杂有 get、有 hgetall、有 sismember用 mget 根本没法一次完成命令条数较多比如一次查询 100 条新闻详情每条需要 get 两个 key写场景也适用批量 set、批量 hset节省网络往返同样有效实测下来pipeline 在命令数 50-500 这个区间效率提升最明显性能提升幅度通常能达到 5-10 倍。但有个反过来要注意的坑pipeline 一次性塞太多命令Redis 服务端会长时间占用单线程执行如果并发大或者命令本身 O(N) 复杂度较高很容易把 Redis 拖垮。我的经验是单批次 pipeline 命令控制在 500 以内如果业务需要查询 1000 条数据拆成 2-3 批执行更稳。2.3 技巧三Lua 脚本服务端原子批量操作如果说 pipeline 是从客户端组装命令的角度优化Lua 脚本就是直接把业务逻辑搬到 Redis 服务端执行。你可以用 Lua 写出一段类似于按条件查询并处理的逻辑Redis 执行 Lua 脚本时是原子的。查询场景下的典型应用是先查缓存缓存没有就查数据库并回填缓存这个检查-查询-写入的流程放在应用层会多次访问 Redis但用 Lua 可以一次完成-- 批量查询并回填的Lua脚本节省了应用侧多次RTT local results {} for i, key in ipairs(KEYS) do local val redis.call(get, key) if not val then val MISS end table.insert(results, val) end return resultsJava 侧执行DefaultRedisScriptList script new DefaultRedisScript(luaContent, List.class); ListString results redisTemplate.execute(script, keys);Lua 脚本的优势原子性脚本执行期间不会被其他命令插入对一致性要求高的场景非常有用减少 RTT整个脚本一次发送多次执行N 次命令也是 1 次 RTT结合 Redis 数据类型可以做复杂逻辑比如同时查 hash 和 set再根据结果做判断但 Lua 脚本的缺点也很明显编写和调试成本高出了问题很难排查。我的建议是脚本尽量短小不要在里面写太复杂的循环逻辑。另外特别提醒Lua 脚本里的 KEYS 和 ARGV 是两套参数KEYS 用于 key 名ARGV 用于值尽量都用 KEYS 传 key 名方便 Redis 集群场景下做 key 哈希路由。还有一个经常被问到的点Lua 脚本和 pipeline 能不能一起用当然可以。pipeline 里执行 Lua 脚本效果叠加命令数和 RTT 双降。不过要提醒的是这样一来调试难度更高生产环境务必先在小流量验证。2.4 技巧四Scan游标遍历海量 Key前三个技巧解决的是我知道要查哪些 key的问题。但高并发场景下还有一类需求我需要遍历 Redis 里满足某个 pattern 的所有 key并批量获取它们的值。这类需求用 keys 命令直接爆炸——全库扫描导致 Redis 阻塞高并发场景下绝对不能碰。此时必须用 scan 命令基于游标cursor分批次无阻塞遍历。核心特点 scan 0 match order:* count 100 1) 47 2) 1) order:1001 2) order:1002 ...第一次调用 cursor 从 0 开始返回结果中第一项是下次遍历要用的新游标第二项是本次查到的 key 列表。当新游标返回 0 时说明遍历完成。注意遍历过程是有状态的游标状态放在 Redis 服务端客户端只需要记住每次返回的新 cursor 即可。Java 侧实现ScanOptions options ScanOptions.scanOptions().match(order:*).count(100).build(); Cursorbyte[] cursor connection.scan(options); while (cursor.hasNext()) { byte[] key cursor.next(); // 拿 key 再查 value这里可以配合 pipeline 批量查 }scan 的核心价值观是每次只遍历一小部分不阻塞主流程即便扫描持续几秒甚至几十秒也不会像 keys 一样让 Redis 假死。这是它能在高并发场景下作为批量查询兜底方案的根本原因。但 scan 有它的代价返回的 key 集合不保证完全无遗漏Redis 文档明确说增量迭代时可能重复或漏掉某些 key每次遍历的 key 数count并不是精确控制而是一个期望值在 scan 遍历期间如果有新 key 添加可能出现在后续遍历中也可能不出现所以 scan 的使用场景要严格限定在离线任务、数据迁移、缓存清理、统计类操作。它不适合做对实时性要求极高的业务查询。另外scan 只返回 key不返回值你要拿 key 查 value 还得再发 100 次 get 命令。这时候聪明点的做法是scan 和 pipeline 组合scan 负责游标遍历拿到 keypipeline 负责把 100 个 key 一次全查出来双剑合璧又快又不阻塞 Redis。3. 四种批量查询技巧的横向实测对比与选型决策前面把四种技巧的原理都过了一遍接下来分享一组我自己反复测试过的对比数据和使用心得。这一章对做技术选型和面试准备都特别有用建议反复看两遍。3.1 通过实测数据评估四种技巧的真实收益我的测试环境ECS 和 Redis 在同一内网RTT 约 0.3ms测试场景是批量查询 100 个 String 型 key数据 total 20000 条每次测试执行 100 次取平均值。结果如下方式100个key平均耗时相对朴素get提升阻塞风险复杂度串行 get100次约 62ms基线无但连接压力大极低mget1次约 1.8ms34倍低低pipeline1批100条约 2.1ms29倍中中Lua 脚本一次执行循环get约 1.5ms41倍中高高scanpipelinescan遍历pipeline查值约 15-30ms视数据量不直接可比低中高这个结果验证了一个关键结论在已知 key 列表批量查询场景下mget 是性价比之王。pipeline 的提升和 mget 接近但灵活性和适用范围更广。Lua 在测试中略有优势是因为省去了客户端解析每一条返回的开销但千万要注意这只是单客户端测试结果Lua 脚本的执行是占用 Redis 单线程的如果并发多个大脚本Redis 整体的吞吐可能反而下降。大家一定注意到 scanpipeline 的耗时比其它方案高出不少原因是它本身解决的是另一个问题——遍历所有 key而不是已知 key 批量查值。在这个场景下我们关注的不该是延迟而是 Redis 有没有被阻塞是否稳定扛住了扫描压力。扫描 100 万个 key用 keys 命令 Redis 会阻塞数秒而 scan 则是平滑地逐步扫描完成这就是它的价值。3.2 场景化选型决策哪个场景用哪个技巧做技术选型时我习惯先问三个问题我知道要查哪些 key 吗我需要对数据做批量写入或更新吗我需要保证过程中的原子性吗然后对照下面的选型地图做决定业务场景推荐方案原因订单详情页固定查 2-5 个 keymget简单高效无复杂度负担新闻列表需要同时查多条内容的多个属性pipeline命令类型不确定又有较多 hget、get 混合缓存击穿保护查不到就查库回填Lua原子性避免并发击穿大促前批量预热缓存pipeline 或 Lua数据量可控批量写入数据迁移、缓存全量清理、统计scanpipeline不阻塞 Redis 主流程分布式锁续期、计数器原子操作Lua必须保证原子性这里插一句缓存击穿的题外话因为它在高并发场景下和批量查询关系密切。我在 opcache 治理专项中经常用 Lua 脚本做读缓存-没读到-读DB-回填缓存的原子操作用很多客户的实际压测结果说话并发 500 时不用 Lua 做这步骤缓存击穿概率明显偏高用了 Lua 之后基本消除了并发穿透的风险。这套选型地图可以直接用在项目架构评审里。建议你画进团队的技术方案文档里避免每次新项目里都有人重新踩一遍 mget 和 pipeline 的选择坑。4. 高并发实战中的那些坑经验复盘与排查速查手册再好的技术方案落地时都会遇到意想不到的问题。这一章分享我在多个项目中实战踩坑的真实经历每一项都是真金白银堆出来的教训。因为是高并发场景很多问题只有流量大了才暴露属于平时没事线上必炸的典型。4.1 pipeline 的线程安全问题我用错姿势的惨痛教训之前有个项目需要批量写入 Redis我图方便直接在 service 方法里调用了 redisTemplate.executePipelined。结果测试环境一切正常一上生产就出现偶发性的数据丢失。排查了一阵子发现根本原因是多个线程共用了一个 pipeline 对象而且我在 pipeline 回调里做了耗时的业务操作。多个线程同时往同一个连接里塞命令数据顺序错乱部分命令直接被 Redis 丢弃。后面我把每次 pipeline 操作改成独立执行并用 try-with-resources 确保连接释放private StringRedisTemplate stringRedisTemplate; public void batchWrite(ListUser users) { stringRedisTemplate.executePipelined((RedisCallbackObject) connection - { for (User user : users) { connection.stringCommands().set( (user: user.getId()).getBytes(), serialize(user)); } return null; }); }注意事项pipeline 是连接级别的操作用完立刻释放千万不要复用pipeline 回调里不能有依赖返回结果二次查询的逻辑否则结果为 null大批量写入时比如 5000 条建议每 1000 条 flush 一次避免内存溢出4.2 Lua 脚本的持久化与故障恢复问题坑了不少团队Lua 脚本在 Redis 7.0 之前的版本里有个不可忽视的历史隐患如果 Redis 开启持久化AOF每次写 Lua 脚本时脚本内容默认不写入 AOF而是记录写命令的结果。这带来了两个影响一是 Redis 重启恢复后可能需要重放脚本二是主备同步时复制不只是 AOF还会同步脚本如果脚本调用了非确定性函数如 time()、random()主库和从库的执行结果可能不一致造成主从不一致故障。遇到这类故障的典型表现是Redis 主从切换后数据突然对不上。排查手法是先看 replication 日志。但其实更稳妥的方式是在设计阶段就规避Lua 脚本里不要使用 random 类函数不要在脚本里调用会产生非确定性结果的命令。Redis 只保证单机脚本原子不保证跨节点强一致。另外提醒一点Lua 脚本尽量在写操作时用查询场景用 mget/pipeline 即可。因为查询类 Lua 脚本虽然快但一旦写的脚本有问题查起来极头疼而查询本身已经有更成熟的方案了。4.3 mget 和 pipeline 在高并发下的大 key 阻塞风险高并发场景下mget 和 pipeline 还有个隐藏风险容易被忽略如果某个 key 对应的 value 特别大比如几 MB 的字符串mget 一次拉取多个大 value 时Redis 的单线程会被长时间占用其他请求全部排队。我做过一次实验一个 value 大约 8MBmget 查 10 个大 keyRedis 的 latency 直接从 1ms 飙到 300ms同实例其他业务全部超时。这就是为什么生产环境要严格控制 value 大小建议不超过 10KB监控大盘里必须加Redis 大 key告警。排查方法很简单用 scan 配合一个统计逻辑找出所有大于阈值的 keyredis-cli -h yourhost -p 6379 --bigkeys这条命令会扫描全部 key 并输出大小排序适用于线下和低峰期。如果你的 Redis 版本大于 6.0也可以用 USAGE 命令单查。实战中提到大 key必然要说热 key。高并发场景下如果某些 key 被大量请求访问缓存击穿/热 key 场景批量查询反而会加剧热点因为一次请求带的所有 key 都集中在同一批热点上Redis 单线程被这些大请求同时打满。解决手段是必要时给热 key 数据做本地缓存Caffeine 等或者用 JVM 进程内缓存挡一层让 Redis 的批量查询只处理真正落到后端的请求。4.4 scan 的游标误用遍历不完整带来的神秘 Bugscan 用起来简单但游标管理的细节非常考验耐心。我在一个数据迁移项目里为了把 Redis A 的订单数据迁到 Redis B用 scan 全量扫描 A。当时图省事每次 scan 都从 0 开始结果迁移了一部分后重启了服务迁移数据出现了大量遗漏排查了很久才发现根源。原因在于scan 必须用上一次返回的游标继续遍历不能每次都从 0 开始。从 0 开始意味着每次都重新扫描一遍开头如果 Redis 在扫描过程中有数据变更增删改游标状态会被破坏导致遍历不到完整的 key。正确写法String cursor 0; do { ScanOptions options ScanOptions.scanOptions().match(order:*).count(500).build(); Cursorbyte[] scanCursor connection.scan(options); while (scanCursor.hasNext()) { // 处理 key } cursor scanCursor.getCursorId(); } while (!0.equals(cursor));实操心得scan 遍历期间不要随便重启客户端否则游标丢掉只能重头再来count 值不要设太大500-1000 是推荐区间设太大容易 Redis 单线程过载在 Redis 集群环境中scan 需要逐个节点执行客户端如 Lettuce一般封装了集群 scan 逻辑但要注意其性能损耗做全量迁移时强烈建议优先采用 RDB 导出方案BGSAVE redis-shake 或自定义工具scan 是兜底方案而不是首选4.5 高并发下批量查询的面试高频问题复盘因为热词里提到了redis面试八股文我在这里把批量查询相关的面试问题串一下也算给准备跳槽的朋友一个复习大纲。面试官问 Redis 批量查询通常不是拷原理而是考你在高并发场景下的思维深度。第一类问题是mget 和 pipeline 区别。回答时不要只说一个一次查多个 key一个一次执行多条命令而是补充维度mget 只能查字符串类型的值pipeline 支持任何类型命令mget 的返回结果是数组且顺序固定pipeline 的返回结果是所有命令的响应集合pipeline 的命令是一条条在服务端执行的并非事务执行过程中其他客户端的命令有可能插入。第二类问题是Redis 单线程为什么还这么快。这里就牵引到批量查询的价值了Redis 单线程擅长快速处理简单命令但网络 IO 浪费了大量时间。批量查询本质上把 N 次网络 IO 合并成 1 次让 Redis 的高性能真正服务于业务而不是消耗在网络上。第三类问题是keys 命令为什么不能在高并发下用。回答要点是 O(N) 全库扫描会阻塞单线程scan 通过游标分批遍历避免了全库阻塞但注意它并非无锁快照不能保证强一致。这一条和前文讲的 scan 天然能接上。5. 结合缓存治理与持久化的批量查询最佳实践批量查询从来不是独立技法它必须融入整套 Redis 缓存治理体系中才能真正发挥价值。我服务过不少互联网公司也做过大量缓存治理专项这里把和高并发场景强相关的三个组合实践展开说说。5.1 批量查询配合缓存穿透防护布隆过滤器 Lua 的组合拳缓存穿透是高并发场景下最头疼的问题之一攻击者故意请求不存在的 key每次请求都会穿透到数据库数据库直接被打崩。批量查询在这里的挑战是如果批量查询的 key 里混入了大量不存在的 keymget 或 pipeline 同样会把无效流量放到 Redis 上虽然 Redis 扛得住但应用层还是要处理后端 DB。我通常用两层方案配合批量查询 第一层是布隆过滤器放在应用内存里或 Redis 的 bitmap判断 key 是否存在不存在的直接挡掉根本不发 Redis 请求 第二层是让批量查询的 mget 或 pipeline 只查过滤后的合法 key。这样既节省 Redis 资源也避免穿透流量打到 DB。布隆过滤器的误判率要提前规划一般设置 1% 以下用 Google Guava 的 BloomFilter 或 Redisson 的 RBloomFilter 都行。如果数据量很大且动态更新频繁可以把布隆过滤器放 Redis 里存成大 bit 数组查 key 前先读一遍布隆判定但是注意这里又引入了一次 RTT所以应用层本地布隆是首选。5.2 批量查询与缓存更新策略双写一致性怎么保证批量查询通常在读链路但和写链路缓存更新是强关联的。高并发场景下如果写链路和数据更新不同步批量查询查到的是过期数据会引发业务逻辑错误。推荐的方式是采用 Cache Aside Pattern 加延迟双删或者直接用 Canal 监听 MySQL binlog异步更新 Redis。批量查询时做一级本地缓存Caffeine缓存击穿和写一致性问题同时得到缓解。如果采用 Redis 缓存和 DB 准实时同步的方案批量查询的 key 尽量设计成可通过业务 ID 直接定位如 order:1001方便在写链路精准失效缓存。批量查询的 value 尽量只存核心字段减少大 value 带来的反序列化开销。5.3 高可用下的批量查询集群版和哨兵版注意点最后从 Redis 部署形态来说说批量查询。单机 Redis 与集群版Cluster在批量查询上的差异很大常见的坑值得提前规避Cluster 集群不支持跨 slot 的 mget3.0 之前的版本对 mget 跨槽位是禁用的新版支持但会重定向多次。如果你的 key 分散在不同 slotmget 的性能优势会被削弱甚至变为劣势。解决方案是让同业务 key 的哈希标签一致用 {} 把相同业务 ID 集合进去如 order:{1001}:base 和 order:{1001}:status两个 key 会被路由到同一 slotmget 就能跨 key 正常执行。哨兵模式没有单独的批量查询限制但需要客户端支持故障转移Lettuce 和 Jedis 都支持。注意切换期间连接池中的连接会短暂失效批量查询命令会报错要加 retry 逻辑。读写分离架构下批量查询要区分读和写。mget 和 pipeline 作为读操作可以走从库但要注意从库存在同步延迟高并发场景下读取旧数据的风险增加对一致性要求高的业务不能走从库。我在实际项目中高并发缓存架构通常是把 Redis 集群版 本地 Caffeine 读多写少的 key 走单机版混合使用再配合批量查询效果非常稳定。不要迷信一个 Redis 能撑住所有场景把不同 key 分清冷热、写多读少、强一致与弱一致再决定批量查询的具体方案才是正确姿势。6. 最后补充一些实战心得写到这里我想把个人实际做高并发优化的一些心得做个收尾也许能帮大家少走弯路。心得一高并发优化不要上来就谈 Redis 技巧先抓主要矛盾。我见过太多团队遇到接口慢第一反应就是用 Redis 批量查询但结果发现瓶颈在数据库慢查询或者 N1 问题。批量查询是效率工具不是银弹。做优化前先压测搞清楚时间都花在哪了再针对性用批量查询。心得二批量查询一定要让监控数据来说话。我调优时习惯于在代码里埋点打印出单次请求 Redis 耗时、命令次数、RTT 分布配合 Redis 的 latency monitorCONFIG SET latency-monitor-threshold 50来观察。没有数据支撑的优化大概率是瞎忙活。心得三技术文档要沉淀成团队规范。批量查询的四种方式、踩过的坑、选型决策这些是最值得沉淀的内部知识。我每年会在团队做一次 Redis 高并发专项分享把压测数据、线上 case 复盘都放进去。技术影响力就是这么建立的。最后送大家一个我常用的批量查询自检清单每次上线前对照过一遍key 数量是否控制在合理范围mget/pipeline 单批不超过 500是否避免了 keys 命令和全量 O(N) 操作value 是否过大超过 10KB 的 key 单独治理集群环境是否考虑了 slot 分布是否用了 {} 哈希标签是否有熔断和降级策略Redis 挂了或超时时走本地缓存还是限制流量批量查询后是否做了结果映射是否处理了 nil/异常这六条过完基本能保证批量查询在真实高并发场景里稳定落地。如果你有更好的实践经验欢迎留言分享技术这东西一起讨论才能走得更远。
返回列表