Files
ts-anomaly-td/ts_anomaly_td/connector.py
T

118 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""TDengine 连接模块:taos-ws-py WebSocket 封装 + 指数退避重试。"""
import time
import logging
import taosws
logger = logging.getLogger(__name__)
_MAX_RETRY_DURATION = 1800 # 30 分钟
_MAX_BACKOFF = 300 # 5 分钟
_INSERT_BATCH_SIZE = 500
class ConnectionError(Exception):
"""TDengine 连接不可用时抛出。"""
pass
class TDConnection:
"""TDengine WebSocket 连接封装。
Args:
url: WebSocket 连接字符串,默认 ws://root:taosdata@localhost:6041
"""
def __init__(self, url: str = "ws://root:taosdata@localhost:6041"):
self._url = url
self._conn: taosws.Connection | None = None
@property
def conn(self) -> taosws.Connection:
if self._conn is None:
self._conn = self._connect_with_retry()
return self._conn
def _connect_with_retry(self) -> taosws.Connection:
"""指数退避重试连接 TDengine。
初始退避 1s,倍增因子 2,最大间隔 300s,最长持续 1800s。
每次重试前执行 SELECT SERVER_VERSION() 验证服务可用。
Raises:
ConnectionError: 30 分钟内仍不可用
"""
backoff = 1.0
started_at = time.monotonic()
last_exc = None
while True:
elapsed = time.monotonic() - started_at
if elapsed > _MAX_RETRY_DURATION:
raise ConnectionError(
f"TDengine 连接失败,已重试 {elapsed:.0f}s(超过 30 分钟上限)"
) from last_exc
try:
conn = taosws.connect(self._url)
conn.execute("SELECT SERVER_VERSION()")
logger.info("TDengine 连接成功 (url=%s)", self._url)
return conn
except Exception as e:
last_exc = e
logger.warning(
"TDengine 连接失败 (attempt, backoff=%.1fs, elapsed=%.0fs): %s",
backoff, elapsed, e,
)
time.sleep(backoff)
backoff = min(backoff * 2, _MAX_BACKOFF)
def execute(self, sql: str) -> list[tuple]:
"""执行查询 SQL,返回结果行列表。"""
logger.debug("execute: %s", sql[:120])
result = self.conn.execute(sql)
rows = []
if result is not None:
for row in result:
rows.append(tuple(row))
return rows
def execute_no_result(self, sql: str) -> None:
"""执行非查询 SQLINSERT / DDL)。"""
logger.debug("execute_no_result: %s", sql[:120])
self.conn.execute(sql)
def close(self):
"""关闭连接。"""
if self._conn is not None:
self._conn.close()
self._conn = None
def is_healthy(self) -> bool:
"""检查连接健康状态。"""
try:
self.execute("SELECT SERVER_VERSION()")
return True
except Exception:
return False
def batch_insert(self, stable: str, rows: list[tuple[int, float, int]]) -> None:
"""批量 INSERT 数据到 subtable。
每 500 条一个 batch,单次 execute_no_result()。
Args:
stable: supertable 名称(不含 ds_ 前缀)
rows: [(ts_ms, value, label), ...]
"""
total = len(rows)
for i in range(0, total, _INSERT_BATCH_SIZE):
batch = rows[i:i + _INSERT_BATCH_SIZE]
values_clauses = []
for ts, val, label in batch:
values_clauses.append(f"({ts}, {val}, {label})")
sql = f"INSERT INTO s_{stable} VALUES {' '.join(values_clauses)}"
self.execute_no_result(sql)
logger.debug("batch_insert: %d/%d rows", min(i + _INSERT_BATCH_SIZE, total), total)
logger.info("batch_insert complete: %d rows into s_%s", total, stable)