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

相关推荐
躺柒18 分钟前
读数字时代的网络风险管理:策略、计划与执行04风险指引体系
大数据·网络·信息安全·数字化·网络管理·网络风险管理
橘子1322 分钟前
MySQL用户管理(十三)
数据库·mysql
Dxy123931021623 分钟前
MySQL如何加唯一索引
android·数据库·mysql
我真的是大笨蛋28 分钟前
深度解析InnoDB如何保障Buffer与磁盘数据一致性
java·数据库·sql·mysql·性能优化
怣5028 分钟前
MySQL数据检索入门:从零开始学SELECT查询
数据库·mysql
shengli72230 分钟前
机器学习与人工智能
jvm·数据库·python
2301_7657031438 分钟前
Python迭代器(Iterator)揭秘:for循环背后的故事
jvm·数据库·python
倔强的石头1061 小时前
关键信息基础设施的数据库选型:高可用、全链路安全与平滑替代的技术实践
数据库·安全·金仓数据库
人道领域1 小时前
javaWeb从入门到进阶(SpringBoot事务管理及AOP)
java·数据库·mysql
煎蛋学姐1 小时前
SSM音乐播放软件的开发与实现7g5j0(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm 框架·javaweb 开发·前后端开发