TDengine Rust 连接器进阶指南

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 提供实时分析、可视化、事件管理与报警等功能。

相关推荐
二哈喇子!17 小时前
MySQL数据库操作命令【SQL语言】
数据库·sql·视图与索引
China_Yanhy17 小时前
AWS S3 深度配置指南:每一栏每个选项有什么作用
java·数据库·aws
YangYang9YangYan17 小时前
中专大数据技术专业学习数据分析的价值分析
大数据·学习·数据分析
yong999017 小时前
基于MATLAB的大变形悬臂梁求解程序
前端·数据库·matlab
九河云17 小时前
数据驱动未来,华为云DWS为智能决策提速
大数据·人工智能·安全·机器学习·华为云
施嘉伟18 小时前
Oracle SQL Profile 固化执行计划实战说明
数据库·sql·oracle
兆龙电子单片机设计18 小时前
【STM32项目开源】STM32单片机智能语音家居控制系统
stm32·单片机·嵌入式硬件·物联网·开源·自动化
Dr.Alex Wang18 小时前
Google Firebase 实战教学 - Streamlit、Bucket、Firebase
数据库·python·安全·googlecloud