1. 为什么要在 ThingsBoard 场景下认真对待 Cassandra 集群
ThingsBoard 这类物联网平台有个很现实的特点:设备数量一上来,时序数据的写入压力不是线性增长,而是成倍地往上翻。单机 Cassandra 在实验室里跑几百台模拟设备毫无压力,但一旦接入几千上万台设备、每秒产生数万条遥测记录,单节点的磁盘 IO 和内存就会迅速成为瓶颈。我最初也是抱着"先单机跑通再说"的心态,结果在压测阶段直接被打脸——写入延迟从个位数毫秒飙到几百毫秒,查询开始超时,整个仪表盘卡得像幻灯片。
所以这篇内容的核心,就是把 ThingsBoard 依赖的 Cassandra 存储层从单机扩展到集群的完整过程讲透。Cassandra 集群搭建这件事,表面上是装几个节点、改几个配置文件,实际上涉及一致性哈希、副本策略、种子节点、机架感知、读写一致性级别等一系列需要提前想清楚的设计决策。我会围绕 ThingsBoard 的实际使用场景,把每个参数为什么这么设、踩过哪些坑、怎么验证集群真的健康,一条条拆开讲。
适合谁来读?如果你已经能在单机上把 ThingsBoard 跑起来,但对"数据量大了怎么办"没有把握,这篇就是给你准备的。如果你是完全的 Cassandra 新手,也没关系,我会在关键概念上做生活化解释,保证你能跟着操作。整篇内容基于常见的三节点集群实践展开,你可以按同样思路扩展到五节点、七节点,逻辑是通用的。
需要先说明一点:下面涉及的所有节点地址、主机名、目录路径都是示例值,实际部署时请替换成你自己的环境信息。我刻意用了通用的占位写法,避免任何具体环境绑定,你照着改就行。
2. 集群设计前的关键决策拆解
2.1 节点数量与副本策略怎么定
很多人一上来就问"搭几个节点合适",这个问题其实要倒过来想:先确定副本数,再定节点数。Cassandra 的数据可靠性靠副本(replication factor,简称 RF)保证,RF=3 意味着每份数据存三份。ThingsBoard 官方推荐的生产配置通常是 RF=3,因为这样能容忍一个节点宕机而不丢数据、不停服。
节点数和 RF 的关系有个经验法则:节点数至少等于 RF,最好大于 RF。三节点配 RF=3 是最小可用生产集群,但此时任何一个节点故障,虽然数据不丢,写操作的一致性余量已经很紧张。如果预算允许,我一般建议至少四个节点起步,或者干脆按"RF=3 + 每机架至少一个副本"来做机架感知部署。
这里有个容易被忽略的点:副本不是越多越好。RF 从 3 提到 5,写入放大和存储成本都显著上升,而容错能力的边际收益递减。对大多数中等规模的物联网场景,RF=3 是性价比最高的选择。我见过有团队为了"保险"直接上 RF=5,结果磁盘半年就满了,扩容成本远超预期。
2.2 一致性级别与 ThingsBoard 的读写模式
Cassandra 的一致性级别(Consistency Level,CL)是集群调优的核心旋钮。写一致性常见的有 ONE、QUORUM、ALL,读一致性同理。QUORUM 的定义是"多数派",三副本下 QUORUM 等于 2。
ThingsBoard 的写入是高频的遥测数据,读取则集中在仪表盘查询和历史回溯。我的实践结论是:写入用 QUORUM,读取用 ONE 或 LOCAL_QUORUM。写入用 QUORUM 能保证数据落到多数副本,避免单点故障时丢最新数据;读取用 ONE 则优先保证查询响应速度,因为遥测数据即使读到稍旧的副本,对展示影响也不大。
这里要解释一个原理:Cassandra 满足W + R > RF时能保证强一致性。三副本下 W=2、R=2 就是强一致,但读性能会打折。ThingsBoard 的场景里,我倾向于牺牲一点读的强一致性换取吞吐,因为设备遥测本身就有时间戳,业务上对"绝对最新"的容忍度较高。这个取舍你要根据自己的业务来定,如果是计费类、告警类数据,那就老老实实 QUORUM 读写。
2.3 种子节点与机架感知的规划
种子节点(seed node)的作用是让新节点加入集群时知道"找谁握手"。它不是主节点,不承担特殊的数据职责,只是集群发现的入口。常见误区是把所有节点都设成种子——这会让 gossip 协议的开销无谓增大。我的做法是:三节点集群设两个种子,五节点以上设三个种子,且种子节点尽量分散在不同机架。
机架感知(rack awareness)在单机房部署时经常被忽略,但只要你的节点分布在不同的物理机柜、不同的可用区,就必须配置。原理是 Cassandra 会尽量把同一份数据的副本放在不同机架,这样单个机柜断电不会导致数据不可用。配置方式是通过cassandra-rackdc.properties文件指定 dc 和 rack,后面实操部分会给出具体写法。
注意:机架感知一旦在集群初始化时没配好,后期再改需要做数据迁移,代价很大。所以第一次搭建时就要想清楚拓扑,别图省事全填默认值。
3. 环境准备与系统层调优
3.1 基础环境清单与依赖确认
在动手装 Cassandra 之前,系统层有几件事必须先做扎实,否则后面会出现各种"莫名其妙"的问题。我整理了一份准备清单,按顺序执行:
- 操作系统:主流 Linux 发行版均可,内核版本建议 4.x 以上,确保支持较新的文件系统和网络特性。
- JDK:Cassandra 4.x 需要 JDK 11,3.x 用 JDK 8。版本不匹配会直接启动失败,这个坑我踩过,报错信息还很隐晦。
- 时间同步:所有节点必须做 NTP 时间同步。Cassandra 用时间戳解决冲突,节点间时钟偏差过大会导致数据覆盖逻辑出错。
- 主机名与 hosts:每个节点配置固定主机名,并在
/etc/hosts里互相解析。别依赖 DNS,集群内部通信用 hosts 解析更稳。 - 防火墙:开放 7000(节点间 gossip)、7001(TLS 时用)、9042(客户端 CQL)、7199(JMX 监控)等端口。
时间同步这条我要多强调一句。曾经有个环境因为某台机器时钟慢了十几秒,导致写入的数据时间戳乱序,查询结果出现"数据明明写了却查不到"的诡异现象,排查了大半天才定位到时钟问题。所以chronyd或ntpd一定要配好,并且验证所有节点时间差在毫秒级。
3.2 系统参数调优:别让默认值拖后腿
Cassandra 对系统资源很敏感,默认的 Linux 参数往往不够用。下面这几项是我每次部署必调的:
# 提高文件描述符上限 echo "cassandra soft nofile 100000" >> /etc/security/limits.conf echo "cassandra hard nofile 100000" >> /etc/security/limits.conf # 调整虚拟内存映射数量,Cassandra 大量使用 mmap sysctl -w vm.max_map_count=1048575 # 关闭 swap,Cassandra 极度讨厌 swap 导致的 GC 停顿 swapoff -a为什么要关 swap?因为 Cassandra 的 JVM 堆和页缓存对延迟极其敏感,一旦发生 swap,读写延迟会从毫秒级跳到秒级,整个集群的响应都会受影响。vm.max_map_count这个参数很多人不知道,但 Cassandra 的 SSTable 读取依赖内存映射文件,默认值 65530 在高负载下会不够用,报 "Map failed" 错误。
还有vm.swappiness建议设为 1,net.core.somaxconn和net.ipv4.tcp_max_syn_backlog适当调大以应对高并发连接。这些参数写进/etc/sysctl.conf持久化,别用临时命令,否则重启就丢了。
3.3 磁盘与目录规划
Cassandra 的数据目录规划直接影响后期运维。我的建议是数据目录、提交日志目录、缓存目录分开,如果有多块盘,提交日志(commitlog)单独放一块 SSD,能显著降低写入延迟。
目录结构示例:
/data/cassandra/data # SSTable 数据 /data/cassandra/commitlog # 提交日志 /data/cassandra/saved_caches # 缓存 /data/cassandra/hints # 提示文件提交日志为什么单独放?因为它是顺序写、高频写,和数据文件的随机读会争抢 IO。分开之后,写入路径的延迟更稳定。如果只有一块盘,那至少把目录分开放,方便后期监控和迁移。
4. 集群搭建的完整实操流程
4.1 安装 Cassandra 与目录初始化
我习惯用官方二进制包安装,比包管理器更可控。以 Cassandra 4.x 为例,下载解压后放到/opt/cassandra,然后创建专用用户运行,别用 root 直接跑。
# 创建用户和目录 useradd -r -s /sbin/nologin cassandra mkdir -p /data/cassandra/{data,commitlog,saved_caches,hints} chown -R cassandra:cassandra /data/cassandra /opt/cassandra用专用用户运行是安全基线,同时也避免权限混乱。目录权限一定要提前设好,我见过因为 commitlog 目录属主不对导致节点起不来的情况,日志里只报权限拒绝,不够直观。
4.2 cassandra.yaml 核心参数逐项配置
conf/cassandra.yaml是集群配置的核心,下面这些参数必须逐个确认,我按重要性排序说明:
| 参数 | 示例值 | 作用与设置理由 |
|---|---|---|
| cluster_name | tb_cluster | 集群名,所有节点必须完全一致,否则无法组成集群 |
| num_tokens | 16 | 每节点虚拟节点数,4.x 默认 16,比旧版 256 更均衡且运维简单 |
| seeds | 10.0.0.1,10.0.0.2 | 种子节点列表,所有节点配置相同 |
| listen_address | 本机 IP | 节点间通信地址,必须填本机真实 IP |
| rpc_address | 0.0.0.0 | 客户端连接监听地址 |
| endpoint_snitch | GossipingPropertyFileSnitch | 机架感知 snitch,配合 rackdc 文件使用 |
| data_file_directories | /data/cassandra/data | 数据目录 |
| commitlog_directory | /data/cassandra/commitlog | 提交日志目录 |
| concurrent_writes | 64 | 并发写线程数,按 CPU 核数调整 |
| concurrent_reads | 32 | 并发读线程数 |
num_tokens这个参数值得多说两句。老版本默认 256 个 vnode,好处是数据分布均匀,坏处是节点加入/离开时的流式传输开销大、运维复杂。4.x 改成默认 16,配合改进的分配算法,均衡性已经够用,我实测下来新节点加入时的数据迁移量明显更可控。如果你是从 3.x 迁移过来,别盲目沿用 256。
endpoint_snitch选GossipingPropertyFileSnitch是为了支持机架感知,它读取cassandra-rackdc.properties里的 dc 和 rack 信息。如果只是单机架测试环境,用SimpleSnitch也行,但生产环境强烈建议用前者。
4.3 机架感知文件与 JVM 参数调整
conf/cassandra-rackdc.properties的配置示例:
dc=dc1 rack=rack1每个节点根据自己的物理位置填不同的 rack,比如 rack1、rack2、rack3。dc 在单数据中心场景下保持一致即可。
JVM 参数在conf/jvm.options里调。堆大小是重点,经验值是堆内存不超过 8GB,且不超过物理内存的一半。为什么封顶 8GB?因为堆越大,GC 停顿越长,而 Cassandra 更依赖操作系统的页缓存来加速读。堆设太大反而会让 GC 成为瓶颈。我一般设 4GB 到 8GB,剩下的内存留给页缓存。
-Xms8G -Xmx8G # 使用 G1 GC -XX:+UseG1GCG1 GC 在 4.x 是默认推荐,停顿更可控。如果你的堆小于 4GB,用 CMS 也可以,但新版本已经逐步淘汰 CMS,建议统一用 G1。
4.4 启动顺序与集群握手验证
配置改完后,先启动种子节点,等它完全起来再启动其他节点。这个顺序很重要,如果所有节点同时启动,gossip 握手可能混乱,出现节点互相看不到的情况。
启动命令:
su - cassandra -c "/opt/cassandra/bin/cassandra -f"-f是前台运行,方便看日志。生产环境用后台方式,日志重定向到文件。启动后观察日志里有没有 "Starting listening for CQL clients" 和 "Node ... is now part of the cluster" 这类信息。
验证集群状态用nodetool status:
/opt/cassandra/bin/nodetool status正常输出里每个节点前面应该是UN,U 表示 Up,N 表示 Normal。如果看到UJ(Joining)、DN(Down Normal)或者节点缺失,说明握手有问题,需要查日志。我遇到过节点显示DN的情况,最后发现是种子节点列表里写了一个不存在的地址,导致 gossip 一直重试。
5. ThingsBoard 侧的关键配置对接
5.1 创建 Keyspace 与副本策略设置
集群起来后,要在 Cassandra 里为 ThingsBoard 创建 keyspace。ThingsBoard 默认用thingsboard这个 keyspace,创建时副本策略直接决定数据分布:
CREATE KEYSPACE thingsboard WITH replication = { 'class': 'NetworkTopologyStrategy', 'dc1': 3 } AND durable_writes = true;这里必须用NetworkTopologyStrategy而不是SimpleStrategy,因为前者支持机架感知,后者只认节点数不认拓扑。dc1对应你 rackdc 文件里的 dc 名,后面的 3 就是 RF。如果你有多个数据中心,每个 dc 单独指定副本数。
注意:keyspace 的副本策略创建后可以改,但改完要执行
nodetool repair让数据重新分布,数据量大时这个操作很耗时。所以第一次就设对,别反复折腾。
5.2 ThingsBoard 配置文件对接
ThingsBoard 的thingsboard.yml里,Cassandra 相关配置大致如下:
cassandra: cluster_name: "${CASSANDRA_CLUSTER_NAME:tb_cluster}" keyspace_name: "${CASSANDRA_KEYSPACE_NAME:thingsboard}" url: "${CASSANDRA_URL:10.0.0.1:9042,10.0.0.2:9042,10.0.0.3:9042}" ssl: false read_consistency_level: ONE write_consistency_level: QUORUMurl里把所有节点的 9042 端口都列上,这样客户端驱动能自动发现整个集群,某个节点挂了也不影响连接。一致性级别按前面分析的,读 ONE 写 QUORUM。如果 ThingsBoard 版本较新,配置项名称可能有细微差异,以你实际版本的官方配置模板为准。
5.3 连接池与超时参数微调
ThingsBoard 连接 Cassandra 用的是 DataStax 驱动,连接池和超时参数对稳定性影响很大。默认的连接池大小在高并发下可能不够,表现为偶发的 "No host available" 错误。可以在配置里适当调大每节点的连接数,并设置合理的请求超时。
我的经验是:先按默认跑,压测时观察连接池指标,不够再调。盲目调大连接数会加重节点负担,反而适得其反。超时时间也别设太短,Cassandra 在 compaction 期间偶有延迟抖动,超时设太紧会误判节点故障。
6. 集群健康验证与常见问题排查
6.1 用 nodetool 做全面体检
集群搭完不能只看status显示 UN 就完事,还要做几项深度检查:
# 查看节点负载和数据分布 nodetool status nodetool ring # 检查是否有节点间数据不一致 nodetool repair -pr # 查看 compaction 和 GC 情况 nodetool compactionstats nodetool gcstatsnodetool ring能看到每个节点负责的 token 范围,如果发现某个节点负责的 token 明显偏多,说明数据分布不均,可能需要调整num_tokens或重新分配。nodetool repair是保证副本一致性的关键操作,建议定期执行,尤其是节点增减之后。
6.2 常见问题速查表
我把实际运维中高频遇到的问题整理成表,方便对照排查:
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| 节点显示 DN | 进程挂了或网络不通 | 查进程、查端口、查日志 |
| 写入超时 | 一致性级别过高或节点过载 | 降 CL、查 compaction、扩容 |
| 数据分布不均 | num_tokens 设置不当 | 调整 vnode 数或重新分配 |
| 查询慢 | 页缓存不足或读 CL 过高 | 加内存、降读 CL、优化查询 |
| 节点无法加入集群 | cluster_name 不一致或种子错误 | 核对配置、检查 gossip 日志 |
| GC 停顿长 | 堆过大或 GC 策略不当 | 降堆、换 G1、调 GC 参数 |
6.3 独家避坑经验
说几个文档里不会写、但实际会遇到的坑。第一,别在集群运行中随意改 cluster_name,改了之后节点会认为自己是另一个集群的,直接分裂。第二,nodetool repair在数据量大时可能跑几个小时甚至更久,要安排在业务低峰期,并且监控它对 IO 的影响。第三,节点扩容时新节点加入会触发流式传输(streaming),这段时间集群负载会上升,最好在低峰操作。
还有一个隐蔽的坑:提交日志目录所在磁盘写满会导致节点直接拒绝写入。我建议给 commitlog 目录单独监控磁盘使用率,设个 80% 的告警阈值。Cassandra 本身有 commitlog 空间回收机制,但配置不当或写入突增时仍可能撑满。
7. 扩容与日常维护的实操建议
7.1 平滑扩容的正确姿势
业务增长需要加节点时,流程是:新节点配好cassandra.yaml(seeds 填现有种子节点),启动后它会自动加入并开始流式传输数据。关键点是一次只加一个节点,等它状态变成 UN 且 streaming 完成后再加下一个。同时加多个节点会让集群内部流量暴增,影响在线业务。
扩容后记得对新节点执行nodetool cleanup,清理不再属于它的数据副本。这个操作也耗 IO,同样安排在低峰期。
7.2 备份与快照策略
Cassandra 自带快照功能,nodetool snapshot可以快速创建某个 keyspace 的一致性快照。我的做法是每天定时快照,保留最近若干份,并定期把快照文件同步到独立存储。快照本身不占额外空间(基于硬链接),但同步出去的数据要算好容量。
恢复时用nodetool refresh或直接拷贝 SSTable 文件,具体方式取决于恢复粒度。这块内容展开又是一篇,这里只提醒一句:备份一定要定期做恢复演练,没验证过的备份等于没有备份。
7.3 监控指标重点关注项
日常监控我重点看几个指标:节点存活状态、读写延迟的 P99、compaction 积压、GC 停顿时间、磁盘使用率、连接数。其中 compaction 积压最容易被忽视,积压多了会导致读放大,查询变慢。用nodetool compactionstats能看到待处理的 compaction 任务数,持续偏高就要考虑调整 compaction 策略或扩容。
读写延迟的 P99 比平均值更有参考价值,平均值好看但 P99 飙高,说明有长尾请求,用户体验会受影响。这些指标接入监控系统后设好告警,比事后救火强得多。
8. 我在实际部署中的几点体会
回过头看,Cassandra 集群搭建最难的不是装软件改配置,而是前期把拓扑、副本、一致性这些设计决策想清楚。我最初图快,直接三节点全默认配置跑起来,结果压测时数据分布不均、读延迟抖动,返工重配花的时间比一开始认真规划多得多。
另一个体会是,ThingsBoard 和 Cassandra 的配合要当成一个整体来调。ThingsBoard 的写入模式、查询模式决定了 Cassandra 的一致性级别和 compaction 策略该怎么选,脱离业务谈 Cassandra 调优是空中楼阁。我现在的习惯是,先在测试环境用真实数据量压测,观察瓶颈在哪,再针对性调参,而不是照搬网上的"最佳实践"。
最后分享一个小技巧:搭建集群时准备一份检查清单,把每个节点的配置项、启动顺序、验证命令都列上,逐项打勾。集群节点一多,靠记忆很容易漏配某个参数,而漏配的后果往往在运行一段时间后才暴露,排查成本很高。清单化操作,是我这些年做基础设施部署最省心的习惯。