
先别急着选型。上周一个做社交产品的朋友问我排行榜用 Redis 还是 MongoDB 我反问他你更怕排行榜数据丢失还是更怕它变慢 他愣了一下——这正是大多数人纠结时的真实状态只听过 Redis 快、MongoDB 灵活却说不清背后具体的取舍逻辑。我做了六年后端和数据库相关的开发Redis 和 MongoDB 都深度用过既踩过大 key 阻塞的坑也感受过文档模型带来的建模爽快感。标题里这俩经常被放在一起比但严格来说它俩根本不是同一类东西Redis 是内存数据结构服务器MongoDB 是磁盘文档数据库定位、数据模型、一致性模型、运维方式都天差地别。这篇文章我就从六个维度把它们拆开揉碎讲清楚最后给你一份可以直接照着做的选型清单。不管你是刚入行的后端开发还是正在做技术选型的架构师看完都能少走弯路。1. 先说结论这俩不是一类东西选型先看定位1.1 一句话分清 Redis 和 MongoDB 各自在干什么Redis 全称是 Remote Dictionary Server核心定位是内存里的数据结构工具库。它把数据存在内存里以键值对方式组织value 支持 String、Hash、List、Set、Zset有序集合、Stream、HyperLogLog 等丰富结构。单线程事件循环模型让它能把单命令延迟压到微秒级简单命令单机 QPS 轻松破十万。但代价也很明显内存是昂贵资源数据量受制于物理内存持久化只是附加能力。MongoDB 是文档型数据库数据以 BSON 格式存成一条条类似 JSON 的文档存放在磁盘上。它的卖点是灵活的动态 schema —— 同集合里不同文档可以有完全不同的字段结构嵌套文档和数组可以非常自然地表达复杂业务对象。查询语言和聚合管道功能很强多文档事务在 4.0 版本之后才真正成熟。延迟在毫秒级但它真正擅长的是海量数据的持久化存储、灵活查询和横向扩展。一句话版Redis 是内存工具箱MongoDB 是磁盘文档仓库。前者追求极致的速度和灵活的数据结构操作后者追求可靠的存储和灵活的数据建模。1.2 为什么总有人把这两个放在一起比因为在国内技术社区这俩经常出现在非关系型数据库这个分类里所以被默认成了可替代选项。加上两者都能存 JSON——Redis 的 Hash 可以塞字符串 JSONMongoDB 本身就是 JSON 文档——很多新人在心智上就把它们归成了一类。但选错是要付出实际代价的。我曾见过一个项目团队为了省事把大量业务数据直接写进 Redis存储满了之后用默认的 noeviction 策略结果写入直接报错面临扩容和迁移的双重压力。反过来也有团队用 MongoDB 扛热点计数为了实现排行榜的实时自增一条 update 走聚合管道延迟总是压不下去最后硬着头皮改回 Redis。说到底选型的第一步不是比功能多少而是先想清楚我的核心场景是状态还是资产热门排行榜是临时状态丢了可以重算订单、用户资料、交易记录是持久资产丢了就是事故。这个判断直接影响技术选型后面所有细节都是在为这个大方向服务。2. 数据模型与读写逻辑决定你写代码的姿势2.1 Redis 的数据结构原子操作即业务Redis 的数据结构是它的灵魂。用对了结构很多业务逻辑可以直接用命令完成不需要业务代码一层层叠加。我平时用得最多的几个Zset有序集合排行榜、延迟队列、优先级任务。每一条记录带 scoreZRANGE 直接取 TopNZINCRBY 一行命令完成加分并重新排序不需要任何业务逻辑。Hash对象属性存储。比如接一个用户会话直接把用户 ID 做 key字段存昵称、头像、tokenHGETALL 一次取完。INCR / DECR计数器零业务代码实现点赞数、访问数、库存原子扣减。SETNX 过期时间分布式锁的标准姿势。Stream5.0 之后引入的消息队列自带消费组、ACK 机制比列表模拟队列可靠得多。每个命令本身就是原子的这意味着你不需要考虑并发自增的竞态问题——Redis 单线程执行命令类似一个人排队盖章天然避免了多个线程同时改数据的冲突。这里有个关键提醒Redis 的结构选择直接决定线上性能和运维难度。你如果拿 String 存一个 JSON 大对象每次更新都要先读出来反序列化再写回去如果是 Hash 结构只需要 HGET 单个字段改单个值。结构用错了等于亲手制造慢查询。2.2 MongoDB 的文档模型业务对象即存储MongoDB 的建模思路和关系型数据库完全相反。关系型数据库要求先设计表、定义字段数据再往里填MongoDB 是直接把业务对象塞进文档一条文档就是一棵完整的数据树。比如一个电商订单你可以在 MongoDB 里这样存{ _id: ObjectId(...), orderNo: 20240518001, userId: u_1001, status: PAID, items: [ { skuId: s_01, name: 蓝牙耳机, price: 199, qty: 1 }, { skuId: s_02, name: 充电器, price: 89, qty: 2 } ], receiver: { name: 某开发者, phone: 138****1234, address: 某市某区 }, logistics: [ { time: 2024-05-18 12:00, desc: 已发货 } ], createdAt: new Date(2024-05-18T10:00:00Z) }订单的商品列表、收货人、物流轨迹全部嵌套在一个文档里。查询一个订单一次磁盘 IO 就能取出全部关联数据不需要像关系型数据库那样 JOIN 三张表。这种建模方式对查询模式固定、数据关系天然聚合的场景极其友好。动态 schema 是双刃剑。好处是业务字段变动时不需要执行 ALTER TABLE直接往新文档里加字段就行非常适合业务快速迭代的早期阶段。坏处是没人约束字段规范和命名时间久了同一个字段在不同文档里可能类型不一致、命名不一致查询和统计就会踩坑。2.3 一个实际建模对比案例电商系统的选择拿电商订单来对比更直观。Redis 存订单会非常别扭你得为每个订单拆出多个 key——order:1001:info 存基本信息order:1001:items 存商品列表order:1001:logistics 存物流轨迹。查一次订单要 3 次网络往返相当于订单被大卸八块扔进不同的抽屉用的时候再一个一个拼起来。加上 Redis 内存有限订单这种只增不减的数据长期占用内存代价极高。反过来用 MongoDB 存排行榜的实时分数也会很别扭查询 TopN 需要排序写入时需要原子自增虽然从 4.4 版本开始支持了大部分功能但底层还是文档级操作延迟和 Redis 的 ZINCRBY 差一两个数量级。穷举业务没有意义核心结论是选择数据模型等于选择你后面所有写代码的方式。你可以在 Redis 里模拟树结构你也可以在 MongoDB 里实现计数器但一切能用但不好用的实现最终都会在系统上线后被流量放大成性能问题。3. 性能、持久化与数据安全性能怪兽和安全网怎么选3.1 性能差异的本质内存与磁盘、单线程与多引擎Redis 为什么快全内存操作加上单线程事件循环。内存本身比磁盘快几个数量级单线程模型又省去了锁竞争和上下文切换的开销命令队列逐条执行QPS 自然高。MongoDB 走的是传统数据库路线WiredTiger 存储引擎负责管理内存缓存和磁盘文件读写请求先在内存缓存中处理最终异步落盘延迟量级在毫秒级。但快只是表象。Redis 的快是有边界的它单机的数据容量上限就是内存大小。你给 Redis 分配 8G那能存的数据就是 8G超出只能扩容或者写不进去。MongoDB 是磁盘数据库单机容量可以到 TB 级而且支持分片集群可以把数据打散到多台机器上水平扩展能力强很多。实操里还有一个容易被忽略的性能点Redis 是单线程一个慢命令会阻塞后续所有命令。比如 KEYS * 和 SMEMBERS 在大集合上的耗时会让线上出现明显的卡顿我见过有人因为生产上误执行了 KEYS * 导致整个服务两秒内没有任何响应。MongoDB 是并发执行查询的单个慢查询只会占自己的资源虽然也可能拖垮 CPU但起码不是全局阻塞。3.2 持久化机制深度拆解数据能丢还是不能丢Redis 的持久化有两条路RDB 快照和 AOF 日志。RDB定期把内存数据全量 dump 成二进制快照恢复快但快照之间有数据丢失窗口。AOF把每次写命令追加到日志文件恢复时重放命令丢数据窗口更小但文件体积大、恢复慢。混合持久化4.0 之后RDB 打底 AOF 记录增量兼顾恢复速度和数据安全。我见过很多人图省事把 Redis 持久化整个关掉——如果Redis你只做缓存确实可以丢了重建就是回头查一次数据库的事但如果拿它存计数器、token、会话这种不能重算的数据关闭持久化等于裸奔。建议至少开启 AOF并且把 appendfsync 设为 everysec这样最多丢一秒数据。MongoDB 默认就是持久化的。WiredTiger 引擎会把写入先记到 journal 日志里再定期做 checkpoint 把数据刷到磁盘。你写入后默认的 w:1 只表示主节点写成功了如果想要更高的安全性可以设置 writeConcern 为 majority表示复制集过半数节点确认写入才算成功。数据只要能真正落盘它就是资产而非状态。关键判断数据丢了能不能重算能重算的缓存、排行榜、验证码放 Redis不能重算的订单、余额、聊天记录、埋点数据放 MongoDB。这条规则能解决 80% 的选型问题。3.3 事务与一致性从命令原子到文档 ACIDRedis 的事务是 MULTI 和 EXEC 包裹的一组命令这些命令会被顺序执行中间不会被其他命令插队。但它的原子性非常有限如果中间某条命令语法错误之前的命令不会回滚后面的也可能继续执行。Redis 团队自己都说Redis transactions do not support rollback。它还提供 WATCH 命令做乐观锁可以手动判断数据是否被其他客户端修改过。MongoDB 在 4.0 版本引入了复制集内的多文档事务在 4.2 版本扩展到分片集群支持真正的 ACID 语义事务内多个文档的读写要么全部成功要么全部回滚。需要处理转账、库存扣减、订单状态流转这种多个文档必须保持一致的场景MongoDB 可以堂堂正正地接住。有个常见的错误认知把 Redis 的 MULTI 当成数据库事务来用。Redis 的多命令打包只能保证按顺序执行和不被插队它不保证执行过程中出错就回滚。拿它做业务一致性等于默认系统允许部分成功这种脏状态后果后面会很难收拾。4. 高可用、扩容与运维成本上线之后才见真章4.1 高可用架构方案哨兵与复制集Redis 的高可用方案是主从复制 哨兵。主节点负责读写从节点同步数据哨兵监控主节点状态当主节点挂掉时自动把一个从节点提升为新主节点。数据量超过单机内存时需要用 Redis Cluster 分片把 key 按哈希槽分散到多台节点上每个节点只负责一部分数据。集群模式下客户端要做重定向处理运维复杂度明显上升。MongoDB 的高可用靠复制集天然支持。一个复制集通常是一主两从主节点挂掉后从节点会自动选举产生新主节点客户端驱动的自动发现逻辑能无感知切换。数据量超出单机处理能力时可以继续往上叠加分片集群把不同范围的 shard key 分发到不同分片节点上。从架构复杂度来说MongoDB 的复制集更省心它是数据库自带的语义不用单独部署哨兵组件。Redis 的哨兵虽然也成熟但多了一套组件就多了一堆要维护的进程和可排查的问题。4.2 运维成本的真实账单Redis 最贵的是内存。生产环境 32G 内存的机器成本远高于 1TB 磁盘的机器同样的业务数据量存 Redis 的成本可能是 MongoDB 的十倍以上。内存一旦被占满还要面对淘汰策略的问题noeviction默认策略内存满了直接拒绝写入线上最容易踩的坑。allkeys-lru不管 key 是否设置了过期时间按 LRU 淘汰最久没用的。volatile-lru只淘汰设置了过期时间的 key没设过期时间的永生 key 不参与淘汰。另外还有大 key 问题。一个 value 几 MB 的 keyDEL 命令会阻塞 Redis 主线程几秒期间全站请求全部排队。必须用 UNLINK 命令异步删除或者用 SCAN 分批处理。MongoDB 的运维成本更多地体现在索引和查询优化上。磁盘便宜但你要是没建索引一条范围查询可以扫全表几十万文档CPU 和 IO 一下就上去了。索引本身也占内存和磁盘索引设计不合理写入变慢查询也变慢。还有慢查询日志system.profiler一定要定期看把常见慢查询捞出来针对性地补索引。4.3 组合架构缓存 持久化的常见姿势我实际项目里用得最多的是两者组合MongoDB 充当持久化底座Redis 当缓存层挡流量。典型链路是读请求先查 Redis命中直接返回未命中则查 MongoDB然后回填 Redis 并设置过期时间写请求先写 MongoDB成功后再异步更新 Redis。这个组合要处理好三个经典缓存问题缓存穿透查询一个根本不存在的 key每次都会打到 MongoDB。解决把空值也缓存起来或者用布隆过滤器挡一层。缓存击穿某个热点 key 恰好过期大量请求同时打到 MongoDB。解决互斥锁只让一个请求去重建缓存其他请求等待。缓存雪崩大量 key 同一时间过期压力瞬间打到 MongoDB。解决过期时间加一个随机偏移量让过期时间分散开。这套组合扛住了我做过的最大的几个流量峰值。Redis 负责挡和快MongoDB 负责存和全各司其职才是最优解。5. 选型决策框架一个可以照着做的评判清单5.1 六个问题帮你想清楚直接上核心方法论。面对一个业务场景按顺序问自己这六个问题答案自然就浮出来了。数据必须持久化吗丢了能重算吗——不能重算的直接选 MongoDB绝不用 Redis 碰。核心访问模式是读多写多还是写多读少——Redis 适合读写都极其频繁的热点操作MongoDB 适合大量写入、中低频查询的场景。数据模型固定还是变化频繁——变化频繁的文档型业务选 MongoDB固定结构的计数器、排行榜、状态机可以和 Redis 无缝配合。需要复杂查询、聚合计算、跨文档关联吗——需要的话MongoDB 的聚合管道和索引体系远强于 Redis 的手动 foreach。单机数据量预估多大——超过内存量级且不想为内存付账的选 MongoDB数据量小但要求极致速度的选 Redis。团队熟悉哪个、运维能力如何——Redis 的运维偏简单但内存规划要时刻关注MongoDB 的运维重点是索引、分片和备份恢复门槛稍高。5.2 不同业务场景的推荐组合场景推荐方案理由排行榜、计数器、点赞数RedisZset 和 INCR 天然支持微秒级延迟分布式锁、限流RedisSETNX Lua 脚本原子性无敌登录会话、TokenRedis天然的键值过期机制无需额外清理用户资料、内容管理MongoDB文档嵌套模型贴合复杂资料结构动态 schema 方便字段扩展电商订单、交易流水MongoDB Redis 缓存MongoDB 保证持久化Redis 缓存热订单读取日志、埋点、IoT 数据MongoDB写吞吐大、无固定结构、可以水平分片热数据 全量数据Redis MongoDB 组合Redis 做热数据加速MongoDB 保留全量数据5.3 一个真实案例的选型过程去年我给一个内部管理系统做技术方案场景是用户上传感应器数据需要实时展示最近一小时曲线同时要能按天、按周做历史趋势统计。数据量一天大概几百万条总量很快到亿级。一开始有人提直接用 Redis 存所有数据理由很简单快。我算了一下亿级数据全塞进内存至少要 80G 内存成本直接劝退而且数据的价值是历史可追溯,这属于典型的持久资产。最后方案定为原始传感器数据进 MongoDB按天建集合、按小时建索引聚合管道做趋势统计同时用 Redis 存最近一小时未聚合的原始数据用于实时曲线展示。一小时之前的数据自动过期Redis 内存占用恒定可控。这个案例的核心就是先判断资产 vs 状态。数据如果只在一小时内有效Redis 完全可以兜住如果要追溯三个月前的某一条记录Redis 根本不够格。想明白这个整个技术方案就顺了。6. 踩坑实录与常见问题速查6.1 Redis 侧的高频坑第一个坑是大 key。比如一个 Hash 存了上百万字段或者一个 Zset 加了百万分数这类大 key 在删除、迁移、序列化时都会卡住单线程。建议在写入时做拆分字段超过 5000 个或 value 超过 1MB 就该报警了。删除一律用 UNLINK 而不是 DEL。第二个坑是内存淘汰策略配错。默认 noeviction 在内存满时会直接拒绝写请求对线上是灾难。如果你是纯缓存场景直接配 allkeys-lru如果有部分 key 必须长期存活配 volatile-lru 并给临时 key 都设置过期时间。第三个坑是持久化配置不当。appendfsync 设为 always 会让每条命令都刷盘性能骤降设为 no 则最多可能丢 30 秒数据。生产建议是 everysec。RDB 和 AOF 同时开启时重启恢复以 AOF 为准如果 AOF 文件损坏要用 redis-check-aof --fix 修复。6.2 MongoDB 侧的高频坑第一个坑是没建索引。入门时我经常发现一条查询要扫几百万文档慢查询日志里全是黄色告警。排查方法很简单对查询条件做 explain(executionStats)看 totalDocsExamined 和 nReturned 的差距差距越大越说明索引缺失。第二个坑是嵌套文档无限膨胀。文档嵌套层级过深或者数组字段无限追加会让文档体积膨胀到几 MB。查询时要取出并反序列化整个文档即使只需要其中一个字段性能也会劣化。设计时就要控制嵌套层数数组字段设置上限。第三个坑是分片键没选好。MongoDB 分片集群按 shard key 分布数据如果选了一个基数很低的字段比如性别只有男女数据只会落在几个分片上完全丧失水平扩展的意义。shard key 要选基数高、分布均匀、查询频繁的字段。第四个坑是 writeConcern 设置太低。开发环境无所谓但生产如果设置了 w:0主节点写入失败也不会报错你会以为数据写进去了实际上可能已经丢了。建议生产至少 w:1核心数据用 majority。6.3 问题速查表现象可能原因排查命令 / 思路解决方案Redis 写入报 OOM command not allowed内存满了且是 noevictionINFO memory 查看 used_memory调整 maxmemory-policy或扩容/清洗数据Redis 偶发几秒阻塞大 key 删除或 KEYS 命令SLOWLOG GET 查看慢命令改用 UNLINK、SCAN拆分大 keyMongoDB 查询巨慢缺索引explain(executionStats)补充对应查询条件的复合索引MongoDB 某分片数据特别多片键基数低或分布不均sh.status() 查看分片分布重新选片键或做范围分片调整切换主从后丢数据复制延迟或 writeConcern 过低rs.status() 查看复制状态设置 writeConcern: majority6.4 我的几条独家习惯第一条所有 Redis key 命名必须有前缀和过期时间设计。比如 user:info:1001 的 key必须在写入时带好 TTL宁可多花一次重建成本也不要让内存里堆满僵尸数据。第二条MongoDB 建完索引后一定要定期看索引使用情况。MongoDB 提供 $indexStats 聚合能看出哪些索引被真正使用、哪些是摆设。没用的索引不仅占内存还会拖慢写入。第三条两个数据库的监控都要做。Redis 重点看命中率、内存、慢日志MongoDB 重点看 opcounters、连接数、慢查询、复制延迟。有人问为什么不用统一监控平台我一般回答先把数据捞出来看明白再花钱上平台。最后再分享一个小技巧做选型对比时别光看技术博客里的天花乱坠。拿你自己真实的业务数据分别往 Redis 和 MongoDB 里写十万条和百万条压测工具实测一下读写延迟和吞吐再算一下成本。我在实际项目里就是这样验证的——别人的 benchmark 只能给你一个量级参考你真实的业务模型和数据量下测出来的数字才是选型的最终依据。