被问得最多的技术选型问题,大概就是“HBase、MongoDB、Cassandra 到底选哪个”。这三个名字经常被一起放进候选清单,但真把它们当成同一类东西来比,方向一开始就错了。我这些年做过日志采集、用户画像、订单中心、IoT 数据平台,三个库都深度用过,也都踩过不少坑。这篇文章不打算照着官方文档复述概念,而是把我在实际项目里的体感差异、原理层面的关键分叉点,以及最后沉淀下来的选型判断方法一次讲清楚,给正在纠结的人一份能落地的参考。
1. 为什么这三个库总被放在一起比,又为什么它们根本不是一回事
1.1 三个“非关系型”,三套完全不同的出身
先说出身。HBase 的灵感来自 Google 的 Bigtable 论文,定位是在廉价服务器上跑超大规模结构化数据,它从骨子里就是为海量写入和按 RowKey 的顺序扫描设计的。Cassandra 的基因来自两份论文的混合——Bigtable 的数据模型加 Dynamo 的分布式架构,目标更极端:没有单点、全部节点对等、写入能力随节点数线性增长。MongoDB 则走了完全不同的路线,它最初解决的是“业务模型变得太快,关系表结构跟不上”的问题,核心资产是灵活的文档模型和接近关系库的查询语言。
这三者的共同点只是都被归进 NoSQL,都宣称高可用、高扩展、跑在分布式集群上。但“非关系”这个前缀掩盖了一个关键事实:HBase 和 Cassandra 是宽列存储,数据按主键排序分布;MongoDB 是文档存储,数据以 BSON 文档为单位,天然支持嵌套和数组。查询范式不同、一致性模型不同、运维复杂度不同,选型时需要回答的问题自然也完全不同。
1.2 我早期踩过的认知误区
我最早犯过的错,是以为 HBase 和 Cassandra 差不多,都是“列族数据库”,随便换着用。实际上一旦落到查询方式,两者天差地别。HBase 靠的是 RowKey 的范围扫描,查询能力按“行”展开;Cassandra 靠的是分区键定位,查询能力按“分区”锁死。把 Cassandra 的表结构当成 HBase 的表结构去设计,或者反过来,都会在数据量上来之后发现模型改不动。
另一个常见误区是觉得 MongoDB 什么都能装。文档模型确实灵活,但灵活是有代价的:跨文档事务、超高并发下单文档争抢、以及海量数据下的索引膨胀,各有各的天花板。我见过不止一个团队把日志直接塞进 MongoDB,结果体量上来之后写入抖动、索引跟不上,最后又折腾着迁走。选库之前先想清楚“我的数据长什么样、我要按什么路径读它”,比先看哪个库热门重要得多。
2. 数据模型差异:先回答“你的数据长什么样”
2.1 HBase:一张稀疏到极致的无限大表
HBase 的概念模型就是一张大表:每一行由 RowKey 唯一标识,行与行之间按 RowKey 字典序排列;列分成若干列族,每个列族下可以挂任意多的列标识(qualifier)。最反直觉的地方是稀疏性——某一行只在某一列有值,其他列可以完全空着,空着的部分不占任何存储。每个单元格还自带时间戳,可以保留多个历史版本,读取时可以指定要哪个版本。
物理存储上,数据最终落在 HDFS,写入路径是 WAL(预写日志)、MemStore(内存缓存)、HFile(磁盘文件)这条链路。理解这条链路,才能理解为什么 HBase 适合宽表和时序数据。比如设备上报数据,把 RowKey 设计成“设备 ID + 时间戳”,同一个设备的连续上报就会落在相邻区域,范围扫描非常高效。反过来,如果你需要按任意字段做随机查询,比如“查出所有名称包含某关键字的产品”,HBase 原生做不到,得自己建二级索引表来维护。这是一开始就要认清楚的限制。
# HBase Shell 中最常用的建表、写入、范围扫描 create 'device_metric', 'cf' put 'device_metric', 'dev001_20250101_0001', 'cf:temp', '23.5' get 'device_metric', 'dev001_20250101_0001' scan 'device_metric', {STARTROW => 'dev001_20250101', STOPROW => 'dev001_20250102'}2.2 MongoDB:装着任意结构的文档口袋
MongoDB 的基本单位是文档,文档是一棵 BSON 树,可以嵌套、可以放数组、每个文档可以拥有不同的字段。集合只是文档的容器,本身不约束结构。这种感觉就像整理文件时用口袋而不是表格——每份文件可以有自己的页数、章节和附件,不用先设计好统一的模板。
文档模型在业务快速迭代期优势非常明显。产品后台的用户资料、商品目录、配置信息这类数据,字段经常变、天然有嵌套,用 MongoDB 开发速度能快很多。索引方面,MongoDB 支持单字段索引、复合索引、数组索引、文本索引、地理索引,还有 TTL 索引可以按时间自动清理过期数据。查询语言和聚合管道也是三者里最接近大家关系库直觉的,后面第 4 节我会展开讲。
2.3 Cassandra:把分区键焊死的前缀树
Cassandra 的表结构看起来很像关系表:建表时指定主键,主键拆成分区键(partition key)和聚类列(clustering key)。分区键决定这一行落在哪个节点,聚类列决定分区内部的数据排列顺序。CQL 的语法有 SQL 的影子,但执行规则极其苛刻:查询必须带上分区键,不带就只能加ALLOW FILTERING——那基本等于全表扫描,性能上是大坑。
这个模型想表达的意思是:你要提前想清楚“最常用的访问路径是什么”,然后把这条路用主键结构固化下来。比如订单表设计成以user_id为分区键、order_time为聚类列,那“查某个用户最近三个月的订单”就是一次单分区内扫描,性能极好。但如果你突发奇想“按订单金额排序查全站订单”,这种查询在 Cassandra 里没有设计空间,只能放弃。建模的约束力,三个库里它最强。
2.4 一个订单场景的三种建模对比
我们用同一笔订单来看三种思路的差异。假设订单包含用户、下单时间、商品明细、金额、状态,通常还需要按用户查订单列表。
| 对比维度 | HBase | MongoDB | Cassandra |
|---|---|---|---|
| 建模思路 | RowKey 设计为 用户ID+时间,订单属性平铺成多列 | 订单作为一个文档,商品明细内嵌成数组 | 分区键=user_id,聚类列=order_time |
| 查某用户订单 | 按 RowKey 前缀做 range scan | find({userId})配合索引 | 主键精确查询,单分区内排序返回 |
| 随意组合筛选 | 不支持,需自己建索引表 | 支持,但要控制查询模式和索引数量 | 基本不支持,主键结构决定一切 |
| 字段频繁变更 | 列族内可动态加列 | 文档随意加字段 | 只能加非主键列,且建议一次性想清 |
提示:宽列模型优先的是“海量写入 + 固定路径查询”,文档模型优先的是“灵活结构 + 演进中的查询”。别指望一个库把两件事同时做到极致,建模阶段就要做出取舍。
3. 架构与一致性:CAP 三角形里的三个坐标
3.1 HBase:单 Master 加强一致的确定性
HBase 的架构是:HMaster 负责元数据管理和 Region 分配,RegionServer 负责实际的数据读写。某一行数据在任一时刻只会由一个 Region 提供服务,所以读写天然强一致——写成功就意味着数据已经写入 WAL 并被 MemStore 接收,读不会读到某个旧版本。这是 HBase 让我最放心的地方。
但这个强一致是有代价的。写入链路要经过 HDFS 的副本确认,延迟比纯内存复制要高;Region 分裂、HMaster 主备切换期间,对应的 Region 可能短暂不可服务。所以 HBase 的定位非常清晰:牺牲一部分可用性和灵活性,换取确定性的强一致,以及超大表上的顺序扫描能力。如果你需要的是“数据错一点都不行”的场景,比如账户流水、订单对账,这一票很重要。
3.2 MongoDB:副本集选举加分片集群
MongoDB 的高可用基础是副本集:一个主节点加多个从节点,写入全部走主节点,默认读也走主节点,从主节点读时是强一致的。主节点故障后,剩余节点会发起选举,在几秒内选出新主,业务侧通常会感知到一次短暂的连接抖动。从节点主要承担容灾和读扩展,但默认读从节点可能读到滞后数据,需要在驱动里配置 read preference,这是个容易忽略的细节。
更大的规模靠分片集群:mongos 路由层、config server 存元数据、多个分片(每个分片本身是一个副本集)存数据。分片键的选择是一扇单向门——分得均匀、又能覆盖主要查询模式的分片键,上线后一劳永逸;选错了,chunk 会集中在少数节点上,balancer 天天忙着搬数据,还可能出现超过了 chunk 上限的 jumbo chunk,搬迁起来非常痛苦。集群的日常调优工作,很大一部分是在跟 chunk 分布打交道。
3.3 Cassandra:去中心化与“一致性任你调”
Cassandra 没有主节点。所有节点地位对等,数据按分区键哈希映射到 token 环上,每个分区默认存多份副本,节点之间用 gossip 协议互相交换状态。写入时如果某个副本节点暂时不可用,系统会先用 hinted handoff 把数据记在当前节点,等对方恢复后再补发;读如果发现多个副本数据不一致,后台的 read repair 会以时间戳最新的数据为准修复。这套机制让 Cassandra 在节点故障时表现非常平稳。
最特别的是它的可调一致性:一次写入或读取可以指定 ONE、QUORUM、ALL 等不同级别。默认配置下,它更偏 AP——网络分区时照常提供读写,但可能读到旧数据;如果你把读写都提高到 QUORUM,则能获得近似强一致,但可用性和延迟都会打折。这个灵活性是把双刃剑,用好了能按业务场景分别调节,用不好就会出现“测试环境没问题、线上数据对不上”的诡异问题。
3.4 一次分区故障里的三种实际表现
我记得某次机房网络抖动,三个库在同一套业务环境里的表现完全不同。HBase 那一侧,部分 Region 短暂不可服务,但一旦恢复,读到的数据一定是完整的。MongoDB 副本集发生了主节点切换,应用侧感知到几秒连接中断,切换完成后数据保持一致。Cassandra 集群则全程可写可读,但 QUORUM 级别下出现了一小批写超时,降到 ONE 级别后部分请求读到了旧副本的值。
这个对比不是为了分高下,而是想说明:一致性、可用性、延迟不可能同时完美,先选好你的立场,再选库。
| 场景 | HBase | MongoDB | Cassandra |
|---|---|---|---|
| 默认一致性立场 | CP,强一致优先 | 主读强一致,从读可滞后 | AP,可调 |
| 节点故障时 | Region 重新分配,短暂不可用 | 触发选举,秒级切换 | 副本继续服务,基本无感 |
| 网络分区时 | 一致性优先,可用性受损 | 主节点可能降级为只读 | 默认继续读写,可能读旧值 |
| 写入确认成本 | 需 HDFS 多副本确认 | 副本集多数确认 | 按一致性级别自定义 |
4. 查询能力与写入性能:没有十项全能
4.1 HBase:RowKey 就是一切
HBase 原生查询只有两种:get 精确读一行,scan 按 RowKey 范围扫描。过滤器虽然能剪枝,但本质还是在范围扫描基础上的过滤,复杂条件查询性能很难看。所以 HBase 项目里最重要的设计工作永远是 RowKey:前缀要覆盖主要查询模式,后缀要分散写入热点。比如纯粹用时间戳做前缀,同一时刻的写入会全部打到同一个 Region,直接形成热点,这个坑我见过太多次。
作为补偿,HBase 和整个 Hadoop 生态配合得非常顺。数据进了 HDFS,Spark、MapReduce、Hive 都可以直接在上面跑离线分析,这是很多团队选它做“实时写入 + 离线分析”底座的核心原因,我们当时就是看中这一条才没换。
4.2 MongoDB:最像关系库的查询体验
MongoDB 的 find 支持各种条件、排序、投影、正则、地理位置,聚合管道更是能把$match、$group、$sort、$unwind、$lookup组合成相当复杂的报表逻辑。在查询能力上,它是三个库里最接近大家“关系库直觉”的,学习曲线最平缓。
// 一个简单的订单写入与聚合查询 db.orders.insertOne({ userId: "u_10001", total: 299, items: [{ sku: "A01", price: 199 }, { sku: "B02", price: 100 }], createdAt: new Date() }) db.orders.createIndex({ userId: 1, createdAt: -1 }) db.orders.aggregate([ { $match: { userId: "u_10001" } }, { $unwind: "$items" }, { $group: { _id: "$items.sku", total: { $sum: "$items.price" } } } ])但注意,索引设计永远是 MongoDB 性能的关键。文档结构灵活,不等于可以不给查询建索引。很多线上事故都是“跑得好好的,某个字段第一次全表扫描”。另外,WiredTiger 存储引擎默认会用掉相当一部分机器内存做缓存,工作集如果远大于内存,性能曲线会掉得很难看,容量规划时要提前算。
4.3 Cassandra:CQL 的“SQL 感”是一种错觉
CQL 里有 WHERE、ORDER BY、GROUP BY,会给人一种“这跟 MySQL 差不多”的错觉,实际上约束非常严格:ORDER BY 只能按聚类列排,GROUP BY 只能按分组键聚合,WHERE 里非主键列基本不允许。二级索引和物化视图虽然能用,但在高基数、高并发场景下性能不稳定,官方自己都不建议高强度使用。
-- CQL 建表:主键直接定义查询路径 CREATE TABLE orders_by_user ( user_id text, order_time timestamp, order_id text, amount decimal, status text, PRIMARY KEY (user_id, order_time) ); -- 允许的查询:带分区键 SELECT * FROM orders_by_user WHERE user_id = 'u_10001' AND order_time >= '2025-01-01';Cassandra 真正的强项是写入。它天生就是 appender,写操作全部顺序写 commit log 和 memtable,不需要读改写,没有行锁,因此在多节点下写入吞吐可以随节点数线性扩展。我们当时把用户行为事件流从自研的队列转到 Cassandra,写入压力一下降了一个量级。但要记住:写入便宜,查询受限,每条查询都要走在主键预设的轨道上。
4.4 一次写入压力测试的体感差异
某 IoT 模拟项目要求每天入库数亿条遥测数据,单节点写入带宽接近每秒两万条。HBase 走的是批量导入路径,利用 BulkLoad 方式直接生成 HFile 再加载,吞吐没问题,但需要额外的数据预处理流程。Cassandra 几乎是最省事的,整个集群每个节点平均分摊写入,压测曲线非常平滑。MongoDB 在较小体量下写入很快,但数据量上去、索引变大之后,写放大和内存压力会更早显现。这个对比给我的感受是:如果你明确知道自己的核心负载是“高吞吐写入 + 按固定键查询”,宽列系两个库天然占优;如果你的业务还在快速变化,经常要写新的查询逻辑,MongoDB 的开发效率会拉回分数。
5. 运维与扩容:上线之后的真实体感
5.1 HBase:三件套的联动复杂度
HBase 生产环境基本等于三套系统叠着跑:HDFS 负责存储,ZooKeeper 负责协调,HBase 自己负责表服务。升级要联动,排错要跨层,任何一个组件出问题,体感都像整条链路的锅。日常运维里,Region 数量、大 Region、compaction 风暴是三大心头患。Region 数太多,RegionServer 内存和 RPC 压力上升;compaction 如果跟不上写入速度,HFile 数量会越积越多,读放大严重。
这里有个实操经验:监控别只盯着机器负载,要多看 Region 级指标,比如单 Region 的请求延迟、store 文件数、compaction 队列长度。我们曾遇到一次“看起来 CPU 不高、但读延迟飙到秒级”的诡异问题,最后定位是某几个热点 Region 的 store 文件过多,触发了一整轮大 compaction,整台机器性能被拖垮。
5.2 MongoDB:分片键是一扇单向门
MongoDB 副本集的运维其实很省心,换个主、加个从、备份恢复都有一套成熟工具。真正的复杂度在分片集群。mongos 路由要管理,chunk 要均衡,最要命的是分片键选错之后几乎没法低成本修改。比如选了一个基数很低的字段做分片键,数据会集中在少数 chunk 上,balancer 搬来搬去都治标不治本;选了一个完全跟查询模式无关的字段,每次查询又得广播到所有分片。
监控上我建议重点看 oplog 时间窗。oplog 是副本同步的基石,如果它覆盖的时间太短,从节点落后一多就会跟不上,只能重建从节点。我们踩过的一次大坑就是从节点被延迟重建后没注意 oplog 已经溢出,结果全量重搭花了整整两天。
5.3 Cassandra:加节点容易,清墓碑难
Cassandra 扩容是我用过的分布式存储里最省心的。无主节点架构下,加一个新节点,配好 seed 和 gossip,它会自动学习 token 分布并开始搬数据;配合 vnode,节点间的数据迁移粒度更细,均衡性也更好。跟 HBase 动辄要调整 Region 分配相比,Cassandra 的横向扩容几乎是无感的。
但运维的难点在另一边:墓碑(tombstone)和压缩策略。删除操作在 Cassandra 里只是一个写标记,不会立刻清掉底层数据,要等 gc_grace_seconds(默认 10 天)之后才会被真正清理。如果业务有高频删除或反复更新,墓碑会大量堆积,读路径要跳过这些标记,延迟慢慢变差。我建议从第一天就给表指定合适的压缩策略,时序数据用 TimeWindowCompactionStrategy,通用数据用 LeveledCompactionStrategy,不要留着默认配置裸奔。
5.4 三个库共通的几条运维红线
不管最后选哪个,有几条经验是通用的。第一,备份和恢复演练一定要做,而且要在模拟环境里真实跑一遍恢复流程,别等故障来了现学。第二,把监控图表的延迟、错误率和饱和状态分开看,很多所谓“慢查询”其实是 CPU 或 IO 饱和导致的等待。第三,任何数据库升级都要先在低峰时段小范围试点,我见过太多“升级前一切正常,升级后配置不兼容”的例子。第四,容量规划不要按平均值做,按峰值和未来的增长斜率做,否则上线半年就会陷入频繁扩容。
6. 选型决策框架:我在实践里的最终判断方法
6.1 三种典型业务画像
这些年下来,我把适合这三个库的典型画像总结成三类。
画像 A:海量时序与日志类。特点是写入量大、几乎不更新、按时间或设备维度查询、需要长期保存。如果数据以“写为主、读为辅”,且已经或计划跟 Hadoop 生态深度绑定,HBase 很合适,比如订单流水、工单轨迹、历史归档;如果更在意多活、跨机房写入、要求写入吞吐线性扩展,Cassandra 更合适,比如用户行为流、消息推送记录。
画像 B:灵活业务模型、快速迭代。特点是数据结构经常变、嵌套关系多、查询条件不确定、需要快速上线。这类是 MongoDB 的主场,典型如商品目录、内容系统、用户资料扩展、A/B 实验配置。团队如果以应用开发为主,没有专职 DBA,MongoDB 的上手成本也是三者中最低的。
画像 C:超宽表、稀疏数据、固定键路径。特点是单行字段多且稀疏、查询永远走某个前缀或主键。HBase 的稀疏存储优势极大,Cassandra 也可以,但后者更强调“每个查询必须走主键轨道”。如果还有强一致和范围扫描需求,HBase 的优势更明显。
6.2 一张可以直接照做的决策清单
我给自己留了一套固定问题,每次评估新的存储需求都先过一遍,比讨论数据库本身快得多。
- 数据更新的频率和方式是什么?如果大量是“插入和末尾追加”,宽列系更顺手;如果大量是“字段级修改”,文档模型更自然。
- 查询模式在未来一年内能否基本确定?能确定,Cassandra 的表现会惊人;不能确定,MongoDB 的灵活性是保险。
- 是否需要范围扫描和顺序读?需要且数据量大,HBase 的优势无法替代。
- 是否强依赖 SQL 或二级索引?无论哪个库原生能力都比关系库弱,需要快速试错期可以选 MongoDB。
- 团队能养得起哪套运维体系?没有专职基础设施团队时,优先 MongoDB 副本集,而不是一上来就上三套件或分片集群。
- 是否有跨机房、多活、异地写入诉求?Cassandra 在这条路上竞争优势最明显。
注意:选型评估请用真实数据规模、真实查询分布、甚至真实压测来验证,而不是靠“社区火不火、面试总提到”来定方向。社区热度跟业务匹配度没有直接关系。
6.3 别排斥多库共存
很多团队把选型理解为“一个系统只能用一个数据库”,这是没必要的自我设限。我更倾向于按数据域拆:订单和账户这类强一致数据继续留在关系库里,商品和用户画像这类灵活结构放 MongoDB,行为日志和遥测数据进 Cassandra,需要做离线分析的存量走 HBase 加 Hadoop 管道。多库并存的维护成本确实更高,但每个数据域都用最合适的存储,长期来看反而少很多折腾。
多库迁移时记住两条经验。一是尽量采用双写加校验的灰度方案,先双写一段时间,夜间跑对账任务,确认数据一致后再切换读流量,不要断崖式迁移。二是主键设计要预留业务主键,不要直接用数据库自增或内部生成字段做关联,否则将来跨库关联会非常痛苦。
我在实际使用里的体感是:HBase 适合“我要海量数据,还要按一种方式快速访问它”,Cassandra 适合“我要极致写入吞吐,能接受把查询路径提前焊死”,MongoDB 适合“我的业务还在快速变化,需要随时调整数据结构和查询方式”。这三个库没有谁全面碾压谁,只有谁更贴合你当下的业务特征。如果只让我留一条经验,那就是:先把“查询路径”这个问题想清楚,再打开任何一份数据库安装文档。路径定错了,后面所有优化都是往错误的方向上加速。