尧图精选

智能家居海量设备高并发接入:TDengine 实战与性能调优

🕒 发布时间:2026/9/16 3:47:02 📁 来源:尧图网络
做智能家居平台的朋友应该都有过这种经历设备规模从几百台涨到几万台之后以前还能凑合的 MySQL 突然就顶不住了。不是 CPU 被打满就是磁盘 IO 告警更头疼的是每到设备集中上报的时间段整个服务的写延迟一路飙红连带着大屏展示、告警判断全部变慢。我这两年折腾过不少时序数据库最后在生产环境稳定跑下来的是 TDengine。这篇文章就把我在智能家居场景下用 TDengine 处理海量并发连接的完整思路、建模方式、压测过程和踩坑记录一次性说清楚希望对正在选型或者已经被并发问题折磨的你有点帮助。1. 智能家居场景下并发连接问题到底有多“吓人”1.1 智能家居数据流的真实形态智能家居的数据上报和常见的 Web 后端请求有一个本质区别它的数据是“海量小包 突发聚集”。一套普通家庭系统里智能门锁、灯光、窗帘、温湿度传感器、烟雾报警器、扫地机器人、摄像头云台这些都是独立设备上报周期从几百毫秒到几十分钟不等。单个设备产生的数据量不大但一旦接入平台设备数量是几千、几万甚至几十万级别那么每秒进入平台的数据包就是成千上万条。更麻烦的是设备上报并不是匀速的。你想想一下晚上 8 点到 11 点的黄金时段全屋灯光、电视、空调、空气净化器集体动作或者某个小区的智能安防系统在凌晨统一执行定时巡检数据会在一瞬间同时打过来。这种秒级的“尖峰冲击”才是考验系统并发能力的真正场景。它可以表现为 TCP 连接数暴增也可以表现为写 TPS 瞬间拉满还有可能表现为大量设备同时断线重连把认证服务和接入网关都打懵。在实际项目里我曾模拟过 5 万个设备同时上线、每台设备每 10 秒上报一条状态数据的场景折算下来每秒写 TPS 接近 5000 条峰值时刻甚至能到 2 万条以上。如果算上报到平台后还需要做实时聚合、历史查询和告警判断那需要处理的连接和查询压力就远不止“写入”这一项了。1.2 连接数高不等于并发写入高这里要先把两个概念掰开高并发连接和高写入吞吐它们不是一回事。所谓“连接数高”指的是服务端同时维持的大量客户端连接比如 10 万个设备终端长期保持在线每个连接虽然活跃度不高但依然占着文件描述符、内存缓冲区和心跳资源。“高写入吞吐”则是指单位时间内产生的数据条数和写入速度比如每秒 5 万条记录入库。很多开发者一开始只关注写 TPS结果系统一秒处理几万条没问题但连接数一旦上到几万服务就崩了。这是因为不少数据库连接池和服务的处理模型是面向低连接数的每个连接都需要消耗固定内存和线程。TDengine 之所以适合智能家居是因为它对这两种压力都做了针对性设计一方面服务端连接处理比较轻量支持大量客户端同时接入另一方面通过批量写、异步提交和追加式存储能支撑极高的写入吞吐。1.3 传统关系型数据库为什么越用越难受先说一句公道话MySQL、PostgreSQL 这类关系型数据库不是不能存设备数据而是硬顶着高并发持续写入的代价太大了。关系型数据库的写入链路是围绕事务和行锁设计的每插入一条数据都要走一遍事务日志、行锁、索引更新主键索引如果还是 B 树随机插入磁盘随机写放大的问题会非常严重。当几千个设备同时上报产生的可能是每秒上万次的单行 Insert数据库光在锁竞争和日志落盘上就会消耗大量资源。水平扩展也有办法比如分库分表但智能家居数据天然带时间属性数据冷热分明用业务分片很难均匀分布而且如果你只是按设备 ID 分库查“过去 24 小时所有客厅设备平均温度”这种跨设备查询分库后就是一场灾难。时序数据库解决的正是这个问题。TDengine 看起来也支持 SQL但它的核心逻辑是按时间线每个设备一条时间线组织数据存储时天然按时间排序写入是顺序追加而不是随机插入查询也主要在时间分区内扫描。再加上它用列式存储和预聚合整个模型的匹配度比关系型数据库高了好几个量级。1.4 为什么选择 TDengine 而不是别的时序库市面上时序数据库不少InfluxDB、TimescaleDB、Prometheus 都有各自定位。我选择 TDengine 的理由有三个。第一是能用到“超级表”。智能家居数据模型高度统一一堆设备上报的温度、湿度、电量、状态字段基本一致只是设备 ID 和设备属性不同。TDengine 的超级表可以把这些数据统一管理又在物理上按设备子表分开存储写入和查询都很快这是很多其他时序库没有的建模能力。第二是存储成本和查询性能的平衡。TDengine 对真实场景做了大量压缩相同数据量占用的磁盘可能只有 MySQL 的十几分之一而且它有内建的降采样和自动分区清理不需要你手动写定时任务去删除旧数据。第三是它的接入方式符合智能家居生态。这类系统往往有多种协议网关MQTT、HTTP、Modbus 等都有TDengine 提供了 RESTful 接口、原生连接驱动和一个可选的 taosAdapter 来对接多种协议不用自己再写一层很重的数据适配服务。2. 解开 TDengine 扛住高并发连接的底层原理2.1 超级表 子表为智能家居量身定做TDengine 对智能家居场景最友好的一点就是它的“超级表STable”模型。你可以把超级表理解成一张逻辑表它定义了一堆采集量和标签字段而每个设备的实际数据存储在属于这个设备的一个“子表”中。子表继承超级表的列结构但通过标签值来区分设备属性。在实际建模时我会把设备类型、房间位置、固件版本、所属用户这些都设成标签TAG把采集到的温度、湿度、功率等指标设成列FIELD。比如创建一张设备温湿度超级表SQL 大致长这样CREATE STABLE IF NOT EXISTS device_env ( ts TIMESTAMP, temperature FLOAT, humidity FLOAT, battery INT ) TAGS ( device_id NCHAR(64), location NCHAR(64), device_type NCHAR(32) );之后每个设备接入时给设备创建一张子表标签值和真实设备属性绑定。这样做的好处是写入时可以直接定位到对应的子表也就是对应一条独立的时间线不需要全局去重、不需要跨表寻找插入位置查询“某个设备最近一小时数据”时就是单表扫描很快。要查“所有客厅设备温度”则可以直接在超级表上按标签过滤。用关系型数据库打比方这相当于你提前把数据按设备分好了桶但所有桶还共享同一个结构。数据量再大也不会出现一张表里堆上亿行、索引膨胀到查询超时的情况。2.2 时间分区和列式存储让数据天然有序TDengine 在底层存储上做了两个很关键的设计按时间窗口切分数据文件和按列存储数据块。默认情况下TDengine 会根据你建库时设置的时间跨度DURATION自动把数据切到不同的数据文件里。假设 DURATION 是 7 天那么一周的数据就是一个独立的数据文件集合查询某一天的数据时只扫描对应文件旧文件只要超过保留时间就能被自动删除。这和 MySQL 里手动做分区表是一个思路但是 TDengine 帮你全自动处理了不需要定期维护。列式存储则是“按列”而不是“按行”组织数据。对智能家居这种强指标型的数据同一列的数据值往往相似压缩率很高。我在生产环境里观察过压缩机组的功率数据压缩后磁盘占用有时候只有原始文本数据的 5% 不到。这个特性直接影响成本同样的服务器容量可以保存更久的历史数据或者支撑更多的设备接入。2.3 客户端写模型批量提交、自动合并、异步落盘高并发写入的吞吐上限很大程度上取决于写入模型。TDengine 提供的原生写入接口设计得比较务实无论你用 C、Java、Python 还是 Go 的客户端都支持“一次提交多行数据”的批量写入模式。举个例子如果每台设备每次上报只有一条记录那最忌讳的做法是收到一条就写一条。正确做法是在接入层缓存一批数据比如攒 500 条或者攒 500 毫秒再一次性批量提交。这么做的好处是显著减少了网络往返和数据库内部的事务开销。TDengine 服务端收到一批多行数据后会先做按时间排序和去重合并再顺序写入 WAL 和存储引擎。顺序写相比随机写磁盘 IO 性能差距可以超过一个数量级。另外在系统设计里要关注 WALWrite-Ahead Log级别。TDengine 提供了不同级别的 WAL 策略。如果对极端情况下的数据丢失不太敏感可以选择性能更高的模式如果每个数据点都很重要那就保留比较安全的级别。这个要根据业务需求来权衡没有标准答案。2.4 高连接数下的服务端架构取舍TDengine 服务端本身对连接的处理也做了取舍。它没有采用“每个连接必配一个线程”这种简单粗暴的模型而是尽量用事件驱动和有限的线程池来处理大量客户端连接。这样可以在一台普通服务器上同时维持大量 TCP 连接。不过这里要提醒一句连接数“扛得住”不代表你可以无上限地建立连接。操作系统本身有文件描述符限制ulimit -n如果客户端连接池配了 10000 个长连接服务端所在系统的连接数上限也得同步调高。实际项目中更推荐的做法是不要让每个设备直连 TDengine而是让接入网关、业务服务作为中间层统一连接数据库网关和设备之间走 MQTT 或私有协议网关内部再用固定大小的连接池跟 TDengine 通信。3. 从零搭建一个能扛住高并发的智能家居数据链路3.1 安装 TDengine 与基础环境配置在讲配置之前先说明白一个概念同一套 TDengine 可以同时承担“写入端”和“查询端”的压力但如果你的智能家居平台对查询实时性要求很高建议后期把数据写入和查询拆开用单独的查询节点来支撑应用侧访问。这个我们后面再说。安装 TDengine 的步骤不同系统上有差异以 Linux 服务器为例官方提供了 tar 包、rpm、deb 以及 Docker 镜像。我比较常用的方式是用官方安装包直接装因为它会把 taosd 服务、客户端工具和默认配置一起处理好。# 下载安装包后解压并执行安装脚本 tar -xzf TDengine-server-3.x.x.x-Linux-x64.tar.gz cd TDengine-server-3.x.x.x-Linux-x64 ./install.sh # 启动服务 systemctl start taosd systemctl enable taosd # 进入命令行客户端检查状态 taos -s show dnodes;装好后有几个配置项是需要提前调好的dataDir决定数据文件存放位置务必使用单独的磁盘目录不要跟系统盘混用logDir决定日志位置fqdn是集群模式下节点间的通信地址如果只是单机部署可以保持默认但生产环境中建议提前规划。3.2 建库建表面向智能家居场景的建模原则TDengine 建库语句里有很多参数会影响并发写入性能比如DURATION数据文件拆分周期、KEEP保留时长、BUFFER内存块大小、WAL_LEVEL预写日志级别和REPLICA副本数。我在智能家居场景下常用的库配置是CREATE DATABASE IF NOT EXISTS smarthome DURATION 7 KEEP 365 BUFFER 256 WAL_LEVEL 2 WAL_FSYNC_PERIOD 3000 MAXROWS 4096 MINROWS 100 RETENTIONS 5m,10m,1h;这里几个参数的实际含义我解释一下。DURATION 7表示每个数据文件分区保存 7 天的数据这个值建议根据你的查询习惯来定。如果频繁查“近 3 天数据”分区周期设得短一些查询效率更高如果要经常跨 30 天做统计分区周期短了反而会增加扫描文件的数量。KEEP 365表示数据保留 365 天超过 1 年的数据会自动删除这个很适用于智能家居数据合规和成本控制。BUFFER 256表示每个 vnode 写入缓冲的大小单位是 MB。这个值设小了高并发写入时容易频繁触发落盘设大了内存占用会上升。对单机 64GB 内存的服务器我通常设 256 或者 512。WAL_LEVEL 2表示 WAL 在“写满一个块”或“超过 fsync 周期”后落盘一次。我这里的WAL_FSYNC_PERIOD 3000指最多 3000 毫秒强制落盘一次适合对性能要求较高、能容忍秒级异常断电丢数据的场景。如果数据涉及计费或安全告警建议把WAL_LEVEL设为更严格的 0 或 1。建好库之后就可以建超级表了。根据前面说过的模型假设现在要管理智能灯光和温湿度设备我一般拆成两张超级表一张device_env存环境数据一张device_switch存开关状态事件。每接入一个设备执行一次建子表操作CREATE TABLE IF NOT EXISTS env_dev_001 USING smarthome.device_env TAGS (dev_001, living_room, sensor);在设备规模比较大的场景里建子表这个动作本身也有并发压力。我在某次模拟中一次性创建了 10 万张子表TDengine 在几十秒内就完成了没有卡顿这一点比很多传统数据库建 10 万张物理表的体验好太多因为子表本质上是轻量的元数据记录。3.3 写入侧代码示例用 Python 实现高并发批量上报接下来看实际写入代码。接入层的逻辑一般是这样设备上报数据先到网关网关做好协议解析后把数据封装成标准结构放在一个队列里后台线程定时批量写入 TDengine。下面是一个 Python 并发写入的简化示例用连接池 批量插入来模拟高并发设备上报import taos import time import random from concurrent.futures import ThreadPoolExecutor conn taos.connect( host192.168.1.100, port6030, userroot, passwordtaosdata, databasesmarthome, timeout10 ) device_count 5000 start_ts int(time.time() * 1000) def write_batch(batch_id): # 每批构造 500 条插入语句 lines [] base_id batch_id * 100 for i in range(100): device_idx base_id i ts start_ts - device_idx temp round(random.uniform(20.0, 30.0), 2) humidity round(random.uniform(40.0, 60.0), 2) battery random.randint(80, 100) # TDengine 参数绑定接口推荐使用 stmt 绑定避免拼接 SQL 注入风险 lines.append((ts, temp, humidity, battery, fdev_{device_idx}, room_1, sensor)) # 使用参数绑定接口批量写入 stmt conn.stmt_init(INSERT INTO ? USING smarthome.device_env TAGS(?,?,?) VALUES (?,?,?,?)) stmt.prepare() # 这里按实际设备分别绑定子表实际项目中通过循环绑定 stmt.set_tbname(fenv_dev_{batch_id}) stmt.set_tags((dev_ str(batch_id), room_1, sensor)) stmt.bind_param(lines) stmt.execute() stmt.close() return len(lines) executor ThreadPoolExecutor(max_workers32) futures [executor.submit(write_batch, i) for i in range(50)] total sum(f.result() for f in futures) print(f写入完成总条数: {total}) conn.close()这段示例最大的价值是展示了参数绑定接口的用法。如果你在生产环境里用 SQL 字符串拼接的方式插入大量数据不仅解析开销大而且很容易踩 SQL 注入的坑。TDengine 的 stmt 参数绑定接口更适合高吞吐写入场景它把表名、标签和值分开绑定预编译后执行性能会好不少。3.4 我做过的压测从 5000 并发到 8 万条每秒为了验证这套方案能不能扛住真实智能家居场景我做过一轮压测。测试环境是两台 8C16G 的云服务器一台部署 TDengine 3.2一台部署模拟上报程序。模拟程序开了 100 个线程每个线程负责创建连接并持续向 TDengine 写数据模拟 5 万个设备每 10 秒上报一次。结果比较有代表性放在下面这张表里供参考客户端并发连接数批量大小条/批写入 TPS条/秒平均延迟毫秒服务端 CPU1000148001242%1000100165001835%2000100310002551%5000200580003868%8000500830006182%从数据里能明显看到两个趋势第一批量大小比连接数更影响吞吐同样是 1000 个连接从单条写入改成 100 条一批吞吐从 4800 涨到 16500涨幅超过 3 倍第二连接数增加并不是线性的好从 5000 并发继续往上加延迟增长明显服务端 CPU 也逼近瓶颈。所以生产环境里“控制连接数 提高单批数据量”是最有效的调优组合。当然这个压测规模在超大型智能家居平台面前还不算极端。如果你的规模再往上走一个数量级就需要引入集群和多节点负载均衡了这时候要考虑的就不再是单机参数而是数据分片策略和网络带宽规划。3.5 数据接入层最常见的三种架构形态在真实系统里设备数据不是直接连 TDengine 的而是通过网关或业务层接入。总结下来我见过比较靠谱的接入架构有三类第一种是最简单的“共库直连”。小规模项目里设备上报服务跟数据查询服务共用同一个 TDengine 库。好处是部署简单坏处是一旦写入压力大了查询也会变慢。第二种是“读写分离”。写入服务只负责跟 TDengine 写入节点交互查询服务通过 taosAdapter 连接查询节点或者直接读取从库。这种架构适合有一定规模、需要保证监控大屏实时响应的项目。第三种是“消息队列消化峰值”。设备上报先进 Kafka、EMQX 这类消息队列再由消费者批量写入 TDengine。这种架构的优点是抗住前端的瞬时峰值特别有效缺点是多了一层组件运维复杂一些。就智能家居场景而言如果设备量在几万以内我倾向于第二种简单实用如果设备量达到几十万并且上报频率很高那第三种值得认真考虑。4. 常见问题与排查技巧实录4.1 连接数爆满但 CPU 很低问题出在哪有一次生产环境报警说 TDengine 连接数突破上限但服务器 CPU 利用率只有 20%。排查下来发现是应用层每个业务请求都新建了数据库连接根本没有复用连接池。这里的问题不在于 TDengine 服务端没有能力扛而在于应用侧把连接当成一次性资源在消耗。解决办法有三个一是确认所有调用方都用了官方客户端里的连接池机制二是给接入层设置合理的最大连接数三是调大系统层文件描述符上限。标准做法是把服务端maxConnections如果有配置项和操作系统ulimit -n同步调大但更关键的是管住应用层的连接创建行为。4.2 报错 0x83aquery denied by license: external query is restricted这个错误我在升级环境后遇到过。报错信息很明确0x83a表示查询被 license 限制拒绝后面的external query is restricted则点出了具体原因当前授予的 license 不允许执行外部查询。所谓“外部查询”通常指的是通过 taosAdapter 提供的 RESTful 端口或某种外部接入方式发起的 SQL 查询而不是通过原生客户端驱动发起的查询。这个问题最常出现在社区版或试用版 license 上因为功能特性不完全开放也可能是因为 taosAdapter 对 license 的检查比较严格。排查步骤可以先这样走# 1. 查看当前版本和 license 信息 taos -s show license; # 2. 检查 taosAdapter 日志确认报错来源 journalctl -u taosadapter -f确认是 license 功能受限后解决办法有三个方向第一如果业务不依赖外部查询把应用切换到原生连接驱动使用taos客户端对应的连接方式绕开对外部查询的许可限制第二如果必须用 RESTful 之类的接口那就要联系官方升级 license让它支持外部查询第三检查是不是配置错误导致走错了端口有时候你以为走的是原生端口实际却连接到了 taosAdapter 的 RESTful 端口上。还有一点要提一下那个报错里的external query在 TDengine 里还可能与“跨节点查询”权限有关系。所以出现0x83a时先别急着怀疑电脑坏了先看 license再看是不是接错了端口。4.3 RESTful 接入和原生连接到底怎么选很多第一次接触 TDengine 的朋友会纠结接入方式。RESTful 接口的优点是跨语言、跨平台只要支持 HTTP 就能接入对物联网里的嵌入式设备特别方便缺点也很明显HTTP 的解析开销和 JSON 序列化开销比原生二进制协议高高并发场景下往往成为瓶颈。原生连接则做到了极致性能但要求客户端语言有对应的 SDK 版本且连接参数、端口号和协议都需要正确配置。我的建议是设备端或边缘网关上报数据优先用原生批量写入对第三方开放查询、需要跨语言轻度使用的场景可以保留 RESTful 接口但不要让核心写入链路走 HTTP。一个对比表格整理如下对比项原生连接RESTful 接口性能高二进制协议中等HTTP 解析开销多语言支持C/Java/Go/Python 等官方 SDK任意支持 HTTP 的语言外部查询限制通常不受外部查询限制可能受 license 外部查询限制适用场景高吞吐写入、核心链路轻量查询、调试、第三方对接4.4 一查就慢标签基数过高把超级表拖垮了超级表虽然方便但如果标签基数设计失控也会让查询性能暴跌。所谓标签基数就是子表数量乘以标签取值组合数。比如一张超级表有 3 个标签每个标签 10 种取值理论上最多 1000 种组合但如果设备总数是 100 万每个设备的标签组合都是唯一的那标签索引规模就是 100 万。高基数标签会带来两个问题一是写入时更新标签元数据的开销变大二是查询时按标签过滤需要扫描的索引范围变大。解决办法是合理设计标签维度把房间号、设备类型这种低频枚举字段设为标签把设备的唯一 ID 或精确 IP 这种高基数、但只在单点查询时用到的字段合并进子表名称或普通列里面不要全部做成标签。另一个容易被忽略的点是LIKE模糊查询在超级表上性能并不好。如果你经常查“名称包含客厅的设备”建议在应用层维护一份设备维度表用关系型数据库存在 TDengine 里主要存时序指标两边通过设备 ID 关联。4.5 写入磁盘缓慢从配置到硬件的排查路径智能家居场景做到后期最常见的性能瓶颈往往不是 CPU而是磁盘 IO。TDengine 即使做了顺序追加写和列式压缩本质上还是要落盘磁盘性能决定写入天花板。排查顺序一般是这样的先看服务器磁盘是否和系统盘共用导致日志、系统读写和数据写入争抢 IO再看数据目录是否用了机械盘如果写入要求高至少要换成 SSD 或者 NVMe然后看 RAID 配置和数据文件预分配策略某些云盘在高随机写下性能差别很大。如果已经确认硬件没问题但写入延迟还是高可以把写入批量调大或者把 WAL 刷盘频率调低。这听起来像在“牺牲可靠性换性能”但很多时候业务上根本不需要每 1 毫秒就刷盘一次降低频率并不会造成实际影响。4.6 排查问题时的“保命”命令清单最后分享几个我遇到问题时的常用检查命令都是实际项目中用得比较多的# 查看 TDengine 集群节点状态 taos -s show dnodes; taos -s show vnodes; # 查看当前连接数能从日志或者系统连接看到 ss -antp | grep 6030 | wc -l # 查看最近写入是否阻塞或报错 taos -s show mnodes; tail -f /var/log/taos/taosdlog.0 # 查看 taosAdapter 的访问日志排查外部查询相关错误 journalctl -u taosadapter -n 200 --no-pager日志目录的位置是/var/log/taos/如果排查问题没有头绪从这段日志里基本都能找到线索。不要一看报错就重启服务TDengine 支持在线查询状态先看状态再下结论。5. 一点题外话并发连接之外的架构思考最后说点压测和调优之外的内容。TDengine 解决并发连接问题本质上是因为它的存储引擎和数据模型更适合时序数据但再强的引擎也扛不住糟糕的上层设计。我见过很多项目把 TDengine 当成 MySQL 的替代品表结构设计完全没有利用超级表和标签的特性结果并发一上来照样崩。我的建议是动手部署前先把数据模型设计好明确哪些字段是标签、哪些是采集量、保留多久、查询频率最高的是什么。这些决策对后续性能的影响比选哪一款数据库大得多。另外TDengine 的集群能力在设备规模跨越单机极限时非常重要。如果你的平台准备长期运营建议从一开始就考虑多节点部署哪怕初期只有一个节点也要把集群配置的先决条件考虑进去比如节点命名、网络端口、数据副本数。否则等数据量大了再迁移工作量会非常大。智能家居的高并发连接问题说到底是“海量设备持续产生小规模数据并且这些数据必须在秒级内完成存储和查询”的问题。TDengine 正好在这个交叉点上做了大量优化。我在实际使用中最大的体会是把连接数控制下来、把批量写利用起来、把表模型设计好你就能用很小的成本撑起非常大的设备规模。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →