
TDengine Rust 连接器进阶指南
本文档面向熟悉 TDengine 和 Rust 的专业开发人员,提供 Rust 连接器的进阶使用指南。内容涵盖性能优化、异步编程最佳实践、连接池配置、复杂场景处理等高级主题,帮助您充分发挥 TDengine 和 Rust 的性能潜力。
:::tip 前置条件
阅读本文档前,请确保您已熟悉 Rust 连接器基础文档 中的基本概念和 API 使用方法。
:::
连接策略选择
Native vs WebSocket:如何选择
| 维度 | Native 连接 | WebSocket 连接 |
|---|---|---|
| 性能 | 更高(直接调用 C 库) | 略低(HTTP 协议开销) |
| 部署依赖 | 需要客户端驱动 | 无需客户端驱动 |
| 跨平台 | 受限于客户端驱动支持 | 支持所有 Rust 平台 |
| 防火墙 | 需开放 6030 端口 | 仅需 6041 端口(HTTP) |
| 负载均衡 | 不支持多端点 | 支持多端点负载均衡 |
| 云环境 | 部分云环境受限 | 云原生友好 |
| 异步支持 | 通过 tokio spawn_blocking | 原生异步支持 |
推荐选择策略:
rust
use taos::*;
// 场景1:高性能写入场景,本地或内网部署 → Native 连接
let native_dsn = "taos://root:taosdata@192.168.1.100:6030/power";
// 场景2:云环境、跨网络、需要高可用 → WebSocket 连接
let ws_dsn = "taos+ws://root:taosdata@node1:6041,node2:6041,node3:6041/power";
// 场景3:需要 TLS 加密连接 → WebSocket + TLS
let wss_dsn = "taos+wss://root:taosdata@192.168.1.100:6041/power";
// 创建连接
let builder = TaosBuilder::from_dsn(native_dsn)?;
let taos = builder.build().await?;
多端点负载均衡配置
WebSocket 连接支持配置多个 taosAdapter 端点,实现连接级别的负载均衡:
rust
use taos::*;
// 多端点配置,连接时自动选择可用节点
let dsn = "taos+ws://root:taosdata@adapter1:6041,adapter2:6041,adapter3:6041/power\
?conn_retries=5\
&retry_backoff_ms=200\
&retry_backoff_max_ms=5000";
let builder = TaosBuilder::from_dsn(dsn)?;
// 检查连接是否就绪
if builder.ready() {
let taos = builder.build().await?;
println!("Connected to TDengine");
}
DSN 高级参数配置
rust
use taos::*;
// 完整的 DSN 参数配置示例
let dsn = "taos+ws://root:taosdata@192.168.1.100:6041/power?\
timezone=Asia/Shanghai\
&compression=true\
&conn_retries=5\
&retry_backoff_ms=200\
&retry_backoff_max_ms=2000\
&token=your_cloud_token";
let builder = TaosBuilder::from_dsn(dsn)?;
// 查看支持的参数
let params = TaosBuilder::available_params();
println!("Available parameters: {:?}", params);
异步编程最佳实践
同步与异步接口选择
Rust 连接器同时提供同步和异步接口,选择策略如下:
rust
use taos::*;
use tokio::runtime::Runtime;
// 方式1:纯异步环境(推荐)
#[tokio::main]
async fn main() -> Result<(), Error> {
let taos = TaosBuilder::from_dsn("taos+ws://localhost:6041")?
.build()
.await?;
// 异步执行查询
let result = taos.query("SELECT * FROM meters LIMIT 10").await?;
Ok(())
}
// 方式2:同步环境使用异步连接器
fn sync_main() -> Result<(), Error> {
let rt = Runtime::new()?;
rt.block_on(async {
let taos = TaosBuilder::from_dsn("taos+ws://localhost:6041")?
.build()
.await?;
taos.exec("INSERT INTO d1001 VALUES(NOW, 10.5, 220, 0.31)").await?;
Ok(())
})
}
// 方式3:同步接口(适用于简单场景)
fn sync_query() -> Result<(), Error> {
let taos = TaosBuilder::from_dsn("taos://localhost:6030")?
.build()?; // 同步 build
let affected = taos.exec("INSERT INTO d1001 VALUES(NOW, 10.5, 220, 0.31)")?;
println!("Affected rows: {}", affected);
Ok(())
}
高效并发查询
rust
use taos::*;
use tokio::task::JoinSet;
async fn concurrent_queries(taos: &Taos, queries: Vec<String>) -> Result<Vec<ResultSet>, Error> {
let mut join_set = JoinSet::new();
for sql in queries {
let taos = taos.clone(); // Taos 实现了 Clone
join_set.spawn(async move {
taos.query(&sql).await
});
}
let mut results = Vec::new();
while let Some(result) = join_set.join_next().await {
match result {
Ok(Ok(rs)) => results.push(rs),
Ok(Err(e)) => eprintln!("Query failed: {:?}", e),
Err(e) => eprintln!("Task panicked: {:?}", e),
}
}
Ok(results)
}
// 使用示例
#[tokio::main]
async fn main() -> Result<(), Error> {
let taos = TaosBuilder::from_dsn("taos+ws://localhost:6041/power")?
.build()
.await?;
let queries = vec![
"SELECT AVG(current) FROM d1001 WHERE ts > NOW - 1h".to_string(),
"SELECT MAX(voltage) FROM d1002 WHERE ts > NOW - 1h".to_string(),
"SELECT MIN(phase) FROM d1003 WHERE ts > NOW - 1h".to_string(),
];
let results = concurrent_queries(&taos, queries).await?;
println!("Got {} results", results.len());
Ok(())
}
流式处理大结果集
rust
use taos::*;
use futures::StreamExt;
async fn stream_large_result(taos: &Taos, sql: &str) -> Result<(), Error> {
let mut result = taos.query(sql).await?;
// 获取列信息
let fields = result.fields();
for field in fields {
println!("Column: {}, Type: {:?}, Bytes: {}",
field.name(), field.ty(), field.bytes());
}
// 方式1:按行迭代
let mut rows = result.rows();
while let Some(row_result) = rows.next().await {
let row = row_result?;
// 处理每一行
for (idx, value) in row.iter().enumerate() {
println!("Column {}: {:?}", idx, value);
}
}
// 方式2:按数据块迭代(更高效)
let mut result = taos.query(sql).await?;
let mut blocks = result.blocks();
while let Some(block_result) = blocks.next().await {
let block = block_result?;
println!("Got block with {} rows", block.nrows());
// 按列处理数据块
for col_idx in 0..block.ncols() {
let column = block.get_column(col_idx);
// 处理列数据...
}
}
Ok(())
}
超时和取消处理
rust
use taos::*;
use tokio::time::{timeout, Duration};
use tokio_util::sync::CancellationToken;
async fn query_with_timeout(taos: &Taos, sql: &str, timeout_secs: u64) -> Result<ResultSet, Error> {
match timeout(Duration::from_secs(timeout_secs), taos.query(sql)).await {
Ok(result) => result,
Err(_) => Err(Error::from_string("Query timeout")),
}
}
async fn query_with_cancellation(
taos: &Taos,
sql: &str,
cancel_token: CancellationToken
) -> Result<Option<ResultSet>, Error> {
tokio::select! {
result = taos.query(sql) => {
Ok(Some(result?))
}
_ = cancel_token.cancelled() => {
println!("Query cancelled");
Ok(None)
}
}
}
// 使用示例
#[tokio::main]
async fn main() -> Result<(), Error> {
let taos = TaosBuilder::from_dsn("taos+ws://localhost:6041/power")?
.build()
.await?;
// 带超时的查询
match query_with_timeout(&taos, "SELECT * FROM meters", 30).await {
Ok(result) => println!("Query succeeded"),
Err(e) => eprintln!("Query failed or timeout: {:?}", e),
}
// 带取消的查询
let cancel_token = CancellationToken::new();
let cancel_clone = cancel_token.clone();
// 在另一个任务中可以取消查询
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(5)).await;
cancel_clone.cancel();
});
let result = query_with_cancellation(&taos, "SELECT * FROM meters", cancel_token).await?;
Ok(())
}
连接池配置
使用 deadpool 连接池
toml
# Cargo.toml
[dependencies]
taos = "0.12"
deadpool = "0.10"
tokio = { version = "1", features = ["full"] }
rust
use taos::*;
use deadpool::managed::{Manager, Pool, RecycleResult};
use std::sync::Arc;
// 实现连接池管理器
struct TaosManager {
builder: TaosBuilder,
}
impl TaosManager {
fn new(dsn: &str) -> Result<Self, Error> {
Ok(Self {
builder: TaosBuilder::from_dsn(dsn)?,
})
}
}
impl Manager for TaosManager {
type Type = Taos;
type Error = Error;
async fn create(&self) -> Result<Taos, Error> {
self.builder.build().await
}
async fn recycle(&self, conn: &mut Taos, _: &deadpool::managed::Metrics) -> RecycleResult<Error> {
// 检查连接是否有效
match conn.query("SELECT 1").await {
Ok(_) => Ok(()),
Err(e) => Err(deadpool::managed::RecycleError::Backend(e)),
}
}
}
// 创建连接池
async fn create_pool(dsn: &str, max_size: usize) -> Result<Pool<TaosManager>, Error> {
let manager = TaosManager::new(dsn)?;
let pool = Pool::builder(manager)
.max_size(max_size)
.build()
.map_err(|e| Error::from_string(format!("Pool creation failed: {}", e)))?;
Ok(pool)
}
// 使用连接池
#[tokio::main]
async fn main() -> Result<(), Error> {
let pool = create_pool("taos+ws://localhost:6041/power", 20).await?;
// 从池中获取连接
let conn = pool.get().await
.map_err(|e| Error::from_string(format!("Get connection failed: {}", e)))?;
// 使用连接
let result = conn.query("SELECT * FROM meters LIMIT 10").await?;
println!("Query succeeded");
// 连接自动归还到池中
drop(conn);
Ok(())
}
使用 r2d2 连接池(同步)
toml
# Cargo.toml
[dependencies]
taos = "0.12"
r2d2 = "0.8"
rust
use taos::sync::*; // 使用同步接口
use r2d2::{Pool, ManageConnection, PooledConnection};
use std::time::Duration;
// 实现 r2d2 连接管理器
struct TaosConnectionManager {
dsn: String,
}
impl TaosConnectionManager {
fn new(dsn: &str) -> Self {
Self { dsn: dsn.to_string() }
}
}
impl ManageConnection for TaosConnectionManager {
type Connection = Taos;
type Error = Error;
fn connect(&self) -> Result<Taos, Error> {
TaosBuilder::from_dsn(&self.dsn)?.build()
}
fn is_valid(&self, conn: &mut Taos) -> Result<(), Error> {
conn.exec("SELECT 1")?;
Ok(())
}
fn has_broken(&self, _conn: &mut Taos) -> bool {
false
}
}
// 创建连接池
fn create_pool(dsn: &str) -> Result<Pool<TaosConnectionManager>, Error> {
let manager = TaosConnectionManager::new(dsn);
Pool::builder()
.max_size(20)
.min_idle(Some(5))
.connection_timeout(Duration::from_secs(30))
.idle_timeout(Some(Duration::from_secs(600)))
.max_lifetime(Some(Duration::from_secs(1800)))
.build(manager)
.map_err(|e| Error::from_string(format!("Pool creation failed: {}", e)))
}
fn main() -> Result<(), Error> {
let pool = create_pool("taos://localhost:6030/power")?;
// 从池中获取连接
let conn = pool.get()
.map_err(|e| Error::from_string(format!("Get connection failed: {}", e)))?;
// 使用连接
let affected = conn.exec("INSERT INTO d1001 VALUES(NOW, 10.5, 220, 0.31)")?;
println!("Affected rows: {}", affected);
Ok(())
}
连接池监控
rust
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use tokio::time::{interval, Duration};
struct PoolMetrics {
active_connections: AtomicU64,
idle_connections: AtomicU64,
total_queries: AtomicU64,
failed_queries: AtomicU64,
}
impl PoolMetrics {
fn new() -> Self {
Self {
active_connections: AtomicU64::new(0),
idle_connections: AtomicU64::new(0),
total_queries: AtomicU64::new(0),
failed_queries: AtomicU64::new(0),
}
}
fn record_query(&self, success: bool) {
self.total_queries.fetch_add(1, Ordering::Relaxed);
if !success {
self.failed_queries.fetch_add(1, Ordering::Relaxed);
}
}
fn report(&self) {
println!(
"Pool Stats - Active: {}, Idle: {}, Total Queries: {}, Failed: {}",
self.active_connections.load(Ordering::Relaxed),
self.idle_connections.load(Ordering::Relaxed),
self.total_queries.load(Ordering::Relaxed),
self.failed_queries.load(Ordering::Relaxed)
);
}
}
// 启动监控任务
async fn start_monitoring(metrics: Arc<PoolMetrics>) {
let mut interval = interval(Duration::from_secs(30));
loop {
interval.tick().await;
metrics.report();
// 告警检查
let failed = metrics.failed_queries.load(Ordering::Relaxed);
let total = metrics.total_queries.load(Ordering::Relaxed);
if total > 0 && (failed as f64 / total as f64) > 0.1 {
eprintln!("WARNING: Query failure rate > 10%!");
}
}
}
高效写入模式
批量参数绑定
rust
use taos::*;
async fn batch_insert(taos: &Taos, data: Vec<SensorData>) -> Result<usize, Error> {
let mut stmt = Stmt::init(taos).await?;
// 准备 SQL
stmt.prepare("INSERT INTO ? USING meters TAGS(?, ?) VALUES(?, ?, ?, ?)").await?;
let mut total_affected = 0;
// 按表分组数据
let mut table_data: std::collections::HashMap<String, Vec<&SensorData>> =
std::collections::HashMap::new();
for item in &data {
table_data.entry(item.table_name.clone())
.or_insert_with(Vec::new)
.push(item);
}
// 批量写入每个表
for (table_name, items) in table_data {
// 设置表名
stmt.set_tbname(&table_name).await?;
// 设置 Tags(首次创建子表时需要)
if let Some(first) = items.first() {
stmt.set_tags(&[
Value::Int(first.group_id),
Value::VarChar(first.location.clone()),
]).await?;
}
// 准备列数据
let timestamps: Vec<i64> = items.iter().map(|d| d.ts).collect();
let currents: Vec<f32> = items.iter().map(|d| d.current).collect();
let voltages: Vec<i32> = items.iter().map(|d| d.voltage).collect();
let phases: Vec<f32> = items.iter().map(|d| d.phase).collect();
// 绑定列数据
stmt.bind(&[
ColumnView::from_millis_timestamp(timestamps),
ColumnView::from_floats(currents),
ColumnView::from_ints(voltages),
ColumnView::from_floats(phases),
]).await?;
// 添加批次
stmt.add_batch().await?;
}
// 执行
total_affected = stmt.execute().await?;
Ok(total_affected)
}
#[derive(Clone)]
struct SensorData {
table_name: String,
group_id: i32,
location: String,
ts: i64,
current: f32,
voltage: i32,
phase: f32,
}
无模式写入优化
rust
use taos::*;
use taos::schemaless::*;
async fn schemaless_write(taos: &Taos) -> Result<(), Error> {
// 方式1:InfluxDB 行协议
let lines = vec![
"meters,location=California.SanFrancisco,groupid=2 current=10.3,voltage=219,phase=0.31 1626006833639",
"meters,location=California.SanFrancisco,groupid=2 current=12.6,voltage=218,phase=0.33 1626006833640",
"meters,location=California.LosAngeles,groupid=3 current=11.8,voltage=221,phase=0.28 1626006833641",
];
let sml_data = SmlDataBuilder::default()
.protocol(SchemalessProtocol::Line)
.precision(SchemalessPrecision::Millisecond)
.data(lines.iter().map(|s| s.to_string()).collect())
.ttl(Some(365 * 24 * 60 * 60)) // 1年 TTL
.req_id(Some(generate_req_id()))
.build()?;
taos.put(&sml_data).await?;
// 方式2:OpenTSDB Telnet 协议
let telnet_lines = vec![
"sys.cpu.nice 1626006833 9 host=web01 dc=lga",
"sys.cpu.nice 1626006833 18 host=web02 dc=lga",
];
let sml_data = SmlDataBuilder::default()
.protocol(SchemalessProtocol::Telnet)
.precision(SchemalessPrecision::Seconds)
.data(telnet_lines.iter().map(|s| s.to_string()).collect())
.build()?;
taos.put(&sml_data).await?;
// 方式3:OpenTSDB JSON 协议
let json_data = r#"[
{
"metric": "sys.mem.free",
"timestamp": 1626006833,
"value": 8589934592,
"tags": {
"host": "web01",
"dc": "lga"
}
}
]"#;
let sml_data = SmlDataBuilder::default()
.protocol(SchemalessProtocol::Json)
.precision(SchemalessPrecision::Seconds)
.data(vec![json_data.to_string()])
.build()?;
taos.put(&sml_data).await?;
Ok(())
}
fn generate_req_id() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let seq = COUNTER.fetch_add(1, Ordering::SeqCst) & 0xFFFF_FFFF;
(timestamp << 32) | seq
}
高性能写入管道
rust
use taos::*;
use tokio::sync::mpsc;
use std::sync::Arc;
struct WriteBuffer {
data: Vec<SensorData>,
max_size: usize,
}
impl WriteBuffer {
fn new(max_size: usize) -> Self {
Self {
data: Vec::with_capacity(max_size),
max_size,
}
}
fn push(&mut self, item: SensorData) -> bool {
self.data.push(item);
self.data.len() >= self.max_size
}
fn drain(&mut self) -> Vec<SensorData> {
std::mem::take(&mut self.data)
}
}
async fn write_pipeline(
taos: Arc<Taos>,
mut rx: mpsc::Receiver<SensorData>,
batch_size: usize,
flush_interval_ms: u64,
) {
let mut buffer = WriteBuffer::new(batch_size);
let mut interval = tokio::time::interval(
tokio::time::Duration::from_millis(flush_interval_ms)
);
loop {
tokio::select! {
Some(data) = rx.recv() => {
if buffer.push(data) {
// 缓冲区满,立即刷新
let batch = buffer.drain();
if let Err(e) = flush_batch(&taos, batch).await {
eprintln!("Flush failed: {:?}", e);
}
}
}
_ = interval.tick() => {
// 定时刷新
if !buffer.data.is_empty() {
let batch = buffer.drain();
if let Err(e) = flush_batch(&taos, batch).await {
eprintln!("Flush failed: {:?}", e);
}
}
}
}
}
}
async fn flush_batch(taos: &Taos, data: Vec<SensorData>) -> Result<(), Error> {
if data.is_empty() {
return Ok(());
}
let affected = batch_insert(taos, data).await?;
println!("Flushed {} rows", affected);
Ok(())
}
// 使用示例
#[tokio::main]
async fn main() -> Result<(), Error> {
let taos = Arc::new(
TaosBuilder::from_dsn("taos+ws://localhost:6041/power")?
.build()
.await?
);
let (tx, rx) = mpsc::channel::<SensorData>(10000);
// 启动写入管道
let taos_clone = taos.clone();
tokio::spawn(async move {
write_pipeline(taos_clone, rx, 1000, 1000).await;
});
// 发送数据
for i in 0..10000 {
let data = SensorData {
table_name: format!("d100{}", i % 10),
group_id: (i % 10) as i32,
location: "California.SanFrancisco".to_string(),
ts: chrono::Utc::now().timestamp_millis(),
current: 10.0 + (i as f32) * 0.01,
voltage: 220,
phase: 0.31,
};
tx.send(data).await.unwrap();
}
Ok(())
}
数据订阅高级用法
消费者管理
rust
use taos::*;
use taos::tmq::*;
async fn create_consumer(group_id: &str, client_id: &str) -> Result<Consumer, Error> {
let dsn = format!(
"tmq+ws://root:taosdata@localhost:6041?\
group.id={}&\
client.id={}&\
auto.offset.reset=earliest&\
enable.auto.commit=false",
group_id, client_id
);
let builder = TmqBuilder::from_dsn(&dsn)?;
builder.build().await
}
async fn consume_loop(
consumer: &mut Consumer,
topics: Vec<&str>,
handler: impl Fn(&MessageSet) -> Result<(), Error>,
) -> Result<(), Error> {
// 订阅主题
consumer.subscribe(topics).await?;
loop {
// 拉取消息,超时 500ms
match consumer.recv_timeout(std::time::Duration::from_millis(500)).await {
Ok(Some((offset, message_set))) => {
// 处理消息
handler(&message_set)?;
// 手动提交偏移量
consumer.commit(offset).await?;
}
Ok(None) => {
// 无消息,继续等待
continue;
}
Err(e) => {
eprintln!("Receive error: {:?}", e);
// 可以选择重连或退出
break;
}
}
}
Ok(())
}
多消费者并行处理
rust
use taos::*;
use taos::tmq::*;
use tokio::task::JoinSet;
use std::sync::Arc;
struct ConsumerGroup {
consumers: Vec<Consumer>,
}
impl ConsumerGroup {
async fn new(
group_id: &str,
num_consumers: usize,
dsn_base: &str,
) -> Result<Self, Error> {
let mut consumers = Vec::with_capacity(num_consumers);
for i in 0..num_consumers {
let dsn = format!(
"{}?group.id={}&client.id=consumer-{}&auto.offset.reset=earliest",
dsn_base, group_id, i
);
let consumer = TmqBuilder::from_dsn(&dsn)?
.build()
.await?;
consumers.push(consumer);
}
Ok(Self { consumers })
}
async fn start<F>(
mut self,
topics: Vec<String>,
handler: Arc<F>,
) -> Result<(), Error>
where
F: Fn(String, Vec<Value>) + Send + Sync + 'static,
{
let mut join_set = JoinSet::new();
for mut consumer in self.consumers {
let topics = topics.clone();
let handler = handler.clone();
join_set.spawn(async move {
consumer.subscribe(topics.iter().map(|s| s.as_str()).collect()).await?;
loop {
match consumer.recv_timeout(std::time::Duration::from_millis(500)).await {
Ok(Some((offset, message_set))) => {
// 处理消息
for (topic, data) in message_set.iter() {
for row in data.rows() {
let values: Vec<Value> = row.iter()
.map(|v| v.clone())
.collect();
handler(topic.to_string(), values);
}
}
consumer.commit(offset).await?;
}
Ok(None) => continue,
Err(e) => {
eprintln!("Consumer error: {:?}", e);
break;
}
}
}
Ok::<(), Error>(())
});
}
// 等待所有消费者完成
while let Some(result) = join_set.join_next().await {
if let Err(e) = result {
eprintln!("Consumer task failed: {:?}", e);
}
}
Ok(())
}
}
偏移量管理
rust
use taos::*;
use taos::tmq::*;
use std::collections::HashMap;
struct OffsetManager {
consumer: Consumer,
saved_offsets: HashMap<(String, VGroupId), i64>,
}
impl OffsetManager {
async fn new(consumer: Consumer) -> Self {
Self {
consumer,
saved_offsets: HashMap::new(),
}
}
/// 获取当前分配的分区
fn get_assignments(&self) -> Option<Vec<(String, Vec<Assignment>)>> {
self.consumer.assignments()
}
/// 获取特定主题分区的已提交偏移量
async fn get_committed_offset(
&self,
topic: &str,
vgroup_id: VGroupId,
) -> Result<i64, Error> {
self.consumer.committed(topic, vgroup_id).await
}
/// 获取当前消费位置
async fn get_position(
&self,
topic: &str,
vgroup_id: VGroupId,
) -> Result<i64, Error> {
self.consumer.position(topic, vgroup_id).await
}
/// 重置到指定偏移量
async fn seek_to_offset(
&mut self,
topic: &str,
vgroup_id: VGroupId,
offset: i64,
) -> Result<(), Error> {
self.consumer.offset_seek(topic, vgroup_id, offset).await
}
/// 重置到最早偏移量
async fn seek_to_beginning(&mut self, topic: &str) -> Result<(), Error> {
if let Some(assignments) = self.get_assignments() {
for (t, partitions) in assignments {
if t == topic {
for partition in partitions {
self.consumer.offset_seek(
topic,
partition.vgroup_id(),
partition.begin(),
).await?;
}
}
}
}
Ok(())
}
/// 重置到最新偏移量
async fn seek_to_end(&mut self, topic: &str) -> Result<(), Error> {
if let Some(assignments) = self.get_assignments() {
for (t, partitions) in assignments {
if t == topic {
for partition in partitions {
self.consumer.offset_seek(
topic,
partition.vgroup_id(),
partition.end(),
).await?;
}
}
}
}
Ok(())
}
/// 提交特定偏移量
async fn commit_offset(
&self,
topic: &str,
vgroup_id: VGroupId,
offset: i64,
) -> Result<(), Error> {
self.consumer.commit_offset(topic, vgroup_id, offset).await
}
}
错误处理最佳实践
自定义错误类型
rust
use taos::*;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("TDengine error: {0}")]
TDengine(#[from] taos::Error),
#[error("Connection failed after {retries} retries: {message}")]
ConnectionFailed {
retries: u32,
message: String,
},
#[error("Query timeout after {seconds} seconds")]
QueryTimeout { seconds: u64 },
#[error("Data validation error: {0}")]
ValidationError(String),
#[error("Pool error: {0}")]
PoolError(String),
}
impl AppError {
pub fn is_retryable(&self) -> bool {
match self {
AppError::TDengine(e) => {
// 检查错误码判断是否可重试
let code = e.code();
matches!(code,
0x0200 | // TSC_DISCONNECTED
0x0204 | // TSC_INVALID_CONNECTION
0x0B00 | // RPC_NETWORK_UNAVAIL
0x0B01 // RPC_TIMEOUT
)
}
AppError::ConnectionFailed { .. } => true,
AppError::QueryTimeout { .. } => true,
_ => false,
}
}
}
重试机制
rust
use std::time::Duration;
use tokio::time::sleep;
pub struct RetryConfig {
pub max_retries: u32,
pub initial_delay_ms: u64,
pub max_delay_ms: u64,
pub exponential_base: u32,
}
impl Default for RetryConfig {
fn default() -> Self {
Self {
max_retries: 3,
initial_delay_ms: 100,
max_delay_ms: 10000,
exponential_base: 2,
}
}
}
pub async fn with_retry<T, E, F, Fut>(
config: &RetryConfig,
operation: F,
) -> Result<T, E>
where
F: Fn() -> Fut,
Fut: std::future::Future<Output = Result<T, E>>,
E: std::fmt::Debug,
{
let mut retries = 0;
let mut delay = config.initial_delay_ms;
loop {
match operation().await {
Ok(result) => return Ok(result),
Err(e) => {
retries += 1;
if retries >= config.max_retries {
eprintln!("Max retries exceeded: {:?}", e);
return Err(e);
}
eprintln!(
"Operation failed (attempt {}/{}): {:?}, retrying in {}ms",
retries, config.max_retries, e, delay
);
sleep(Duration::from_millis(delay)).await;
// 指数退避
delay = std::cmp::min(
delay * config.exponential_base as u64,
config.max_delay_ms,
);
}
}
}
}
// 使用示例
async fn query_with_retry(taos: &Taos, sql: &str) -> Result<ResultSet, Error> {
let config = RetryConfig::default();
with_retry(&config, || async {
taos.query(sql).await
}).await
}
连接健康检查
rust
use taos::*;
use std::sync::Arc;
use tokio::time::{interval, Duration};
pub struct HealthChecker {
taos: Arc<Taos>,
check_interval: Duration,
}
impl HealthChecker {
pub fn new(taos: Arc<Taos>, check_interval_secs: u64) -> Self {
Self {
taos,
check_interval: Duration::from_secs(check_interval_secs),
}
}
pub async fn start(&self) {
let mut ticker = interval(self.check_interval);
loop {
ticker.tick().await;
match self.check_health().await {
Ok(status) => {
if status.latency_ms > 1000 {
eprintln!("WARNING: High latency: {}ms", status.latency_ms);
} else {
println!(
"Health OK - Server: {}, Latency: {}ms",
status.server_version, status.latency_ms
);
}
}
Err(e) => {
eprintln!("Health check failed: {:?}", e);
}
}
}
}
async fn check_health(&self) -> Result<HealthStatus, Error> {
let start = std::time::Instant::now();
// 执行轻量级查询
let server_version = self.taos.server_version().await?;
let latency_ms = start.elapsed().as_millis() as u64;
Ok(HealthStatus {
server_version: server_version.to_string(),
latency_ms,
is_healthy: latency_ms < 5000,
})
}
}
pub struct HealthStatus {
pub server_version: String,
pub latency_ms: u64,
pub is_healthy: bool,
}
类型安全与反序列化
使用 serde 反序列化
rust
use taos::*;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Meter {
ts: i64,
current: f32,
voltage: i32,
phase: f32,
#[serde(default)]
location: Option<String>,
#[serde(default)]
group_id: Option<i32>,
}
async fn query_as_struct(taos: &Taos) -> Result<Vec<Meter>, Error> {
let mut result = taos.query("SELECT * FROM meters LIMIT 100").await?;
let meters: Vec<Meter> = result.deserialize()
.filter_map(|r| r.ok())
.collect();
Ok(meters)
}
// 单行查询
async fn query_one_meter(taos: &Taos, table: &str) -> Result<Option<Meter>, Error> {
let sql = format!("SELECT * FROM {} ORDER BY ts DESC LIMIT 1", table);
taos.query_one::<Meter>(&sql).await
}
自定义类型转换
rust
use taos::*;
use std::convert::TryFrom;
// 自定义 GEOMETRY 类型处理
struct GeoPoint {
longitude: f64,
latitude: f64,
}
impl TryFrom<&[u8]> for GeoPoint {
type Error = String;
fn try_from(wkb: &[u8]) -> Result<Self, Self::Error> {
// 解析 WKB 格式(简化示例)
if wkb.len() < 21 {
return Err("Invalid WKB data".to_string());
}
// 跳过字节序和类型标识
let longitude = f64::from_le_bytes(wkb[5..13].try_into().unwrap());
let latitude = f64::from_le_bytes(wkb[13..21].try_into().unwrap());
Ok(GeoPoint { longitude, latitude })
}
}
// 自定义 Timestamp 处理
use chrono::{DateTime, Utc, TimeZone};
fn timestamp_to_datetime(ts: i64, precision: Precision) -> DateTime<Utc> {
match precision {
Precision::Millisecond => Utc.timestamp_millis_opt(ts).unwrap(),
Precision::Microsecond => Utc.timestamp_micros(ts).unwrap(),
Precision::Nanosecond => Utc.timestamp_nanos(ts),
}
}
async fn query_with_custom_types(taos: &Taos) -> Result<(), Error> {
let mut result = taos.query("SELECT ts, location FROM geo_table LIMIT 10").await?;
let precision = result.precision();
for row in result.rows() {
let row = row?;
// 获取时间戳并转换
if let Some(Value::Timestamp(ts)) = row.get(0) {
let dt = timestamp_to_datetime(ts.as_raw_i64(), precision);
println!("Timestamp: {}", dt);
}
// 获取 GEOMETRY 并转换
if let Some(Value::VarBinary(wkb)) = row.get(1) {
if let Ok(point) = GeoPoint::try_from(wkb.as_slice()) {
println!("Location: ({}, {})", point.longitude, point.latitude);
}
}
}
Ok(())
}
性能调优检查清单
写入性能优化
| 优化项 | 建议配置 | 说明 |
|---|---|---|
| 批量大小 | 1000-10000 行/批 | 过小增加网络开销,过大增加内存压力 |
| 参数绑定 | 使用列式绑定 | 比逐行绑定减少 FFI 调用 |
| 传输压缩 | compression=true |
网络带宽受限时启用 |
| 连接复用 | 使用连接池 | 避免频繁创建销毁连接 |
| 异步写入 | 使用 tokio spawn | 不阻塞主线程 |
查询性能优化
| 优化项 | 建议配置 | 说明 |
|---|---|---|
| 流式处理 | 使用 blocks() 迭代器 | 大结果集避免全量加载到内存 |
| 并发查询 | 使用 JoinSet | 多个独立查询并行执行 |
| 查询超时 | 使用 tokio::timeout | 避免慢查询阻塞 |
| 请求 ID | 使用 query_with_req_id | 便于链路追踪 |
连接管理
| 优化项 | 建议配置 | 说明 |
|---|---|---|
| 连接池大小 | 20-50 | 根据并发量和服务端承载能力调整 |
| 连接超时 | 30秒 | 避免长时间等待 |
| 空闲超时 | 10分钟 | 及时回收空闲连接 |
| 多端点配置 | 配置 2-3 个端点 | 实现故障转移 |
故障排查指南
诊断工具
rust
use taos::*;
pub struct Diagnostics {
dsn: String,
}
impl Diagnostics {
pub fn new(dsn: &str) -> Self {
Self { dsn: dsn.to_string() }
}
pub async fn run(&self) {
println!("=== TDengine Connection Diagnostics ===\n");
// 1. DSN 解析
println!("DSN: {}", self.dsn);
// 2. 尝试连接
match TaosBuilder::from_dsn(&self.dsn) {
Ok(builder) => {
println!("DSN Parse: OK");
println!("Client Version: {}", TaosBuilder::client_version());
// 3. 建立连接
match builder.build().await {
Ok(taos) => {
println!("Connection: SUCCESS");
// 4. 服务端信息
match taos.server_version().await {
Ok(version) => println!("Server Version: {}", version),
Err(e) => println!("Server Version: FAILED ({:?})", e),
}
// 5. 测试查询
let start = std::time::Instant::now();
match taos.query("SELECT 1").await {
Ok(_) => {
let latency = start.elapsed().as_millis();
println!("Test Query: OK ({}ms)", latency);
}
Err(e) => println!("Test Query: FAILED ({:?})", e),
}
// 6. 数据库列表
match taos.databases().await {
Ok(dbs) => {
println!("\nAvailable Databases:");
for db in dbs {
println!(" - {}", db.name);
}
}
Err(e) => println!("List Databases: FAILED ({:?})", e),
}
}
Err(e) => {
println!("Connection: FAILED");
self.suggest_fix(&e);
}
}
}
Err(e) => {
println!("DSN Parse: FAILED ({:?})", e);
}
}
}
fn suggest_fix(&self, e: &Error) {
println!("\n=== Suggested Fix ===");
let code = e.code();
let message = format!("{:?}", e);
if code == 0x0200 || code == 0x0204 || message.contains("connect") {
println!("1. Check if TDengine server is running");
println!("2. Verify network connectivity");
println!("3. Check firewall settings (port 6030/6041)");
} else if code == 0x0357 || message.contains("auth") {
println!("1. Verify username and password");
println!("2. Check user permissions");
} else if message.contains("library") || message.contains("driver") {
println!("1. Install TDengine client driver");
println!("2. Or switch to WebSocket connection (taos+ws://)");
} else {
println!("Error code: 0x{:X}", code);
println!("Message: {}", message);
}
}
}
// 使用
#[tokio::main]
async fn main() {
let diag = Diagnostics::new("taos+ws://root:taosdata@localhost:6041/power");
diag.run().await;
}
常见问题解决
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 连接超时 | 网络问题或服务端未启动 | 检查网络和服务状态 |
| 认证失败 | 用户名密码错误 | 检查凭据配置 |
| 找不到库 | 缺少客户端驱动 | 安装驱动或使用 WebSocket |
| 查询慢 | 数据量大或索引缺失 | 优化查询或添加索引 |
| 内存溢出 | 结果集太大 | 使用流式处理或分页 |
附录
参考资料
依赖配置
toml
[dependencies]
# TDengine 连接器
taos = "0.12"
# 异步运行时
tokio = { version = "1", features = ["full"] }
futures = "0.3"
# 序列化
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# 连接池(可选)
deadpool = "0.10"
# 或
r2d2 = "0.8"
# 错误处理
thiserror = "1"
anyhow = "1"
# 时间处理
chrono = "0.4"
# 日志
tracing = "0.1"
tracing-subscriber = "0.3"
版本兼容性矩阵
| taos 版本 | TDengine Server | Rust 版本 | 关键特性 |
|---|---|---|---|
| v0.12.x | 3.3.0.0+ | 1.70+ | VARBINARY、GEOMETRY、性能优化 |
| v0.12.0 | 3.2.3.0+ | 1.70+ | WebSocket 压缩支持 |
| v0.11.x | 3.2.0.0+ | 1.70+ | TMQ 功能优化 |
| v0.10.x | 3.1.0.0+ | 1.65+ | WebSocket endpoint 变更 |
| v0.9.x | 3.0.7.0+ | 1.65+ | STMT WebSocket 支持 |
关于 TDengine
TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。