【系列:TDengine 工业物联网实战:从零搭起可运行系统 · 第 6 篇】
同一个数据库三种进门方式WebSocket 走 6041、REST 也走 6041、Native 独占 6030。端口背后藏着协议实现的本质差异。读懂这里你才能写出公平的基准测试也才知道日常开发该走哪扇门。一、为什么 6041 端口能同时住两种协议先看一张拓扑图。TDengine 的架构里有一个叫taosAdapter的网关组件。它监听 6041 端口把外部的 HTTP/WebSocket 请求翻译成数据库内部调用。所以同一个端口既能收到 REST 的 HTTP POST也能维持 WebSocket 的长连接——协议不同入口相同。Native 则完全不同。它走 6030 端口使用 TDengine 自有的二进制私有协议绕过 taosAdapter直连数据库服务。协议开销更小但环境要求更高——这一点后面基准一节会说明白没跑过基准之前谁更快别下结论。一句话记住6041 是接待大厅6030 是内部通道。二、统一抽象管线不关心你走哪扇门第 4、5 篇的写入管线依赖一个叫TelemetryWriter的抽象类。它的定义非常干净classTelemetryWriter(ABC):transport:strabstractmethodasyncdefconnect(self)-None:...abstractmethodasyncdefwrite(self,records:Sequence[Record])-WriteResult:...abstractmethodasyncdefhealth_check(self)-bool:...abstractmethodasyncdefclose(self)-None:...asyncdef__aenter__(self)-TelemetryWriter:...asyncdef__aexit__(self,...)-None:...四个抽象方法加异步上下文管理器就是全部约定。工厂模式负责三选一defcreate_writer(settings:Settings,transport:str|NoneNone)-TelemetryWriter:selectedtransportorsettings.transportifselectedwebsocket:returnWebSocketWriter(settings)ifselectedrest:returnRestWriter(settings)ifselectednative:returnNativeWriter(settings)raiseValueError(funknown transport:{selected})换传输方式改一个配置项的事。背压、攒批、重试、落盘逻辑一概不动——它们只看TelemetryWriter这个接口。这就是面向接口编程的价值传输层是门业务是房子换门不影响居住。三、WebSocket一条连接一把锁一个 cursorWebSocketWriter 的核心写逻辑浓缩在write方法里asyncdefwrite(self,records:Sequence[Record])-WriteResult:ifnotrecords:returnWriteResult(0,0,0.0,self.transport)ifself._cursorisNone:raiseRuntimeError(writer is not connected)sqlbuild_insert_sql(records,self.settings.tdengine_database)startedtime.perf_counter()asyncwithself._lock:affectedawaitasyncio.to_thread(self._cursor.execute,sql)elapsedtime.perf_counter()-started writtenint(affected)ifisinstance(affected,int)andaffected0elselen(records)returnWriteResult(len(records),written,elapsed,self.transport)几个关键点连接串是 DSN 格式ws://user:passhost:6041/{db}一个连接对应一个 cursor所有写操作通过asyncio.Lock串行化——防止并发踩踏taosws是官方 Python 连接器纯客户端协议实现不需要本地装 taosc注意asyncio.to_threadcursor 的execute是同步阻塞调用这里丢到线程池里执行避免卡死事件循环。这是个很重要的工程细节。四、REST两代响应一个兼容层RestWriter 基于 aiohttp 实现核心是_executeasyncdef_execute(self,sql:str)-dict[str,object]:ifself._sessionisNone:raiseRuntimeError(writer is not connected)asyncwithself._session.post(self.settings.rest_url,datasql.encode())asresponse:bodyawaitresponse.json(content_typeNone)ifresponse.status400:raiseOSError(fTDengine REST returned HTTP{response.status}:{body})ifnotisinstance(body,dict):raiseOSError(TDengine REST returned an invalid JSON body)ifnotresponse_is_success(body):raiseOSError(fTDengine REST SQL failed:{body})returnbody请求本身平淡无奇POST 到http://host:6041/rest/sql/{db}BasicAuth 认证body 直接放 SQL 文本。有意思的是响应兼容层defresponse_is_success(body:dict[str,object])-bool:Accept both legacy taosAdapter and current REST response envelopes.returnbody.get(status)succorbody.get(code)0老版 taosAdapter 返回{status: succ}新版返回{code: 0}。代码必须同时认。affected_rows的解析更繁琐老版取rows字段新版取data[0][0]还得带类型检查——防止把结果集行数误当写入行数。这些兼容代码说明一个现实REST 协议演进过不兼容的历史包袱都在这里偿还。但 REST 有个硬伤没有参数绑定没有订阅能力。每次写入都是一次 HTTP 往返高频写入时协议开销明显高于长连接。五、Native私有协议直连但要养一个客户端依赖NativeWriter 的代码和 WebSocket 几乎一样asyncdefwrite(self,records:Sequence[Record])-WriteResult:ifnotrecords:returnWriteResult(0,0,0.0,self.transport)ifself._connectionisNone:raiseRuntimeError(writer is not connected)sqlbuild_insert_sql(records,self.settings.tdengine_database)startedtime.perf_counter()asyncwithself._lock:affectedawaitasyncio.to_thread(self._connection.execute,sql)elapsedtime.perf_counter()-started writtenint(affected)ifisinstance(affected,int)andaffected0elselen(records)returnWriteResult(len(records),written,elapsed,self.transport)差别在底层taos.connect(host, port6030)走私有协议直连不经过 taosAdapter。代价是依赖必须安装与服务器版本匹配的 taosc 和 taospy安装命令是pip install -e ./python-services[native]。版本不兼容连接直接失败。连接过程还是阻塞的所以connect也得用to_thread包一层。选 Native 等于选了一条快速但需要自己铺路的路。六、公平基准怎么跑不是拉个脚本就完事项目里 benchmark 的设计非常讲究核心四点一、warmup 不计时。先跑 2 批数据让连接、驱动初始化这些一次性开销不污染计时。二、批次间设备轮转。(written index) % devices让数据均匀分布到所有子表避免热点。三、计时只覆盖批次提交。从writer.write开始到返回不包含数据生成时间。数据来自 simulator factory确定性 seed可复现。四、百分位线性插值。样本少也能算出稳定的 P95/P99position (n-1) * q在相邻样本间线性插值。最终产出三份报告benchmark.json机器可读、benchmark.csv表格、benchmark.md人读带结果只描述本主机本负载的免责声明。跑矩阵时batch_size 从 100 到 5000 五档循环。默认参数1000 设备、100000 行、batch 2000、warmup 2 批。但请注意这个项目没有实测报告任何数字都要自己跑出来。本文只教方法论不编数据。docs/04 里的调优顺序也值得记下设备量 → 批量逐级500/1000/2000/5000→ worker 数 → VGROUPS → BUFFER。记录清单同样重要TDengine 版本、CPU、内存、批量、吞吐、P95/P99、错误率、WAL、副本数、VGROUPS、BUFFER——少记一项报告就失去可复现性。七、怎么选文档说得很实在docs/03 里有一段话直接引用“新系统使用 WebSocket。REST 适合简单管理脚本但缺少参数绑定和订阅。Python Native 可用于必须使用原生能力且能管理客户端依赖的场景。最终选择应基于相同数据、相同批量、相同主机的重复基准测试而不是凭经验预判。”翻译成决策树新系统、生产环境→ WebSocket兼得协议性能和部署轻便管理脚本、临时查询→ REST简单直接必须用原生能力、能管好依赖→ Native但要接受版本兼容的运维成本REST 的定位在这套代码里很明确教学和兼容测试。它让新手能以最低门槛跑通链路也让老脚本不至于失效。八、小结三路对比维度WebSocketRESTNative端口604160416030协议WSHTTP私有二进制依赖taoswsaiohttptaosc taospy参数绑定支持不支持支持订阅支持不支持支持适用新系统首选教学/脚本原生能力场景传输层的故事到这里就结束了。但数据从哪来第 7 篇我们聊聊数据入口模拟器与多源采集——毕竟写入前提是得有数据写。觉得有用点个关注持续获取优质内容。