Cassandra在运行时会持续产生大量状态数据:每一次读写请求的延迟分布、每张表的SSTable数量、Compaction任务的排队深度、JVM堆内存的占用趋势、内部线程池的繁忙程度等等。这些数据并不是散落在日志里等着人去翻,而是通过JMX(Java Management Extensions)这套Java标准管理接口实时暴露出来的。可以说,能不能顺畅地收集JMX指标,直接决定了你对集群内部状态的可见程度,日常巡检、容量规划、故障定位都依赖这一层。下面从指标体系结构开始,依次讲清楚手动查询、远程访问配置和基于Prometheus的自动化采集方案。

一、先弄懂Cassandra的JMX指标体系长什么样
Cassandra的指标在JMX中以MBean的形式组织,最核心的域是org.apache.cassandra.metrics,运维日常关心的绝大部分数据都在这个域下面。除此之外,java.lang域提供JVM层面的信息,比如GC次数与耗时、堆内存各区域的使用量、线程总数;org.apache.cassandra.db域则暴露一些存储引擎内部组件的状态。如果你用JMX客户端连上去浏览一遍,会发现MBean的数量动辄上千个,不了解结构的话很容易迷失在列表里。
MBean的命名遵循固定模式,例如org.apache.cassandra.metrics<type=Table,keyspace=my_ks,scope=user_events,name=WriteLatency>。其中type表示指标所属的大类,常见的有Table、Compaction、ThreadPools、Storage、Client、Cache等;name是具体的指标名;scope用来进一步细分,表级别的指标会把keyspace和表名放在这里。理解这套命名规则非常重要,后面配置Prometheus抓取规则时,正则表达式匹配的就是这个字符串结构。
指标本身还分几种类型,读数方式各不相同。Gauge表示瞬时值,比如当前堆内存使用量、当前等待压缩的任务数;Counter表示只增不减的累计值,比如读写请求总数;Histogram记录值的分布,读写延迟就属于这种类型,除了Mean之外还能取到50th、75th、95th、99th分位数;Meter衡量事件发生的速率,会同时暴露OneMinuteRate、FiveMinuteRate等速率值。看延迟指标时一定优先看分位数而不是平均值,平均值很容易掩盖长尾请求,而长尾恰恰是用户投诉的来源。
二、用jmxterm和nodetool手动查询指标
排查问题时最直接的方式是用命令行工具连上去看。jmxterm是一个通用的JMX命令行客户端,下载下来就能交互式地浏览和读取任意MBean,典型用法如下:
# 启动jmxterm并连接远程节点 java -jar jmxterm-1.0.4-uber.jar $> open 192.168.1.10:7199 monitorUser Str0ngJmxPass # MBean太多,先切换到目标域再浏览 $> domain org.apache.cassandra.metrics $> beans # 读取节点数据量(字节) $> get -b org.apache.cassandra.metrics<type=Storage,name=Load> Value # 读取某张表的写入延迟P99(微秒) $> get -b org.apache.cassandra.metrics<type=Table,keyspace=my_ks,scope=user_events,name=WriteLatency> 99thPercentile
jmxterm的优势在于灵活,任何MBean都能查,适合临时验证某个猜想。但它的输出是原始键值对,看起来比较费劲,日常巡检更推荐用Cassandra自带的nodetool。nodetool底层同样走JMX通道,但它把常用指标做了汇总和格式化,几条命令就能覆盖大部分巡检需求。
# 节点整体状态:堆内存、负载、uptime、异常统计 nodetool -h 192.168.1.10 info # 内部线程池状态,重点关注active、pending和blocked三列 nodetool -h 192.168.1.10 tpstats # 单表详细统计:SSTable数量、读写延迟、空间占用 nodetool -h 192.168.1.10 tablestats my_ks.user_events # 压缩任务实时进度 nodetool -h 192.168.1.10 compactionstats
这里特别说一下tpstats的输出。它把Cassandra内部的请求处理线程池逐个列出来,如果某个池的blocked列持续大于零,说明请求产生速度已经超过了处理能力,读写延迟飙升、内部消息被丢弃往往就是从这里开始的。把tpstats和compactionstats结合起来看,基本能判断出节点是不是被磁盘IO拖垮的。
三、开启远程JMX访问并配置密码认证
Cassandra默认只允许本机连接JMX,而生产环境的监控程序通常跑在别的机器上,所以第一步要打开远程访问。修改conf/cassandra-env.sh,把JMX相关参数配置起来:
# 编辑conf/cassandra-env.sh,添加或调整以下配置 JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.port=7199" JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.rmi.port=7199" JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.authenticate=true" JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.password.file=/etc/cassandra/jmxremote.password" JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.access.file=/etc/cassandra/jmxremote.access" JVM_OPTS="$JVM_OPTS -Dcom.sun.management.jmxremote.ssl=false"
把authenticate设为true之后,还需要准备两个文件。access文件定义用户和权限级别,password文件存放用户名和密码,内容如下:
# /etc/cassandra/jmxremote.access:用户名 + 权限 monitorUser readonly adminUser readwrite # /etc/cassandra/jmxremote.password:用户名 + 密码 monitorUser Str0ngJmxPass adminUser Adm1nJmxPass
# JMX强制要求密码文件仅属主可读,权限不对时Cassandra会启动失败 chmod 400 /etc/cassandra/jmxremote.password chown cassandra:cassandra /etc/cassandra/jmxremote.password
改完重启节点生效。安全上有两点必须强调:一是7199端口绝不能暴露到公网,JMX能触发的操作远不止读指标,readwrite权限的用户甚至可以远程触发GC、修改运行参数,一旦被恶意连接后果很严重,务必用防火墙把端口限制在监控机和运维跳板机的来源IP范围内;二是如果内网环境不可信,建议把jmxremote.ssl打开并配置证书,代价是客户端连接会麻烦一些。另外把RMI端口固定成和JMX端口一致,能省去防火墙放行随机端口的麻烦。
四、基于Prometheus JMX Exporter搭建持续采集方案
手动查询适合排查,长期监控需要的是不间断的采集和存储,这也是生产环境的主流做法:用Prometheus的JMX Exporter把JMX指标转换成HTTP接口暴露出来,再由Prometheus定时抓取。JMX Exporter由Prometheus官方维护,部署在Cassandra节点本机,通过本地回环地址连JMX,不需要走网络,前面配置的认证信息对它同样适用。
核心工作是编写一份YAML规则文件,告诉Exporter如何把MBean名称映射成Prometheus的指标名。Cassandra的MBean数量庞大,全量抓取会产生几千个时间序列,一般建议按需过滤,下面是一份针对核心指标的参考配置:
hostPort: 127.0.0.1:7199
ssl: false
rules:
- pattern: 'org.apache.cassandra.metrics<type=(\w+), name=(\w+), scope=(\S*)><>Count: (\d+)'
name: cassandra_$1_$2_count
value: $4
labels:
scope: "$3"
type: COUNTER
- pattern: 'org.apache.cassandra.metrics<type=(\w+), name=(\w+), scope=(\S*)><>Value: (\d+)'
name: cassandra_$1_$2_value
value: $4
labels:
scope: "$3"
type: GAUGE
- pattern: 'org.apache.cassandra.metrics<type=(\w+), name=(\w+), scope=(\S*)><>(Mean|95th|99th)Percentile: (\d+)'
name: cassandra_$1_$2_percentile
value: $5
labels:
scope: "$3"
quantile: "$4"
type: GAUGE
规则里的正则把MBean名称中的type、name、scope捕获出来,拼成新的指标名并打上标签。注意规则中出现的反斜杠属于正则语法的一部分,比如\w匹配字母数字下划线、\d匹配数字、\S匹配非空白字符,编辑配置文件时不要误删。配置完成后启动Exporter并验证输出:
# 7070是Exporter对外暴露指标的HTTP端口 java -jar jmx_prometheus_httpserver-0.20.0.jar 7070 cassandra_metrics.yaml & # 验证指标输出是否正常 curl -s http://127.0.0.1:7070/metrics | grep cassandra_table_writ
然后在Prometheus的配置里把集群节点加入抓取目标:
scrape_configs:
- job_name: cassandra
scrape_interval: 15s
static_configs:
- targets:
- 192.168.1.10:7070
- 192.168.1.11:7070
- 192.168.1.12:7070
抓取间隔建议设在15秒左右,太稀疏会丢失毛刺,太密集会给Prometheus存储带来压力。数据进Prometheus之后,用Grafana做可视化,社区有维护得相当不错的Cassandra dashboard模板,导入后把数据源指向自己的Prometheus就能直接用,读写延迟、Compaction队列、缓存命中率这些面板都是现成的,省去了从零画图的功夫。
五、生产环境最值得盯的核心指标与告警设置
Exporter抓上来的指标有几百个,真正需要人盯着的其实就下面这一小撮,建议把它们做成Grafana固定面板并配置告警规则:
| 指标 | MBean位置 | 异常信号 |
|---|---|---|
| 读写延迟P99 | type=Table, name=ReadLatency / WriteLatency | P99持续超过业务SLA |
| PendingCompactions | type=Compaction, name=PendingTasks | 数值持续攀升说明磁盘IO跟不上写入 |
| LiveSSTableCount | type=Table, name=LiveSSTableCount | 单表超过几十说明压缩明显落后 |
| 堆内存使用率 | java.lang:type=Memory | 长期高于75%需要关注GC压力 |
| DroppedMessages | type=MessagingService | 非零即说明节点已经过载 |
| 线程池blocked数 | type=ThreadPools | blocked持续大于零是过载前兆 |
几个指标的解读思路值得展开说。PendingCompactions反映的是等待执行的压缩任务数,正常情况下它会在几十以内波动,如果持续上升且降不下来,说明写入速度超过了磁盘的压缩能力,放任下去读放大会越来越严重,最终拖垮读延迟。LiveSSTableCount直接对应读放大,一次读请求需要合并的SSTable越多越慢,LSM树对写入的优化本质上就是拿这个做的交换。DroppedMessages则是最直接的过载信号,节点忙到一定程度会主动丢弃内部消息,一旦出现就意味着读写一致性已经受影响,必须立刻处理。
告警阈值的设置建议遵循两个原则。第一,延迟类指标用分位数加持续时间做条件,比如P99写延迟连续5分钟高于10毫秒才告警,单次毛刺不触发,能有效减少无效告警;第二,趋势比绝对值更有价值,PendingCompactions从10涨到80比它停在80更值得警惕,前者说明问题正在恶化。另外别忘了给JMX Exporter本身和Prometheus抓取任务配置存活告警,采集链路断了而没人知道,比没有监控更危险。
Cassandra监控JMX指标Prometheus修改时间:2026-09-26 15:31:11