深入解析Cassandra:无主架构与线性扩展的分布式存储引擎

发布时间:2026/9/17 4:51:00
深入解析Cassandra:无主架构与线性扩展的分布式存储引擎 有人聊起大数据存储第一个想到的往往是HDFS要不就是MongoDB、HBase。可一旦聊到“PB级数据还要扛住全球多活、写入永远不能停”我脑子里第一个蹦出来的其实是Cassandra。这玩意儿没有主节点所有节点地位一样写入性能可以跟着节点数近乎线性扩展坏几台机器也不影响服务。今天这篇就把Cassandra的底裤扒一扒看看它到底是怎么把几百TB甚至PB级别的数据稳稳当当地收下来的顺便聊聊我这些年实际部署和踩坑的经验。这个内容适合谁如果你是后端工程师、架构师或者正在做技术选型、想搞懂分布式存储核心机制那这篇文章可以帮你省掉大量查文档的时间。我不会只讲概念会尽量拆到“能直接拿去用”的程度。1. 先搞清楚Cassandra到底解决了什么很多人上手Cassandra的第一反应是“这玩意儿跟MySQL长得很不像”对它压根就不是关系型数据库。它是Apache下面的分布式NoSQL列族数据库脱胎于Amazon Dynamo的分布式设计和Google Bigtable的数据模型。最经典的应用场景是消息推送、时序日志、IoT设备数据、订单流水、社交信息流这类高吞吐写入、海量存储、多区域部署的活。我当年第一次真正接触Cassandra是在一个日增几十亿条消息的系统里。之前用MySQL分库分表做到后面运维想哭扩容要挪数据、跨库查询没法做、某个分片热到爆炸还得手动拆。换了Cassandra之后最直接的感受是加节点就像往停车位里加车往集群里加一台新机器告诉它“你入伙了”它自己会接管一部分数据不用你人工去搬。所以Cassandra的核心价值说到底就是三个无单点故障的分布式架构任何节点挂了集群照常工作线性扩展能力数据量大了加机器就行不用改应用多数据中心复制全球部署时天然支持就近读写但注意它的牛逼不是没有代价的。Cassandra不支持事务不支持Join查询必须按照主键设计来走。换句话说你得先把数据模型想清楚用“反范式”的思路去建表别把关系型数据库那套习惯带过来。2. 核心架构拆解无主架构与数据分布2.1 去中心化的节点对等设计Cassandra最鲜明的特征就是没有Master节点。每个节点都跑着同样的进程、承担同样的责任没有什么“领导”和“下属”的区别。这跟很多分布式系统不一样比如HDFS有NameNodeElasticsearch有Master节点Kafka有Controller一旦主节点出事整个集群可能就暂时不可用了。Cassandra用去中心化设计换来的是极高的可用性。任何一个节点挂了客户端可以把请求发给其他任意一个存活节点由那个节点代为转发。具体转发到哪个节点由分区器Partitioner决定。每个节点都维护着一份集群状态元数据节点之间通过Gossip协议互相传递信息这样大家谁活着、谁挂了、谁新增了每个人都心知肚明。这种设计天然适合大规模集群因为节点之间不用全互联只需要定期通信就能同步状态。我自己的经验是这种架构在部署和运维上非常省心。比如要做集群滚动升级一台一台重启节点整个过程对客户端完全无感。这在传统主从架构里很难做到因为主节点一重启整个集群可能就要进入只读模式或者短暂不可用。2.2 一致性哈希与虚拟节点Cassandra怎么决定一条数据存在哪里答案是一致性哈希。每一条数据都有一个主键对主键做哈希运算得到一个token值。整个哈希环范围从2^-63到2^63-1每个节点负责环上的一段区间。举个例子张三的主键hash出来是100李四的主键hash出来是200环上的节点A负责0到150节点B负责151到300那张三的数据就落在A李四的数据就落在B。但这里有个问题如果节点数很少哈希环上的区间划分可能会不均匀。比如三台机器性能各不相同某台机器落在的数据特别多就容易形成热点。为了解决这个问题Cassandra引入了虚拟节点Virtual Nodes简称vnodes的概念。你可以把每台物理节点拆成256个虚拟节点随机分布在哈希环上这样数据分布就均匀多了。而且有了vnode新增节点时数据迁移量相对均匀不会出现某台机器突然被大量数据压垮的情况。在我实际运维的经验中如果集群超过20台机器强烈建议开启vnodes。它不仅能提升数据分布均匀度还能让节点扩容和缩容时的数据流平衡许多。默认配置是num_tokens: 256这个值已经能保证大部分场景的均匀分布了。2.3 分区策略与数据副本的放置决定数据放在哪除了Hash算法本身还有分区策略。最常用的是Murmur3Partitioner它把分区键做Murmur3哈希计算速度快、分布均匀。较老版本的RandomPartitioner用的是MD5哈希已经不建议再用了。有了数据分布还得靠副本保证可靠性。你可以设置复制因子Replication Factor简称RF表示一份数据要存几份。比如RF3意味着每条数据会存在三个不同节点上。副本怎么选择Cassandra有两种策略SimpleStrategy单数据中心时从哈希环按顺时针方向选择后续节点作为副本。NetworkTopologyStrategy跨数据中心时按机架感知的方式选择副本保证同一副本不会落在同一机架上。我在生产环境里面跨机房部署一定使用NetworkTopologyStrategy。比如在两个机房每个机房RF2那么任何一个机房整体挂掉另一个机房依然有完整的副本服务不会中断。如果用SimpleStrategy数据可能都集中在一个机房一个机房断电就完蛋了。3. 写路径与存储引擎数据究竟怎么落盘3.1 CommitLog、Memtable与SSTable很多人一开始搞不清楚Cassandra写入为什么快。其实秘密在于“先写内存再顺序写盘”。一次写入请求到达节点后有两个动作同时发生写入CommitLog追加写磁盘日志保证数据不丢。写入Memtable内存中的有序数据结构保证写请求快速返回。Memtable在内存中累积达到一定阈值默认大约是128MB就会 flush 成SSTable文件落盘。SSTable是不可变的、有序的、列式存储文件。之后读数据如果内存里没有就去SSTable里找。这套机制带来的好处非常明显写入没有随机IO全部是顺序写磁盘所以单节点吞吐量极高。我见过单节点跑到每秒几万次写入的案例这在传统B树存储引擎上是不敢想的。要注意的是CommitLog默认写盘成功后写请求才算成功。如果你的业务接受度更高也可以通过调整持久化策略降低同步级别换来更快速度但不建议因为存在丢数据风险。3.2 LSM Tree的Compaction策略SSTable是不可变的数据不断写入会生成越来越多的SSTable文件。那读的时候怎么办可能得从多个SSTable里读然后合并结果时间长了就慢了。所以Cassandra需要定期做Compaction把多个SSTable合并成一个更大的SSTable顺便把过期数据、已删除数据物理清理掉。Compaction策略最常见的有两种SizeTieredCompactionStrategySTCS把小SSTable合并成大SSTable适合写多读少的场景。LeveledCompactionStrategyLCS把SSTable分成一层一层的level每一层内保证key不重叠读放大低适合读多的场景。我踩过的坑是STCS在长时间运行后容易造成空间放大磁盘占用飙高因为小SSTable会先合并成中的、中的再合并成大的中间过程很多临时文件。如果你对磁盘空间敏感建议用LCS。但LCS也有问题——Compaction期间IO开销比较大容易影响读写性能。所以怎么选得根据业务读写比例来。还有一个经验之谈Compaction一定要做好限流在cassandra.yaml里面可以配置compaction_throughput_mb_per_sec默认是16MB/s。在高负载集群里经常要调到64或者128加速Compaction进程不然SSTable积累过多会影响读性能。但调高了又会占用大量磁盘IO需要观察实际情况平衡。3.3 删除并非真删除Tombstone机制Cassandra里有一个让很多人头疼的概念Tombstone墓碑。因为SSTable是不可变的所以删除操作不会真的把数据从文件里抹掉而是写入一个标记表示“这条数据已经删除”。读数据时看到标记会忽略这条数据Compaction时标记所在的数据才会真正被清理。如果业务里高频删除或更新数据就会产生大量Tombstone。短时间积累过多读性能会明显劣化甚至报出tombstone相关异常。我见过一个案例一个表每天更新上亿条数据Tombstone数量暴涨导致查询耗时从几十毫秒涨到了数秒。解决方案有几个调整gc_grace_seconds默认是864000秒10天意思是Tombstone保留10天才允许被彻底清理。如果数据可以接受更短的恢复窗口可以适当调低加速清理。在业务层尽量避免频繁更新能追加写就追加写。定期执行nodetool compact手动触发Compaction强制清理。这里要特别提醒Tombstone机制本身是Cassandra分布式特性的必然选择——它得保证“删除”这个操作也能在集群内传播最终一致。理解这个机制再去设计数据模型会少踩很多坑。4. 读路径与一致性性能和准确性的博弈4.1 读流程从内存到磁盘的多级查找一次读请求到达节点后会先在Memtable里查然后按SSTable的新旧顺序一个个查。为了加速查询Cassandra在每个SSTable文件里面维护了Bloom Filter布隆过滤器。它会快速判断“这个key在这个文件里有没有”如果Bloom Filter说没有就不会去读这个文件。Bloom Filter是Cassandra读性能的大功臣。默认bloom_filter_fp_chance是0.01也就是1%的误判率。如果你的SSTable文件特别多每个都靠Bloom Filter挡掉“不存在”的情况只返回可能存在的文件读放大会小很多。SSTable本身内部是有序的按分区键排序每个数据块有索引。所以真正读取数据时可以定位到具体的块顺序扫描块内数据。整个读链路大概是内存查Memtable → Bloom Filter过滤 → 索引定位 → 读SSTable → 合并结果。4.2 一致性级别QUORUM与LOCAL_QUORUMCassandra的一致性不是强一致而是可调节的一致性级别Consistency Level。写入和读取都可以指定不同级别。最常用的是ONE只要一个副本确认就算成功/读取。性能最好但可能读到旧数据。QUORUM需要超过一半副本确认。通常指 (RF/2) 1 个节点。比如RF3需要2个节点确认。LOCAL_QUORUM在当前数据中心内超过一半副本确认。常用于跨数据中心部署避免跨机房通信延迟。ALL所有副本都确认。最安全但任何一台机器挂了就不可用。在一个跨机房部署的生产环境中我组里常用LOCAL_QUORUM做读写这样既保证了机房内多数派的稳定确认又避免了读请求跨机房带来的高延迟。如果你只有一个机房QUORUM通常就够。关键要理解一致性级别和复制因子的关系。RF3, QUORUM2时写操作只要2个副本成功即返回第三个副本通过后台同步。如果读也走QUORUM2那么理论上读写总是能遇见至少一个最新副本所以读到的数据一定是最新的。这就是最终一致性和读己之写Read-Your-Writes的基础。4.3 数据冲突处理Last Write Wins既然存在多副本就必然有冲突。比如两个客户端同时修改同一条数据一个写值A一个写值B最终以哪个为准Cassandra的做法是Last Write WinsLWW谁时间戳大谁赢。每个列值的写入都带有客户端提供的时间戳Cassandra比较时间戳大小大的覆盖小的。这里有几个值得警惕的坑如果客户端机器时钟不同步一个时钟快的客户端写入的值可能永远“赢”过时钟慢的导致数据异常。所以一定要用NTP校准所有客户端和服务器时钟。LWW在相同时间戳下会按照客户端ID的排序来选但这属于极端情况一般不会遇到。同一个数据的并发更新Cassandra不会做合并只会覆盖。这也是Cassandra不适合做复杂事务型业务的原因。5. 集群运转的隐藏机制Gossip、故障检测与Hinted Handoff5.1 Gossip协议节点间的信息传递Cassandra没有中心节点那节点之间怎么知道彼此的状态靠Gossip协议。每个节点每秒随机选一两个节点交换各自认识的节点状态信息。这种“八卦”传播方式虽然听起来不正经但在分布式系统里非常有效。它保证了即使节点很多、网络拓扑很复杂每个节点最终能收敛到相同的集群视图。Gossip传递的信息包括节点存活状态、负载信息、Schema版本、token范围等。这些信息通过FailureDetector故障检测器判断节点是否存活。默认的故障检测不是简单的心跳超时而是综合历史响应时间算出一个可疑概率更精准。5.2 Hinted Handoff临时接管写入如果一个节点暂时下线写请求到达其他健康节点时健康节点会把数据先存下来然后记一条Hint等那个节点上线后再把数据补发给它。这就是Hinted Handoff机制。这机制说起来很好但有坑如果下线时间过长Hint文件会积累很多节点上线后需要大量补发可能拖垮节点。如果一个节点下线超过max_hint_window_in_ms默认3小时其他节点就不再保存Hint而是直接接受写入让数据通过其他副本间的修复机制Repair来补齐。所以如果集群里出现长期下线节点一定要尽快处理或者主动降低RF以保护集群正常写入。5.3 Read Repair和手动Repair除了Hinted HandoffCassandra还有Read Repair机制。当读请求发现某些副本的数据不是最新时会在后台把新数据同步给旧副本。不过Read Repair只修复读过的数据对于长期不读的数据就得靠手动执行nodetool repair来修复。nodetool repair是我每次运维必做的定期任务。它会比对所有副本的数据把不一致的部分修复。如果从来不跑数据不一致会悄悄积累等哪天节点坏了从副本恢复出来的数据可能是旧的这坑相当深。我记得一个真实案例某团队上线Cassandra一年从未跑过Repair。后来一台节点故障数据从副本恢复结果大量数据丢失。后来排查发现是副本间长期不同步导致的。所以Repair一定得作为日常运维任务建议每周一次按token范围分批执行避免整库Repair产生过大IO。6. 容量规划与Schema设计的实战经验6.1 计算集群规模不要凭感觉很多团队上线Cassandra之前不知道怎么估算节点数。这里给一个我常用的粗算公式假设单条记录平均大小1KB每天新增1亿条则每天新增数据约100GB。如果保留30天总容量约3TB。RF3时总存储需求约9TB。接下来看单节点容量。一般建议单节点数据量不要超过1TB留足Compaction空间和系统余量所以9TB至少需要9到12个节点。再加上读写吞吐的考虑建议再搭配合适的CPU内存配置比如16核32GB起。这个方法只是个粗略估算但它能避免很多同学一开始就犯的“节点太少”错误。节点太少集群热点没法分散单点故障影响面也大。6.2 Schema design反范式、按查询建表Cassandra数据建模的核心原则和MySQL完全相反先想查询再建表。要查什么就按什么建主键。我见过太多人把MySQL表结构直接搬过来然后各种报错“查询需要ALLOW FILTERING”。用一个最经典的例子解释假设有一张用户订单表。订单属性包括用户ID、订单ID、商品名、金额、创建时间。查询需求1查某个用户的所有订单。CREATE TABLE orders_by_user ( user_id TEXT, order_id TEXT, product_name TEXT, amount DECIMAL, created_at TIMESTAMP, PRIMARY KEY (user_id, created_at, order_id) );查询需求2查某个订单的详情。CREATE TABLE orders_by_id ( order_id TEXT PRIMARY KEY, user_id TEXT, product_name TEXT, amount DECIMAL, created_at TIMESTAMP );看到没同一份数据建了两张表分别服务两个查询。这个做法在Cassandra里完全正常因为存储成本相对低写入也便宜但查询的稳定性和速度会好非常多。还有一点分区键的选择特别重要。分区键决定数据落在哪个节点。选得不好会产生热点。比如IoT设备数据如果只按设备ID做分区键某些设备写入特别频繁对应节点就会成为热点。可以考虑加时间维度做复合分区键把数据进一步散开。6.3 主键选择与集群部署的最佳实践还有一个经验是尽量采用“桶”的方式设计分区键。比如社交信息流场景user_id用户很多但某个大V的信息流可能写入量巨大。可以把分区键设计成(user_id, bucket)bucket取时间段按小时分桶。这样每个分区不会无限膨胀数据分布也更均匀。部署层面有几点要注意内存配置Heap大小一般建议16GB以下不要把整台机器内存都塞给JVM Heap要给OS Page Cache留空间。像我们32GB内存的机器Heap设8GB剩下的留给系统缓存。磁盘建议用SSD尤其是有大量随机读的场景。全HDD的集群Compaction会慢到让人崩溃。交换分区一定要关掉swapCassandra在高内存压力下频繁swap延迟会飙到不可接受。7. 常见问题与排查技巧实录7.1 问题速查表整理几个我在实际运维中最常遇到的问题以及排查思路现象可能原因排查/解决方向写入延迟越来越高SSTable文件过多、Compaction跟不上执行nodetool compactionstats观察调大Compaction吞吐考虑更换Compaction策略读超时报大量Tombstone业务高频更新/删除产生Tombstone查表结构确认写模型是否合理调低gc_grace_seconds手动compact节点状态显示UN但延迟高GC停顿、磁盘IO饱和观察GC日志检查磁盘健康必要时调整Heap、换SSD数据分布极不均匀没有启用vnodes或分区键设计有热点检查token分布重新设计分区键启用vnodes集群经过长时间运行节点重启后数据不一致长期未跑Repair定期执行nodetool repair按范围分批跑跨机房写入慢一致性级别设成了QUORUM/ALL需跨机房确认改为LOCAL_QUORUM7.2 常用运维命令的实战心得nodetool是Cassandra运维绕不开的工具。我最常用的几个nodetool status看集群节点状态、负载、token范围。nodetool info看单个节点的内存、磁盘、GC情况。nodetool tablestats看表的SSTable数量、读写下发情况。nodetool compactionstats看Compaction任务堆积情况。nodetool repair -pr只修复主范围比全量Repair快很多。使用Repair时一定要挑低峰期而且要分段执行。比如一个大表可以按token范围一次跑1/10的范围不然整库Repair会拖垮集群正常服务。我踩过最惨的一次坑就是在业务高峰期跑了全量Repair结果整个集群响应时间从20ms涨到了1秒钟差点变成事故。7.3 监控与告警的落地建议最后说一下监控。Cassandra没有监控就等于裸奔因为很多问题都是慢慢积累的。建议至少要对接Prometheus Grafana采集以下几类指标读写延迟的P99、P95值而不是只看平均值。SSTable数量如果某个表SSTable数持续上涨说明Compaction有问题。Compaction的Pending任务数堆积太多说明来不及合并。GC暂停时间Full GC频繁是Cassandra性能下降的重要前兆。磁盘空间使用率建议设定85%告警90%紧急。这些指标看着简单但每一项背后都对应一个典型的故障模式。我遇到过磁盘97%打满导致整个节点Crash的案例从那以后磁盘空间告警我从来都是最优先处理。写在最后一点真实体会有人说Cassandra门槛高我反而觉得它的门槛不在技术上而在思维转换上。只要你舍得扔掉关系型数据库那套设计思路愿意按查询去反范式建表接受“最终一致”而不是“强一致”那么Cassandra能给你带来极其爽快的扩展体验。我自己的习惯是任何新业务接入Cassandra之前先花两天时间把数据模型设计清楚——列出所有查询场景、计算数据分布、定好分区键和集群数再动手。宁可前期多花时间也不要上线后每天和热点、Tombstone、Compaction故障搏斗。如果这篇文章对你有帮助建议你拿一台测试机真正搭一个三节点集群把建表、写入、扩容、模拟节点故障整套流程跑一遍。纸上得来终觉浅分布式系统这种玩意儿亲手踩过坑印象才最深刻。