☰
ThingsBoard 物联网平台 Cassandra 集群搭建与调优实战
2026/11/3 16:33:55 网站建设 项目流程

物联网平台的数据量一旦上来,单机数据库很快就会成为整个系统的瓶颈。ThingsBoard 作为一款主流的开源物联网平台,默认支持多种时序数据库后端,其中 Cassandra 凭借其分布式、高可用的特性,在设备数量多、数据写入频繁的场景下表现相当能打。但很多人卡在第一步——集群怎么搭、参数怎么调、和 ThingsBoard 怎么对接。这篇内容就是围绕 Cassandra 集群搭建这件事,把我在实际项目中反复验证过的思路、步骤和踩坑经验完整摊开来讲。

不管你是刚接触 ThingsBoard 的后端开发,还是正在为设备数据量增长做架构升级的运维人员,下面这些内容都能直接拿去参考。我会从整体设计思路讲起,再到每个节点的配置细节、集群验证方法、和 ThingsBoard 的对接要点,最后把常见故障的排查路径整理成速查表。全程说人话,关键参数给出计算依据,能抄作业的地方绝不含糊。

1. 集群整体设计与方案选型思路

1.1 为什么 ThingsBoard 场景下优先考虑 Cassandra

ThingsBoard 的核心数据流是设备遥测数据(telemetry)的持续写入和按时间范围的查询读取。这类负载有几个鲜明特点:写入量远大于读取量、数据按时间追加、查询几乎都带时间窗口和设备 ID 过滤。关系型数据库在这种场景下,随着数据表膨胀,索引维护成本会急剧上升,写入延迟越来越难看。

Cassandra 的存储引擎基于 LSM-Tree,写入是先写内存表再顺序刷盘,天然适合高吞吐的追加写。它的分区键设计让数据按设备维度分散到不同节点,查询时能精准定位到目标节点,避免了全表扫描。再加上多副本机制,单节点故障不影响整体可用性,这对物联网平台要求的 7×24 运行非常关键。

我对比过几种常见方案:单机 PostgreSQL 在中小规模够用,但横向扩展麻烦;时序数据库专用方案查询灵活但运维复杂度高;Cassandra 在写入吞吐、线性扩展、运维成熟度之间取得了比较好的平衡,也是 ThingsBoard 官方文档中明确推荐的规模化部署选项。所以如果你的设备量预期在数千台以上、数据保留周期较长,Cassandra 集群是值得投入的方向。

1.2 集群规模与副本策略的确定逻辑

集群规模不是拍脑袋定的。核心约束是副本因子(Replication Factor)和一致性级别(Consistency Level)的配合。ThingsBoard 默认的 keyspace 副本因子通常是 1,但在生产集群里我建议至少设为 3,这样允许一个节点宕机而不丢数据。

副本因子为 3 意味着每份数据存三份,那么集群节点数至少要是 3 的倍数才能均匀分布。实际项目中,我一般从 3 节点起步做验证,生产环境根据数据量扩到 6 节点或更多。节点数增加带来的是写入并行度和存储容量的线性提升,但也要注意:节点越多,gossip 通信和修复(repair)的开销越大,不是无脑堆节点就好。

一致性级别方面,写入用 QUORUM(多数派,3 副本时即 2 个节点确认),读取也用 QUORUM,这样能保证强一致性下的可用性平衡。如果业务对读取延迟极度敏感、能容忍短暂的数据不一致,读可以降到 ONE,但我不建议在设备告警这类场景这么做。

提示:副本因子一旦设定,后续修改需要执行 keyspace 的 ALTER 语句并对已有数据做修复,操作成本不低。所以规划阶段就要把副本因子和节点数一起定好。

1.3 节点角色划分与网络规划

Cassandra 集群里所有节点是对等的(peer-to-peer),没有主从之分,但实际部署时我会根据物理资源做一点角色倾斜:把种子节点(seed node)部署在配置更稳定的机器上,种子节点负责新节点加入时的 gossip 引导,虽然它不承担额外的数据职责,但稳定性很重要。

网络规划上,Cassandra 有两个关键端口:9042是客户端连接端口(CQL native transport),7000是节点间通信端口(internode)。如果启用了 SSL,节点间通信走7001。规划防火墙规则时,这两个端口必须在集群内网互通,客户端只需要能访问 9042。

我习惯给每个节点分配固定的内网 IP,并在/etc/hosts里做好主机名映射。Cassandra 的配置里大量使用主机名而非 IP,提前把解析做对能省掉后面一堆莫名其妙的连接失败问题。另外,listen_address填本机内网 IP,rpc_address填0.0.0.0让客户端能从任意网卡接入,broadcast_rpc_address填本机内网 IP 供其他节点回连,这三个地址的配置是最容易出错的地方,后面实操部分会详细展开。

2. 环境准备与核心配置细节拆解

2.1 操作系统层面的前置调优

Cassandra 对系统资源比较敏感,装之前有几项系统参数必须先调,否则跑起来各种诡异问题。首先是文件句柄数,Cassandra 会打开大量文件描述符,ulimit -n建议设到 65536 以上。修改/etc/security/limits.conf,给运行 Cassandra 的用户加上nofile 65536和memlock unlimited。

其次是虚拟内存映射数量,vm.max_map_count默认值往往不够,需要调到 1048575。执行sysctl -w vm.max_map_count=1048575并写入/etc/sysctl.conf持久化。还有 swap,Cassandra 极度不建议使用 swap,因为 GC 停顿叠加 swap 会导致节点假死。用swapoff -a关掉,并在/etc/fstab里注释掉 swap 行。

时钟同步也不能忽视。Cassandra 用时间戳做数据版本判断,节点间时钟偏差过大会导致写入被误判为过期而丢弃。部署 NTP 或 chrony 保证所有节点时间偏差在毫秒级。这几项做完,系统层面的地基才算打牢。

2.2 JVM 堆内存与 GC 参数的选择

Cassandra 跑在 JVM 上,堆内存设置直接影响性能和稳定性。经验法则是:堆内存不超过物理内存的一半,且最大不超过 8GB 到 16GB 区间(具体看版本和负载)。堆太大反而会让 GC 停顿变长。我一般给 16GB 内存的机器配 8GB 堆,通过cassandra-env.sh里的MAX_HEAP_SIZE和HEAP_NEWSIZE控制。

GC 选择上,较新版本的 Cassandra 默认用 G1,老版本用 CMS。G1 在大堆下停顿更可控,我倾向保持默认的 G1 配置,但会把MaxGCPauseMillis调到 500 左右,让它在吞吐和延迟间取平衡。如果你发现 GC 日志里 Full GC 频繁,先别急着调 GC 参数,多半是堆给太大或者数据模型有问题,从根上找原因比调参有效。

注意:cassandra-env.sh里的堆设置如果和系统内存不匹配,启动时可能直接报错退出。改完参数记得看启动日志里的内存计算输出,确认生效。

2.3 cassandra.yaml 关键参数逐项说明

cassandra.yaml是整个集群配置的核心,我挑几个必须改的项说清楚。cluster_name所有节点必须完全一致,这是集群识别的依据,写错一个字符节点就加不进去。num_tokens决定每个节点的虚拟节点(vnode)数量,默认 256,新版本推荐用固定的分配算法,我一般保持 256,节点少的时候分布更均匀。

seed_provider里的 seeds 列表填种子节点 IP,注意每个节点都要配,且列表内容一致。endpoint_snitch选GossipingPropertyFileSnitch,配合cassandra-rackdc.properties文件声明机架和机房信息,这样副本能跨机架分布,提升容灾能力。commitlog_sync默认 periodic 即可,对写入性能友好;如果对数据持久性要求极高可以改 batch,但吞吐会下降明显。

concurrent_writes和concurrent_reads影响并发处理能力,默认值在 SSD 环境下偏保守,可以按 CPU 核数的 8 倍左右调整。memtable_flush_writers在写入密集场景可以适当调大。这些参数没有万能值,我建议先用默认跑起来,观察监控指标再针对性调优,一上来就大改反而容易引入不稳定因素。

3. 集群搭建实操全流程

3.1 单节点安装与基础配置落地

假设三台机器,主机名分别是 node1、node2、node3,内网 IP 分别为 10.0.0.11、10.0.0.12、10.0.0.13。先在三台上都完成系统调优,然后安装 Cassandra。以常见的 tar 包方式为例,解压到/opt/cassandra,创建数据目录/data/cassandra/data和提交日志目录/data/cassandra/commitlog,确保运行用户有读写权限。

修改conf/cassandra.yaml,三台机器的公共配置如下:

cluster_name: 'tb_cluster' num_tokens: 256 seed_provider: - class_name: org.apache.cassandra.locator.SimpleSeedProvider parameters: - seeds: "10.0.0.11,10.0.0.12" data_file_directories: - /data/cassandra/data commitlog_directory: /data/cassandra/commitlog endpoint_snitch: GossipingPropertyFileSnitch listen_address: 10.0.0.11 rpc_address: 0.0.0.0 broadcast_rpc_address: 10.0.0.11

注意listen_address和broadcast_rpc_address每台机器填自己的 IP,seeds 列表三台保持一致(我习惯填前两台做种子,第三台不参与种子引导,减少种子节点压力)。然后在conf/cassandra-rackdc.properties里声明dc=dc1和rack=rack1(三台可以分到不同 rack 模拟跨机架)。

3.2 逐节点启动与集群握手验证

配置改完后,先启动 node1。用bin/cassandra -f前台启动能看到完整日志,确认没有报错后再用后台方式跑。启动后执行bin/nodetool status,应该能看到 node1 状态为 UN(Up/Normal)。这时候集群只有一个节点,属于正常。

接着启动 node2,观察 node2 的日志里出现Starting listening for CQL clients以及 gossip 握手成功的记录。回到 node1 执行nodetool status,应该能看到两个节点都是 UN。同样方式启动 node3。三台都起来后,nodetool status的输出里三个节点全部 UN,且 Load 和 Tokens 列有合理分布,集群就算搭起来了。

这里有个细节:如果某个节点状态显示 UJ(Up/Joining)长时间不变,说明它还在流式传输数据(bootstrap),节点少数据少时很快,数据多时可能要等。如果显示 DN(Down/Normal),检查网络连通性和listen_address配置。我遇到过节点间 7000 端口被防火墙拦了导致一直 DN 的情况,排查时先用telnet或nc测端口通不通。

3.3 创建 ThingsBoard 专用 keyspace 与表结构

集群就绪后,用cqlsh连上去创建 ThingsBoard 需要的 keyspace。ThingsBoard 的 schema 文件通常在源码的dao/src/main/resources/cassandra/目录下,但更省事的做法是让 ThingsBoard 首次启动时自动建表。不过自动建表的副本因子是 1,生产环境我建议手动建 keyspace 再让应用建表。

手动创建语句如下:

CREATE KEYSPACE thingsboard WITH replication = { 'class': 'NetworkTopologyStrategy', 'dc1': 3 } AND durable_writes = true;

NetworkTopologyStrategy配合前面配的 snitch,能让副本按机架分布。dc1对应cassandra-rackdc.properties里的 dc 名,副本数 3 表示这个数据中心存三份。建好后可以用DESCRIBE KEYSPACE thingsboard;确认配置生效。

提示:如果 ThingsBoard 已经用默认配置启动过并建了副本因子为 1 的 keyspace,需要先ALTER KEYSPACE thingsboard WITH replication = {'class':'NetworkTopologyStrategy','dc1':3};再执行nodetool repair让数据重新分布,否则副本数不会真正生效。

3.4 ThingsBoard 侧对接配置与验证

ThingsBoard 的配置文件thingsboard.yml里,数据库相关配置段需要指向 Cassandra。关键配置项包括database.ts.type设为cassandra,cassandra.url填集群节点地址(多个用逗号分隔),cassandra.keyspace填thingsboard,cassandra.local_datacenter填dc1。连接池大小cassandra.pool.max_requests_per_host根据并发量调整,初期保持默认即可。

配置改完重启 ThingsBoard 服务,观察启动日志里是否有 Cassandra 连接成功的记录。然后通过平台界面或 API 创建设备、上报一条遥测数据,再回到cqlsh里查对应的表(通常是ts_kv系列表),确认数据写进去了。这一步验证通过,说明整条链路打通。

我实际对接时踩过一个坑:ThingsBoard 启动时报NoHostAvailableException,但nodetool status显示集群正常。排查发现是broadcast_rpc_address没配对,节点间能通信但客户端回连地址错了。把每台的broadcast_rpc_address改成各自内网 IP 后解决。所以这个参数千万别图省事全填一样。

4. 常见故障排查与运维经验实录

4.1 节点无法加入集群的排查路径

节点加不进去是最常见的问题,排查要按层次来。先看网络层:ping通不通、7000 端口通不通。再看配置层:cluster_name是否完全一致、seeds 列表是否可达、listen_address是否填了本机真实 IP。最后看日志层:system.log里搜gossip和handshake关键字,通常会有明确原因。

我整理了一张速查表,覆盖几种典型现象:

现象可能原因排查动作
节点状态一直 DN7000 端口不通或 listen_address 错误测端口连通性,核对 IP 配置
节点状态 UJ 卡住bootstrap 数据量大或磁盘慢看日志流式传输进度,检查磁盘 IO
启动即退出堆内存设置超限或目录权限不足看启动日志首屏报错,核对目录属主
客户端连不上rpc_address 或 broadcast_rpc_address 错误核对两个地址配置,测 9042 端口

这张表我在项目里反复用到,大部分问题对着查就能定位。补充一个经验:改完配置一定要重启对应节点,Cassandra 不会热加载cassandra.yaml的大部分参数,改完不重启等于没改。

4.2 数据分布不均与热点问题处理

集群跑一段时间后,如果发现某些节点 Load 明显高于其他节点,说明数据分布不均。原因可能是num_tokens设置不合理,或者写入时分区键设计有问题导致热点。先用nodetool status看各节点 Load,再用nodetool ring看 token 分布。

如果是 token 分布问题,可以执行nodetool cleanup清理不再属于该节点的数据,或者用nodetool repair做一致性修复。如果是分区键热点,那要从数据模型层面解决——ThingsBoard 的表结构已经按设备 ID 做了分区,一般不会有大问题,但如果自定义表用了低基数的分区键,就要重新设计。

注意:nodetool repair是重操作,会消耗大量 IO 和网络带宽,生产环境要避开业务高峰,并且按 keyspace 或表粒度分批执行,别一次性全量跑。

4.3 日常运维的关键动作清单

集群稳定运行后,日常运维有几件事必须定期做。第一是监控,重点看各节点的写入延迟、GC 停顿、磁盘使用率和节点状态,这些指标异常往往是故障前兆。第二是定期 repair,保证副本间数据一致,频率根据数据写入量定,一般每周到每月一次。第三是快照备份,用nodetool snapshot做,配合外部存储保留。

还有一件容易被忽略的事:commitlog目录的磁盘空间要留足,写入高峰期 commitlog 增长很快,空间不足会导致节点停止接受写入。我一般给 commitlog 单独挂盘,容量按数据目录的 10% 到 20% 预留。另外,Cassandra 版本升级要谨慎,跨大版本升级前先在测试集群验证,升级顺序是先升种子节点再升普通节点,每次升一个,确认状态正常再继续。

5. 性能调优与扩展性考量

5.1 读写路径上的关键调优点

Cassandra 的写入路径经过 commitlog、memtable、SSTable 三层,调优要顺着这条链路找瓶颈。如果写入延迟高,先看 commitlog 所在磁盘的 IO 是否饱和,commitlog 和 data 目录分盘能显著改善。再看 memtable 的 flush 频率,memtable_flush_writers和memtable_heap_space相关参数可以适当调大,减少频繁刷盘。

读取路径上,SSTable 数量过多会拖慢查询,因为要合并多个文件。定期做 compaction 能控制 SSTable 数量,nodetool compact可以手动触发,但更推荐依赖后台自动 compaction 策略。如果读延迟敏感,可以调整concurrent_reads和缓存相关参数,但前提是先把数据模型和索引设计对,参数调优只是锦上添花。

5.2 横向扩展的时机与操作要点

什么时候该加节点?看两个信号:单节点磁盘使用率超过 60% 到 70%,或者写入延迟持续上升且调优无效。加节点的操作是:在新机器上完成系统调优和 Cassandra 安装,配置cassandra.yaml时 seeds 填现有集群的种子节点,listen_address填新节点 IP,然后启动。新节点会自动 bootstrap,从现有节点流式拉取它负责的那部分数据。

bootstrap 期间集群会有额外负载,建议在业务低峰期操作,并且用nodetool bootstrap相关参数控制流式传输速率,避免把现有节点压垮。新节点状态变成 UN 后,执行一次nodetool cleanup清理旧数据,再对相关 keyspace 做 repair,扩展才算完成。整个过程我一般预留几个小时,数据量大的话可能更久。

5.3 与 ThingsBoard 负载特征的匹配建议

ThingsBoard 的写入模式是持续高频的小批量写入,读取多是按时间范围的范围查询。针对这个特征,我在调优时会重点关注写入侧的 commitlog 和 memtable 配置,以及读取侧的时间窗口查询效率。ThingsBoard 的ts_kv表按设备 ID 和时间分区,查询时带上时间范围能有效减少扫描量,所以应用层的查询写法也会影响数据库压力。

另外,ThingsBoard 有数据保留策略(TTL),过期数据会自动清理。Cassandra 的 TTL 删除是标记删除,实际空间回收依赖 compaction,所以 compaction 策略要和 TTL 周期配合好,避免大量墓碑(tombstone)堆积拖慢查询。如果发现查询变慢且墓碑比例高,可以调整gc_grace_seconds和 compaction 策略,但改之前要理解清楚副作用,别盲目调。

6. 写在最后的几点实操体会

这套集群我在几个项目里反复搭过,最大的感受是:配置的准确性比参数调优重要得多。大部分启动失败、节点加不进去的问题,根源都是cassandra.yaml里某个地址填错、cluster_name不一致、或者防火墙端口没开。把基础配置做对,集群跑起来其实很顺。

另一个体会是别急着上生产就调一堆参数。先用默认配置把集群跑稳,观察监控数据,找到真正的瓶颈再针对性调整。我见过不少项目一上来就把并发数、堆内存、缓存全调大,结果引入新的不稳定因素,反而更难排查。Cassandra 的默认值经过大量生产验证,大多数场景下够用。

最后分享一个小技巧:搭建阶段养成看日志的习惯,system.log里记录了节点启动、gossip 握手、流式传输的全过程,出问题时第一时间翻日志比瞎猜快得多。日志级别可以在logback.xml里按需调整,排查特定问题时临时调成 DEBUG,定位完再改回来,避免日志量过大影响性能。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询