做运维这些年Cassandra集群我前前后后搭过不下十套从三节点的开发环境到几十台机器的大集群都折腾过。很多人一上来就搜“Cassandra集群部署最优配置”搜到的无非是一堆推荐参数照搬上去结果跑起来各种诡异问题GC频繁、节点掉线、读写延迟忽高忽低。我踩过这些坑之后才明白所谓“最优配置”本质上不是某个公式或某个文件模板而是针对你的机器、你的数据模型、你的访问模式做出来的均衡取舍。这篇文章我就结合自己实际部署调优的经历把一套真正可落地的集群配置思路和相关参数拆开来讲尽量让刚接触Cassandra的人也能照着动手让已经入坑的人也能回头审视自己的配置有没有问题。1. 整体配置思路先定场景再谈参数1.1 “最优”为什么不能直接抄很多人有个误区觉得网上别人贴出来的cassandra.yaml就是金标准。我见过有人直接把某个大厂的所谓“生产配置”粘贴进来结果自己的机器只有32G内存却配了24G堆一启动就OOM。真实的配置过程是反过来的先搞清楚你的业务是什么读多写多、数据量多大、延迟敏感不敏感然后反推每一类参数应该怎么设。最简单的分类逻辑是这样写多读少的日志型业务重点在顺序写和CommitLog上的优化读多写少的查询型业务重点在缓存和索引效率上混合型业务则要找一个平衡点通常不能把一个方向压得太狠。所以我会在部署之前先做一次简单的需求评估至少确认几个信息预估单表数据量、QPS峰值、读写比、单条数据大小、一致性要求、副本数要求。这些确定之后后面的参数才有意义。1.2 Cassandra里最核心的几个权衡Cassandra本身是AP模型的分布式数据库它在意的是可用性和分区容错性一致性是可以通过配置去调的。这就引出了第一个关键权衡一致性和可用性怎么平衡。默认的QUORUM级别多数派确认是比较稳的选择但如果你某个机房的节点刚好掉了一半QUORUM就会让写入直接失败这时候可能要把一致性降为ONE来保住可用性代价是可能读到旧数据。第二个权衡是副本数和存储成本。复制因子设成3看起来很标准但如果机架划分不合理三个副本很可能落在同一个机架甚至同一台物理机上机架断电就直接丢数据。副本数不是越多越好它直接乘以存储占用和网络开销我会按“本地跨机架可用”的原则来规划副本分布而不是无脑三副本。第三个权衡是堆内存和堆外内存的关系。Cassandra的堆内存主要管元数据和短期对象真正的大头数据走的是堆外内存和操作系统页缓存。很多人把堆调得巨大反而挤占了页缓存的空间读性能不升反降。这个我在后面专门展开讲。2. 核心配置细节JVM、内存与存储路径2.1 JVM堆大小与GC选型堆大小没有一个通用值但有一条经验线在Cassandra 3.x和4.x版本里我的经验是16G以下机器的堆不要超过8G32G机器的堆可以给到12G到16G。为什么不能把堆给满因为Cassandra的Bloom Filter、Compression Metadata这些关键结构占的是堆外内存同时Linux的页缓存用来加速数据读取如果堆把所有内存都占完了磁盘读命中率会非常难看。GC选型上堆在8G以下用CMS8G以上用G1这是比较稳妥的路线。有一个容易被忽视的参数是GC暂停时间很多默认配置在内存波动大的时候会频繁Full GC。我自己的习惯是在cassandra-env.sh里显式把G1的MaxGCPauseMillis设成200把停顿目标放宽一点换取更高的吞吐。如果你发现GC日志里young区回收非常频繁优先怀疑是不是堆太小或者缓存参数不对不要一上来就继续加堆。另外一个容易被忽略的是JVM的启动参数里有是否启用超大页Large Pages。我建议生产环境不要开除非你非常确定操作系统和硬件都支持且做过压测否则TTLBTranslation Lookaside Buffermiss反而会把性能拖下去。2.2 缓存参数调整Row Cache与Key CacheCassandra的Cache大家最常调的其实是Key Cache。它缓存的是分区索引的位置信息只需要很小的内存就能带来明显的读性能提升。默认是key_cache_size_in_mb设成5%这个值在写多读少的场景下可以压到1%到2%把内存留给页缓存读多场景可以放到10%到15%但超过这个量收益就开始递减。Row Cache则完全不同它缓存的是整行数据命中率受数据模型影响非常大。宽行模型每行很多列和小行模型每行几列最优策略都不一样。我自己的经验是除非单行数据不会被频繁更新并且读请求重复度极高否则Row Cache大多数时候不要开因为维持它的内存一致性开销很可能大于收益。这个判断不绝对但是能帮你省掉很多排查缓存命中率的精力。2.3 数据目录、CommitLog与磁盘规划Cassandra最怕的是数据目录和CommitLog放在同一块物理盘上。CommitLog是顺序写的数据目录是随机读写混在一起会互相拖慢。最优的规划是两块独立磁盘一块专门放CommitLog另一块放数据文件。如果机器上只有一块盘则至少要在文件系统层面把两个目录区分开并留意IO Util的争抢情况。数据盘如果条件允许尽量用SSD。Cassandra对顺序读和随机读都有要求HDD在数据量大时Compaction跟不上会出现写放大和读延迟飙升。我见过一个项目因为用了老式机械盘Leveled Compaction的SSTable重写把磁盘IO全部打满每天高峰期读写全部超时。换到SSD之后同样的配置延迟直接下降一个数量级。3. 关键配置文件逐项拆解cassandra.yaml里的门道3.1 cluster_name、num_tokens与虚拟节点cluster_name必须全集群一致这个不说了见过有人copy配置时忘了改两个节点之间怎么都握不上手日志反复刷gossip协议错误。num_tokens在3.x以后默认256使用虚拟节点vnode机制。如果你是从老版本迁移过来的可能会遇到只设一个token的情况我建议新集群一律使用vnode。vnode最大的好处是不用手动算token range节点增减时自动做负载均衡。但有一个代价因为每个节点持有多个小token range做范围扫描会比单token更重。如果你有比较多的范围查询比如按时间批量拉取可以适当把num_tokens调小到64或128减少元数据碎片。需要注意num_tokens的改动不是随时能做的需要重做集群或者用专门的rebalance工具所以部署之前就要决定好。3.2 seed节点与Gossip配置seed_provider是节点启动时用来发现集群的。它不是流量的中心节点只是帮新节点认识其他节点然后gossip协议会自组织维护成员关系。所以seed节点挂了不会导致集群不可用但是新节点加不进来。生产环境我习惯放2到3个seed节点而且要固定下来不要随意变动。seed节点尽量选稳定的老节点推荐和机架划分结合起来跨机架分布提高容错性。有个坑是listen_address和broadcast_address的关系。默认写法是listen_address填本机IPMulticast用不上的环境还需要注释掉。多网卡或跨网段环境很多人忽略broadcast_address导致节点间通信走的IP是错的客户端能连上但集群内部互相看不见。3.3 并发参数与线程池cassandra.yaml里面有大量concurrent_reads、concurrent_writes、concurrent_compactions这些参数。默认值在4.x版本里是根据CPU核数自动算的说实话大多数机器不需要改。真正需要手动干预的是当某类操作成为瓶颈时比如大量跨分区Count查询可能需要单独调大concurrent_reads但调大并发的同时要注意它和每次请求内存占用的关系否则并发高了内存先爆。关于Compaction这是Cassandra里面影响最大的后台线程之一。SizeTieredCompactionStrategySTCS适合写多场景但它的空间放大Space Amplification比较大LeveledCompactionStrategyLCS适合读多和更新多的场景读放大和空间放大更小但写放大更高。TimeWindowCompactionStrategyTWCS是专门给时间序列数据用的如果你的数据有明显的时间分区特征我强烈建议用TWCS配合固定的保留窗口能大幅减少Compaction的无效扫描。4. 实操部署三节点集群从零搭起来4.1 操作系统层与硬件层准备这一步很多人跳过去直接装Cassandra其实很多时候问题都出在操作系统层。我部署之前会先做这几件事关闭swap或者把swappiness设为非常低1到10避免内存抖动时进swap导致长时间卡顿设置文件句柄上限至少65535Cassandra打开SSTable文件时文件句柄消耗非常大时间同步加好NTPCassandra节点之间跨时钟漂移会导致写入时间戳错乱排查起来极其头疼。我用一台16核32G内存的机器做示例准备两块盘一块系统盘兼CommitLog一块数据盘。两个目录分别规划为/mnt/commitlog和/mnt/data。生产环境我会把数据盘单独格式化用XFS或ext4并禁用atime更新减少不必要的元数据写操作。4.2 下载、解压与基础配置安装包不管用二进制包还是包管理器核心都是配置文件的修改。我的常规操作是先改cassandra.yaml里的路径类配置再改cassandra-env.sh里的内存配置最后改cassandra-rackdc.properties配置机架信息。cluster_name: TestCluster num_tokens: 256 seed_provider: - class_name: org.apache.cassandra.locator.SimpleSeedProvider parameters: - seeds: 10.0.0.1,10.0.0.2 listen_address: 10.0.0.1 broadcast_address: 10.0.0.1 rpc_address: 0.0.0.0 broadcast_rpc_address: 10.0.0.1 data_file_directories: - /mnt/data/data commitlog_directory: /mnt/commitlog saved_caches_directory: /mnt/data/saved_caches这里强调一下data_file_directories可以配多个目录但前提是这些目录必须在不同磁盘上否则没有任何意义反而可能造成文件分布不均匀。同盘多目录是不会提升IO性能的在Cassandra里它只是把文件分散到不同文件系统而已。4.3 cassandra-env.sh中的关键改动堆内存我之前建议过按机器配置来。32G内存的机器我设MAX_HEAP_SIZE16G HEAP_NEWSIZE800M注意HEAP_NEWSIZE也就是年轻代大小设置太大会让Minor GC变慢设置太小会频繁GC。一个经验做法是让年轻代占整个堆的1/4到1/3我习惯先用1/4起步压测后看GC日志再微调。GC这块4.x版本默认用的是G1我加了这些参数JVM_OPTS$JVM_OPTS -XX:UseG1GC JVM_OPTS$JVM_OPTS -XX:G1RSetUpdatingPauseTimePercent5 JVM_OPTS$JVM_OPTS -XX:MaxGCPauseMillis200 JVM_OPTS$JVM_OPTS -XX:InitiatingHeapOccupancyPercent70InitiatingHeapOccupancyPercent默认就含在预调里但我显式写出来是为了让它控制在70左右保证G1老年代回收不会太晚触发。4.4 机架配置与跨机房规划cassandra-rackdc.properties里最核心的就是dc和rack的声明。dcDC_A rackRACK_A1不要小看这个文件。如果所有节点都不配置rack默认文件里的RACK默认值会让Cassandra认为所有节点都在同一个机架这会直接影响snitch的副本放置策略。我在某个项目里就因为漏配了机架所有副本全落在同一机架上机架断电后直接丢失完整数据副本从那以后再也不敢跳过这一步。工厂场景下我一般建议至少两个机架或两个可用区三个副本分布策略是两个副本在本地机架一个副本在远端机架这样本地读的延迟不高远端又能保证灾难容错。4.5 节点启动与集群状态验证配置完成后逐个节点启动bin/cassandra -R这里-R是用root运行时必须加的因为Cassandra默认拒绝root用户启动你可以建一个普通用户避免这个问题。启动后用nodetool检查状态nodetool status这个命令会列出集群里所有节点的状态、地址、Owns百分比等信息。我判断集群是否健康通常会看UNUp Normal状态还有每个节点的Owns值是否接近。如果出现DNDown Normal或者所有权比例严重不均的就要回去检查网络和token配置。nodetool describecluster这个命令可以快速确认集群名称、schema版本是否一致、snitch策略是否正确。5. 压测与性能验证配置是不是最优跑一跑才知道5.1 用cassandra-stress做读写压测Cassandra自带的cassandra-stress工具是检验配置最直接的方式。我自己压测一般分两步先压写再压读把结果放到同一张表里对比。bin/cassandra-stress write n1000000 -rate threads64 -node 10.0.0.1 bin/cassandra-stress read n1000000 -rate threads64 -node 10.0.0.1这里n是操作总次数rate threads是并发线程数。新手容易犯的错是一上来线程数开太大结果把服务端压垮误以为配置有问题。从小并发逐步往上加每次记录吞吐量和延迟百分位。压测时我会额外关注p99延迟而不是平均值。平均值在Cassandra这种分布式系统里非常具有欺骗性因为少数慢请求会把均值拉高或者反过来被大量快请求稀释。p99高而p50正常说明存在长尾延迟典型原因有Compaction抢占IO、网络抖动、GC停顿。5.2 从压测结果反推配置调整有一次压测我发现写延迟正常读延迟的p99却飙到几百毫秒。用nodetool tpstats看到读线程池有大量Pending任务于是判断concurrent_reads默认值不够。当时的机器是32核我把concurrent_reads从默认的32调到了64再压一次p99立刻下来。这个经验要说清楚调并发是治标真正的治本是检查页缓存命中率和数据模型。同样的如果压测时发现CPU没打满但IO已经100%说明瓶颈在磁盘而不是CPU。这时候调整线程数没有意义要检查Compaction策略、数据盘是否混用或者考虑增加节点分担IO。nodetool cfstats这个命令能看到每个表的读延迟、写延迟、压缩比等数据。我压测之后一定会看一眼Bloom Filter的FPRate误判率如果误判率偏高说明Bloom Filter内存不足可以考虑单表级别调大bloom_filter_fp_chance默认值0.01可以调到0.001代价是多占一些内存。5.3 合适副本策略的压测对比如果你的需求允许我建议压测时分别试一下复制因子2和3的差异。复制因子3写的放大比2多了50%但读可用性、故障恢复能力完全是另一个档位。大多数重要业务我坚持三副本副本因子2看着省空间实际上坏一个节点后数据就变成单副本恢复期间任何风吹草动都可能丢数据。压测时你会发现相同配置下RF2的写入延迟略低但这不代表它适合生产。6. 常见问题与排查技巧实录6.1 节点反复注册、日志出现大量Hinted Handoff有一次集群运行中某个临时故障的节点恢复后日志里频繁出现向其他节点发送Hinted Handoff的提示同时gc_grace_seconds时间窗口内出现了数据不一致。排查下来发现是临时节点关闭期间积累了大量写操作恢复后开始向副本节点补发。这个属于正常现象但如果Hinted Handoff文件过大超过commitlog大小会拖慢恢复速度。我通常会手动调用nodetool failover不对准确说应该是查看hinted handoff状态用nodetool statushandoff必要时disable等系统稳定后再enable。这个操作要执行得谨慎确保不会丢数据的前提下才做。6.2 磁盘空间被Compaction占满Compaction需要额外空间来做SSTable合并空间放大是真实威胁。我在一个数据量增长很快的集群里遇到过磁盘被填满Compaction反复失败。排查后发现主要原因是STCS的空间放大率在数据接近磁盘容量时急剧恶化。解决办法除了换LCS或TWCS外还可以在配置里单独设Compaction吞吐上限避免它一次合并太多SSTablecompaction_throughput_mb_per_sec: 16数字取决于磁盘能力。SSD上可以给到32到64HDD上建议16以下。配合设置sstable_preemptive_open_interval_in_mb: 50让它每次合并前预打开临时文件避免合并过程中出现空间不足。6.3 GC暂停导致节点假死GC暂停是最常见的“僵尸节点”原因。节点进程还活着但是所有线程都在STW客户端超时、nodetool up但实际无响应。要快速定位就得打开GC日志-XX:PrintGCDetails -XX:PrintGCDateStamps我第一次排查时用GCLogViewer之类的工具查看GC暂停分布发现老年代回收一次要5秒多一天发生好几次。后来把InitiatingHeapOccupancyPercent调低到60让G1更早介入回收同时给大对象比如批量写入的Blob设置了更合理的chunk大小Full GC频率明显下降。除了参数数据模型也要注意如果一行有超大集合列读写时产生的堆内临时对象会极大增加GC压力。6.4 配置变更没有生效改完cassandra.yaml后发现行为没变化这种问题通常出在没重启或者重启后加载了旧配置。Cassandra不像某些系统支持热加载所有cassandra.yaml的变更都需要重启进程才能生效。我自己有个习惯改完配置后先用bin/cassandra -f前台启动一次看启动日志里有没有报配置错误的提示确认无误再正常后台启动。这样可以避免重启后才发现某个路径写错导致启动失败。6.5 节点扩容后的数据倾斜新节点加入后vnode机制会自动迁移数据但如果一次加入太多节点或者某个旧节点上有大量被标记为“已删除”的数据墓碑可能会造成短期的数据倾斜。我用nodetool status观察Owns值变化时偶尔看到一个旧节点的Owns已经降到很低、但磁盘空间没释放这是正常的Compaction要跑完才会真正清理。别急着手动删SSTable文件那会让数据丢失风险急剧升高。最后的操作心得回头再看这个话题我的核心体会是Cassandra集群部署没有银弹。每一次“最优配置”都是针对当前版本、当前机器、当前业务做出来的局部最优解。很多参数之间是牵一发动全身例如堆大小影响GC、GC影响读延迟、读延迟又影响你对缓存参数的判断。所以我在每套集群上线前都会保留一份完整的压测报告和当时的配置快照后面每调整一个参数就对比一次而不是凭感觉混合调。这样即使过了几个月出了新问题也能快速定位是不是某次调优带来的副作用。最后提醒一句所有配置改动都要走变更流程Cassandra集群最怕的不是配置差而是无记录的“随手改”。