做分布式数据选型最头疼的就是在没有真实数据的情况下,凭官网文档和博客背书拍脑袋定方案。两年前我们团队在重构某个跨地域设备接入平台时,就卡在了HBase和Cassandra之间,网上对比文章不少,但多数要么只讲原理,要么测试场景离实际业务太远,参考价值有限。最后我干脆拉了三台高配机器,用YCSB做了一轮相对完整的读写性能测评,把选型从“据说”变成了“实测”。这篇文章就是把当时的环境准备、测试设计、数据结果和踩坑过程完整复盘一下,适合正在做HBase或Cassandra选型、想了解两者在真实负载下表现差异的工程师参考。
这次测评并不是为了分出绝对输赢,而是想搞清楚两个问题:第一,在同样的硬件和并发条件下,两者的吞吐量和延迟差距到底有多大;第二,这些差距背后的机制是什么,能不能用架构层面的逻辑解释得通。只有把这两点弄明白,后续做容量规划和技术决策才立得住。
1. 为什么要做这次测评:一次真实选型引发的追问
1.1 项目背景和业务负载画像
先说清楚触发这次测评的业务场景。我要处理的是一套设备数据汇聚平台,设备网关每秒钟会产生大量时序状态数据,单条记录很小,大概几百字节,但总量涨得很快,一天新增几亿行是常态。查询侧的需求分两类:一类是设备ID加时间范围的扫描,比如看某个设备最近一小时的上报曲线;另一类是单条记录精确查询,比如根据消息ID查某次上报的原始内容。
这两类查询对数据库的要求不太一样,前者需要高效的范围扫描,后者需要稳定的随机点查。传统关系型数据库在海量数据面前先被排除了,Kudu和ClickHouse也简单评估过,但最终进入决赛圈的只剩HBase和Cassandra。原因很直接:两者都基于LSM树,写入性能预期不错;都是分布式NoSQL,横向扩展有成熟方案;社区活跃度够高,出问题能找到人问。
但到了真正选型的时候,团队内部出现了分歧。支持HBase的一方强调它和HDFS绑定,强一致性好,而且行键有序排列,扫描性能公认强。支持Cassandra的一方指出它无主架构部署简单,读写可调一致性级别,多数据中心支持天然友好。双方都能拿出各自领域里的成功案例,却没人能回答一个最关键的问题:在我们这种读多写少、点查和扫描并存的负载下,两者到底谁能扛住,谁会先撑不住。
1.2 为什么不能直接信官方基准测试
官网放出的基准测试一般来说有两个参考性问题。一是测试环境太理想,用几十台机器堆出来的数据,对小规模集群没有指示意义;二是测试模型和真实业务脱节,很多时候只测了连续写入或简单读取,没有混合负载和GC扰动。
更麻烦的是,HBase和Cassandra的资源模型差异太大。HBase强依赖HDFS,一个读写请求链路里要经过RegionServer、HDFS DataNode多个环节,而Cassandra数据直接落在本地文件系统,每个节点都是完整的存储和计算单元。这两种架构特点会直接体现在延迟和吞吐上,但不是简单谁好谁坏,得看具体负载。
所以我倾向用自己业务相近的数据模型,在可控的三节点集群上跑一轮YCSB测试,哪怕样本量不算大,至少结论是从我们自己的环境里长出来的,比拍脑袋可靠。
如果项目已经有相对明确的读写比例和单行大小,建议在测试前把它们固化成模板,后续所有对比都用同一份模板,避免因为数据模型漂移导致结果不可比。
1.3 测评目标和评估维度的确定
测评目标定为以下三个方向:
- 确定不同并发下读写吞吐量的变化曲线,找到两者的吞吐拐点。
- 对比平均延迟和P99延迟,特别是线程数升高以后的延迟恶化程度。
- 通过监控观察两类数据库在相同负载下对CPU、内存和磁盘IO的消耗差异。
三个方向分别对应容量规划、SLA评估和运维成本估算。没有只盯吞吐这一个数字,是因为很多NoSQL项目死在高延迟尖刺上,吞吐再高也扛不住线上告警。
2. 测评环境准备与测试设计:尽量做到公平
2.1 集群配置与部署细节
为减小网络、磁盘和CPU差异带来的干扰,两台数据库共用同一批物理规格的服务器,并且都部署在同一个机架内。
配置为:
- 3台服务器,每台32核CPU,128GB内存,2块NVMe SSD做数据盘。
- 操作系统是CentOS 7.9,内核参数做过基础优化,关闭透明大页,磁盘调度器设置为none。
- 网络是万兆内网,客户端和服务端之间单独使用一个VLAN,避免和其他业务流量相互干扰。
HBase选用2.4社区版,部署方式是标准HDFS三节点加ZooKeeper三节点,RegionServer和DataNode同机部署。Cassandra选用4.0版本,同样是三节点集群,分区副本数设置为3。存储副本数都保持默认,没有做任何降级。
这里的核心原则是让两边都用自己最常见的生产部署方式,而不是强行改成对方的样子。HBase本身就要用HDFS,没必要为了对比而把副本降到2;Cassandra天然就是副本数等于节点数的架构,也不能强行改。公平是指硬件和负载公平,不是指架构对齐。
2.2 表结构设计和数据模型准备
数据模型方面做了统一抽象。每条记录包含以下字段:
- row_id:全局唯一业务ID,同时也是主键。
- device_id:设备编号。
- ts:事件产生时间戳。
- payload:原始上报内容,按文本存储。
在HBase中,表的一行就是一条记录,row key直接用拼接后的业务ID,另外设计了一个以device_id和ts组合作为前缀的索引表,用于模拟范围扫描场景。在Cassandra中,主键设计为分区键device_id加聚类键ts,row_id作为普通列存储。
这里要特别说明:Cassandra对主键的约束比HBase严格得多,不能像HBase那样随意设计多张索引表来满足不同查询模式,所以Cassandra只能通过主键设计同时满足点查和扫描需求。这也是两者一个很重要的差异点,后面在结果分析中还会提到。
数据总量定为2000万条记录,单条平均500字节左右,总数据量大约10GB。对三节点集群来说,这个数据量不算大,但已经足够让MemStore/SSTable进入稳态,也能把compaction的影响真实暴露出来。
2.3 基准测试工具与负载类型定义
压测工具用YCSB 0.17.0,原因是它提供了统一的读写工作负载定义,而且HBase和Cassandra都有官方适配器,至少保证了客户端侧的调用方式一致。
测试分为三个阶段:
- 加载阶段:并发64线程向两个库写入2000万条数据,观察纯写入吞吐和加载耗时。
- 点查阶段:以95%读、5%写的比例压测随机点查,线程数从16逐步增加到128。
- 混合阶段:以50%读、50%写的比例模拟业务真实读写,线程数同样从16递增到128。
每个负载跑10分钟,前3分钟作为预热期,后7分钟统计数据。每轮测试之间间隔5分钟,等待compaction和GC尽量平静下来再跑下一轮。
有一点很容易被忽略:YCSB的默认读操作是随机读,但业务里还有不少范围扫描动作,所以测试脚本里额外通过自定义workload加入了5%的扫描操作,让结果更贴近真实场景。
3. 性能数据结果与初步解读:实测差距在哪里
3.1 纯写入负载表现
加载2000万条数据的过程中,Cassandra整体耗时比HBase少大约20%到25%。HBase的写入吞吐大概稳定在每秒12万条左右,Cassandra稳定在每秒15万到16万条之间。
写入延迟的差异也很明显。在64线程下,HBase写入平均延迟约4.2毫秒,P99约15毫秒;Cassandra平均延迟约3.2毫秒,P99约11毫秒。线程数增加到128后,HBase的P99跳到30毫秒以上,Cassandra仍然控制在25毫秒以内。
这背后的原因其实不难理解。HBase的每次写入需要先写WAL,再由RegionServer把数据分发到HDFS,链路更长,网络交互次数更多。Cassandra的写入先落本地commit log,再进Memtable,只要本地磁盘能扛住就不会产生跨节点写放大。
但这里不能直接下结论说Cassandra写入全面优于HBase。三节点的Cassandra集群每个节点都是对等的,压力分散自然更均衡;而HBase的写入热点由Region分布决定,如果region预分区做得不好,很容易出现单个RegionServer成为瓶颈的情况。测试前我特意做了预分区,真正常态业务如果没做预分区,HBase的写入表现会比这个结果差不少。
3.2 点查为主时的读取表现
读取场景的差异比写入更有意思。在95%读、5%写的点查负载下,HBase的P99延迟明显更好,128线程时HBase P99约17毫秒,Cassandra约30毫秒。平均延迟差别不大,但延迟的尾部分布差距明显。
HBase这一轮表现好有两个原因。其一,HBase的row key天然有序,点查可以把请求直接定位到某个RegionServer上的具体region,缓存命中率相对更高。其二,HBase的BlockCache对热点数据块有更好的复用,相同负载下HDFS的本地短路读也降低了数据传输路径上的开销。
Cassandra延迟尾部偏大,主要来自读路径上的多SSTable合并。随着数据持续写入和flush,同一个分区键的数据可能分散在多个SSTable中,读取时需要对它们做合并。YCSB这种随机ID访问模式对Bloom Filter的命中率依赖很高,虽然有布隆过滤器挡了一部分,但总有少量请求会穿透到磁盘上做实际合并。
不过要补充一点:Cassandra可以通过调整读写一致性级别来优化读取延迟。测试时用的是默认的LOCAL_QUORUM,如果业务允许降低一致性要求,改为ONE级别读取,P99还能下降不少。代价是可能读到不一致的数据,这个后面会再说。
3.3 混合负载下的稳态表现
50%读、50%写的混合负载是最能说明问题的场景,因为真实业务很少只有纯写或者纯读。
64线程下,HBase吞吐约每秒5.8万条操作,P99延迟在22毫秒左右;Cassandra吞吐约每秒7.4万条操作,P99延迟接近30毫秒。128线程时差距进一步拉大,Cassandra吞吐高出大约20%,但P99延迟也同步恶化到55毫秒左右,HBase则稳定在38毫秒上下。
换成大白话就是:Cassandra更能扛并发总量,但延迟波动大;HBase吞吐上限稍低,延迟却更稳定。
这个特征其实反映了两者在架构上的根本取舍。Cassandra的分散式写入让它更容易把负载平摊到所有节点,吞吐自然更高;但读取时的SSTable合并和可调一致性机制引入了额外开销,导致尾部延迟不够稳定。HBase写入链路虽然长,读取却因为行键有序和块缓存机制受益,延迟分布更可控。
实际监控里还观察到另一个现象:HBase在混合负载下CPU使用率比Cassandra低不少,但HDFS的IO吞吐更高。这说明HBase在存储层分摊了更多的IO压力,对CPU的依赖反而不那么重。
4. 从结果延伸到系统机制:性能差距不是偶然
4.1 HBase有序行键带来的读路径优势
HBase把行键按字典序排列并切分成一个个region,RegionServer上的MemStore、BlockCache和HFile都按这个顺序组织。这种设计让范围扫描成为HBase的天然强项,也让随机点查能够快速定位到某个特定region。
加上HDFS的短路读能力,RegionServer在读本地数据块时可以直接绕过网络栈,减少了一层开销。测试中HBase的点查P99更低,和这一点关系很大。
HBase也有自己的麻烦。行键有序在带来扫描优势的同时,如果业务写入的行键总是递增或者集中在某一段范围,就会产生写热点,所有写请求全打到同一个RegionServer上。更郁闷的是,这个问题做预分区只能缓解,随着数据分布变化,热点还会漂移。
用HBase且业务行键单调递增的,一定想办法加盐或者加随机前缀,不然写入热点迟早会成为运维噩梦。
4.2 Cassandra分散分区与写扩散的代价
Cassandra把数据按分区键的哈希值打散到所有节点上,天然就没有中心节点,所以写热点问题比HBase轻很多。分区键相同的数据在物理上会相邻存储,查询时也能快速定位。
但哈希打散是有代价的。一旦需要范围查询,Cassandra就无能为力,因为它不是按业务字段排序存储的。这也是为什么在Cassandra里设计表结构时,必须事先把所有查询模式想清楚,否则表建完再改就是灾难。
再回到延迟问题。Cassandra读取时要把Memtable、RowCache、多个SSTable合并在一起,生成最终结果。SSTable越多,合并成本越高,P99就越容易劣化。这也是Cassandra对compaction策略比较敏感的原因,SizeTiered Compaction适用于高写入场景,但读放大严重;Leveled Compaction能换取更稳定的读延迟,却在写入时带来更高的IO开销。我们在测试中统一使用默认策略,算是保守方案。
4.3 资源消耗和运维模型差异
监控数据显示两边的资源消耗节奏完全不同。
Cassandra的CPU使用率普遍偏高,尤其是在混合负载下,约比HBase高10到15个百分点。原因是它的读路径和写入路径都要做更多的内存和磁盘结构处理,Compaction也直接把IO压力压在本地。HBase则把大量数据放在HDFS上,RegionServer相对省心,但DataNode的IO和网络占用明显更高。
运维模型方面,HBase依赖ZooKeeper和HDFS,组件多、故障链路长,任何一个环节抖动都可能影响全集群。Cassandra的每个节点自治,部署升级更简单,但一致性维护需要手动执行修复操作,如果长期不做repair,数据不一致问题会逐步积累。
我的体会是:如果团队对Hadoop生态熟悉,HBase走得更顺;如果团队人少、想要低运维成本的独立数据库,Cassandra的上手门槛明显更低。
5. 测试中真实踩过的坑与排查经验
5.1 预热不充分导致数据假象
第一轮测完,HBase读性能惨不忍睹,P99比Cassandra高出好几倍,当时差点直接定结论。后来查监控发现,测试时BlockCache还没热起来,大量请求直接打到磁盘上,HFile的读放大把延迟拉爆了。
解决办法很简单也很土:先把要测试的数据集完整跑一遍读操作,作为预热过程,再等缓存稳定后开始正式压测。
任何LSM架构数据库的对比测试,没有预热阶段的结论都不可信。预热不只是跑一遍数据,还要观察读延迟是否进入平稳状态,平稳后再开始统计。
5.2 JVM堆参数不一致带来的隐性陷阱
数据库在不同堆大小下的GC行为差异极大。第一次对比时,HBase默认堆是8GB,Cassandra默认堆是24GB,结果Cassandra的GC停顿明显更少,测出来性能好得离谱。后来统一把两个进程的堆都设为16GB,并开启同一种G1配置,才得到相对公平的数据。
跑测试时盯着GC日志往往能先于性能指标发现问题。如果看到频繁的Full GC或者长暂停,先别管吞吐数字,回JVM参数调优才是正事。
对HBase来说,MemStore和BlockCache都在JVM堆内,堆调大能提升读写性能,但RegionServer会很快吃满内存,GC时间反而上升。Cassandra把大部分内存用在堆外,设置更高堆大小不一定直接提升性能。两边的优势和瓶颈并不在对同一个位置。
5.3 磁盘和文件系统差异的影响
测试过程中还遇到过一次磁盘故障导致的误判。第一轮测到一半,Cassandra节点的SSD温度过高触发降速,写入吞吐直接掉了一个数量级。如果不看磁盘健康状态,很容易得出错误结论。
另外,XFS和ext4文件系统对LSM数据库的性能也有影响。XFS在高并发写入下表现更稳定,实测中最终统一使用XFS并开启noatime,减少不必要的元数据更新。
Cassandra还有一个容易被遗忘的排查点:commit log和数据目录如果放在同一块磁盘上,写入时IO路径会互相争抢。建议生产环境把commit log和data目录拆分到独立磁盘或分区,测试环境不方便拆分的话,至少要注意IO占用数据是否异常。
5.4 客户端线程数划定不当
YCSB线程数从16直接跳到128,中间缺少32和64的梯度,导致有一轮数据看不出吞吐拐点。后来补齐了32、48、64、96、128多个档位,才画出完整的变化曲线。
线程数梯度不仅影响结果可靠性,还能帮助判断数据库的扩展性。HBase在64到96线程之间出现瓶颈,128线程时吞吐反而下滑;Cassandra在达到96线程后仍能缓慢增长。这种差异对容量规划意义重大,可以直接指导后续集群规模的估算。
6. 选型建议:什么业务场景选谁更合理
6.1 适合HBase优先的场景
如果你的业务对强一致性要求高,需要频繁做范围扫描,又已经有Hadoop生态基础,HBase会是更稳妥的选择。
典型场景包括:订单流水查询、日志检索、基于设备ID加时间范围的数据回溯。这类查询天然依赖有序行键,HBase的架构优势能最大程度发挥出来。
但选择HBase前要做好心理准备,Region管理和预分区需要额外投入人力;HDFS的维护也要求团队具备一定的运维经验。写入热点问题必须从设计阶段就开始规避,比如加盐、预分区策略、row key设计规范,上线后再调整代价非常大。
6.2 适合Cassandra优先的场景
如果业务是写入量大、读取以点查为主,对一致性要求可以放宽,或者需要多数据中心部署,Cassandra是更合适的选择。
典型场景包括:消息推送状态记录、用户行为埋点、IoT设备状态快照。Cassandra的无主架构在多活容灾和跨机房复制上的优势非常明显,运维上少依赖组件,升级扩容也更灵活。
代价是:点查性能虽然不错,但范围扫描能力很弱;读延迟在特殊负载下会出现长尾;需要安排周期性的repair任务来维持数据健康。如果你的业务经常分析变更明细、做区间检索,Cassandra大概率会让你难受。
6.3 我的最终选择和个人体会
项目最终选了HBase,不是因为它在测试中全面胜出,而是因为业务对强一致性和范围扫描的依赖度更高。Cassandra在吞吐上的优势确实存在,但业务场景用不上这部分超额性能,反而HBase的延迟稳定性对我们更重要。
测试跑完到今天已经过了一段时间,线上HBase的表现基本符合测试预期,吞吐和延迟都在预算范围内。不过我也承认,如果当初的业务改成高并发写入加简单点查为主,我会毫不犹豫选择Cassandra。选型没有绝对的对错,只有是否匹配场景。
这次测评最大的收获是让我对“性能对比”有了更客观的认知:没有统一环境、没有业务模型约束的测试数据只能作为参考,真正有效的结论必须从自己的需求倒推。如果未来还要做类似数据库选型,我大概率会沿用这套方法论——先描述业务负载,再搭同构环境,最后让数据说话。