
TDengine Go 语言连接器进阶指南
本指南面向已经掌握 TDengine Go 连接器基础用法的开发者,深入介绍高级特性、性能优化技巧和最佳实践。
目录
连接管理最佳实践
1. 选择合适的连接方式
原生连接 vs WebSocket 连接
原生连接(taosSql)
- ✅ 优势:性能更高,延迟更低
- ✅ 适用场景:高频写入、低延迟查询、大数据量场景
- ❌ 限制:需要安装 TDengine 客户端驱动
WebSocket 连接(taosWS)
- ✅ 优势:跨平台兼容性好,无需安装客户端
- ✅ 适用场景:跨平台部署、无法安装客户端、轻量级应用
- ❌ 限制:性能略低于原生连接
go
// 高性能场景 - 使用原生连接
import _ "github.com/taosdata/driver-go/v3/taosSql"
db, err := sql.Open("taosSql", "root:taosdata@tcp(localhost:6030)/")
// 跨平台场景 - 使用 WebSocket 连接
import _ "github.com/taosdata/driver-go/v3/taosWS"
db, err := sql.Open("taosWS", "root:taosdata@ws(localhost:6041)/")
2. 连接参数优化
原生连接参数调优
go
// 优化 CGO 线程数,建议设置为 CPU 核心数的 1-2 倍
taosUri := "root:taosdata@tcp(localhost:6030)/test?cgoThread=16"
// 设置异步处理器池大小(默认 10000)
taosUri := "root:taosdata@tcp(localhost:6030)/test?cgoAsyncHandlerPoolSize=20000"
// 指定 taos.cfg 配置文件路径
taosUri := "root:taosdata@tcp(localhost:6030)/test?cfg=/etc/taos"
// 设置连接时区(v3.7.4+)
taosUri := "root:taosdata@tcp(localhost:6030)/test?timezone=Asia%2FShanghai"
WebSocket 连接参数调优
go
// 启用压缩传输(适用于大数据量场景)
taosUri := "root:taosdata@ws(localhost:6041)/test?enableCompression=true"
// 调整超时时间
taosUri := "root:taosdata@ws(localhost:6041)/test?readTimeout=10m&writeTimeout=30s"
// 使用 Bearer Token 认证(v3.7.8+)
taosUri := "root:taosdata@ws(localhost:6041)/test?bearerToken=your_token_here"
// 双因素认证(v3.7.8+)
taosUri := "root:taosdata@ws(localhost:6041)/test?totpCode=123456"
3. IPv6 支持(v3.7.1+)
go
// IPv6 地址需要用方括号括起来
taosUri := "root:taosdata@ws([::1]:6041)/test"
taosUri := "root:taosdata@tcp([2001:db8::1]:6030)/test"
4. 密码包含特殊字符处理
go
import "net/url"
password := "p@ssw0rd!#$"
escapedPassword := url.QueryEscape(password)
taosUri := fmt.Sprintf("root:%s@tcp(localhost:6030)/", escapedPassword)
高性能数据写入
1. 批量写入策略
使用事务批量提交
go
func batchInsert(db *sql.DB, data []Record) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
stmt, err := tx.Prepare("INSERT INTO meters VALUES (?, ?, ?, ?)")
if err != nil {
return err
}
defer stmt.Close()
for _, record := range data {
_, err := stmt.Exec(record.Ts, record.Current, record.Voltage, record.Phase)
if err != nil {
return err
}
}
return tx.Commit()
}
使用多值插入语句
go
func multiValueInsert(db *sql.DB, data []Record) error {
// 构建多值插入语句(推荐批次大小:1000-10000 条)
batchSize := 5000
for i := 0; i < len(data); i += batchSize {
end := i + batchSize
if end > len(data) {
end = len(data)
}
var values []string
for _, record := range data[i:end] {
values = append(values,
fmt.Sprintf("('%s', %f, %d, %f)",
record.Ts.Format(time.RFC3339Nano),
record.Current,
record.Voltage,
record.Phase))
}
sql := fmt.Sprintf("INSERT INTO meters VALUES %s",
strings.Join(values, ","))
_, err := db.Exec(sql)
if err != nil {
return err
}
}
return nil
}
2. Stmt2 高性能参数绑定(v3.6.0+)
Stmt2 提供了更高效的参数绑定方式,适合大批量数据写入。
go
import (
"github.com/taosdata/driver-go/v3/af"
"github.com/taosdata/driver-go/v3/common/stmt"
)
func stmt2BatchInsert(conn *af.Connector) error {
// 创建 stmt2(singleTableBindOnce=true 表示单表单次绑定)
stmt2 := conn.Stmt2(0, true)
defer stmt2.Close()
// 准备 SQL
err := stmt2.Prepare("INSERT INTO ? USING meters TAGS(?) VALUES(?, ?, ?, ?)")
if err != nil {
return err
}
// 准备绑定数据
bindData := make([]*stmt.TaosStmt2BindData, 1)
bindData[0] = stmt.NewTaosStmt2BindData()
// 设置表名
bindData[0].SetTableName("d001")
// 设置 TAG 值
bindData[0].AddTag(stmt.NewStmt2Field(common.TSDB_DATA_TYPE_INT, 4))
bindData[0].TagValues[0].SetInt32(1)
// 设置列值(批量)
timestamps := []time.Time{
time.Now(),
time.Now().Add(time.Second),
time.Now().Add(2 * time.Second),
}
currents := []float32{10.2, 10.3, 10.4}
voltages := []int32{220, 221, 222}
phases := []float32{0.31, 0.32, 0.33}
bindData[0].AddColumn(stmt.NewStmt2Field(common.TSDB_DATA_TYPE_TIMESTAMP, 8))
bindData[0].AddColumn(stmt.NewStmt2Field(common.TSDB_DATA_TYPE_FLOAT, 4))
bindData[0].AddColumn(stmt.NewStmt2Field(common.TSDB_DATA_TYPE_INT, 4))
bindData[0].AddColumn(stmt.NewStmt2Field(common.TSDB_DATA_TYPE_FLOAT, 4))
for i := 0; i < len(timestamps); i++ {
bindData[0].ColumnValues[0].AddTimestamp(timestamps[i], common.PrecisionMilliSecond)
bindData[0].ColumnValues[1].AddFloat(currents[i])
bindData[0].ColumnValues[2].AddInt32(voltages[i])
bindData[0].ColumnValues[3].AddFloat(phases[i])
}
// 绑定数据
err = stmt2.Bind(bindData)
if err != nil {
return err
}
// 执行
err = stmt2.Execute()
if err != nil {
return err
}
// 获取影响行数
affected := stmt2.GetAffectedRows()
fmt.Printf("Inserted %d rows\n", affected)
return nil
}
3. 无模式写入(Schemaless)
无模式写入适合数据模型动态变化或不确定的场景。
InfluxDB 行协议
go
import "github.com/taosdata/driver-go/v3/af"
func influxDBWrite(conn *af.Connector) error {
lines := []string{
"meters,location=beijing current=10.2,voltage=220,phase=0.31 1626006833639000000",
"meters,location=beijing current=10.3,voltage=221,phase=0.32 1626006834639000000",
}
// precision: "ns", "us", "ms", "s"
return conn.InfluxDBInsertLines(lines, "ns")
}
OpenTSDB Telnet 协议
go
func openTSDBTelnetWrite(conn *af.Connector) error {
lines := []string{
"put meters 1626006833 10.2 location=beijing",
"put meters 1626006834 10.3 location=beijing",
}
return conn.OpenTSDBInsertTelnetLines(lines)
}
OpenTSDB JSON 协议
go
func openTSDBJSONWrite(conn *af.Connector) error {
payload := `[
{
"metric": "meters",
"timestamp": 1626006833,
"value": 10.2,
"tags": {"location": "beijing"}
}
]`
return conn.OpenTSDBInsertJsonPayload(payload)
}
WebSocket 无模式写入(v3.3.1+)
go
import (
"github.com/taosdata/driver-go/v3/ws/schemaless"
)
func wsSchemalessWrite() error {
config := schemaless.NewConfig("ws://localhost:6041", schemaless.SetDb("test"))
s, err := schemaless.NewSchemaless(config)
if err != nil {
return err
}
defer s.Close()
lines := "meters,location=beijing current=10.2,voltage=220,phase=0.31 1626006833639000000"
// protocol: InfluxDBLineProtocol=1, OpenTSDBTelnetLineProtocol=2, OpenTSDBJsonFormatProtocol=3
// precision: "ns", "us", "ms", "s"
// ttl: 数据过期时间(天),0 表示不过期
// reqID: 请求 ID,用于链路追踪
err = s.Insert(lines, schemaless.InfluxDBLineProtocol, "ns", 0, 0)
return err
}
4. 并发写入模式
go
import "sync"
func concurrentWrite(db *sql.DB, data [][]Record, workers int) error {
var wg sync.WaitGroup
errChan := make(chan error, workers)
// 将数据分片
chunkSize := len(data) / workers
for i := 0; i < workers; i++ {
wg.Add(1)
go func(chunk []Record) {
defer wg.Done()
if err := batchInsert(db, chunk); err != nil {
errChan <- err
}
}(data[i*chunkSize : (i+1)*chunkSize])
}
wg.Wait()
close(errChan)
// 检查错误
for err := range errChan {
if err != nil {
return err
}
}
return nil
}
高性能数据查询
1. 查询结果集优化
使用流式读取大结果集
go
func streamQueryLargeResultSet(db *sql.DB) error {
rows, err := db.Query("SELECT * FROM meters WHERE ts >= ? AND ts < ?",
startTime, endTime)
if err != nil {
return err
}
defer rows.Close()
// 获取列信息
columns, err := rows.Columns()
if err != nil {
return err
}
// 创建扫描目标
values := make([]interface{}, len(columns))
valuePtrs := make([]interface{}, len(columns))
for i := range values {
valuePtrs[i] = &values[i]
}
// 流式处理每一行(不占用大量内存)
count := 0
for rows.Next() {
err := rows.Scan(valuePtrs...)
if err != nil {
return err
}
// 处理数据(可以写入文件、发送到消息队列等)
processRow(values)
count++
// 定期打印进度
if count%10000 == 0 {
fmt.Printf("Processed %d rows\n", count)
}
}
return rows.Err()
}
使用 ColumnTypes 获取元数据
go
func queryWithMetadata(db *sql.DB) error {
rows, err := db.Query("SELECT * FROM meters LIMIT 10")
if err != nil {
return err
}
defer rows.Close()
// 获取列类型信息
columnTypes, err := rows.ColumnTypes()
if err != nil {
return err
}
// 打印列元数据
for _, ct := range columnTypes {
fmt.Printf("Column: %s\n", ct.Name())
fmt.Printf(" Type: %s\n", ct.DatabaseTypeName())
fmt.Printf(" Go Type: %s\n", ct.ScanType())
if length, ok := ct.Length(); ok {
fmt.Printf(" Length: %d\n", length)
}
}
// 根据类型动态创建扫描目标
values := make([]interface{}, len(columnTypes))
for i, ct := range columnTypes {
values[i] = reflect.New(ct.ScanType()).Interface()
}
for rows.Next() {
err := rows.Scan(values...)
if err != nil {
return err
}
// 处理数据...
}
return rows.Err()
}
2. 使用 Context 进行超时控制
go
func queryWithTimeout(db *sql.DB) error {
// 设置 5 秒超时
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, "SELECT * FROM meters WHERE ts >= ? AND ts < ?",
startTime, endTime)
if err != nil {
if err == context.DeadlineExceeded {
return fmt.Errorf("query timeout: %w", err)
}
return err
}
defer rows.Close()
// 处理结果...
return nil
}
3. 链路追踪(Request ID)
使用 Request ID 可以在分布式系统中追踪请求链路。
go
func queryWithRequestID(db *sql.DB) error {
// 生成唯一的 Request ID
reqID := generateRequestID()
// 通过 Context 传递 Request ID
ctx := context.WithValue(context.Background(), "taos_req_id", reqID)
rows, err := db.QueryContext(ctx, "SELECT * FROM meters LIMIT 10")
if err != nil {
return err
}
defer rows.Close()
// 在日志中记录 Request ID,便于追踪
log.Printf("Query with request ID: %d\n", reqID)
// 处理结果...
return nil
}
func generateRequestID() int64 {
// 可以使用雪花算法或其他方式生成唯一 ID
return time.Now().UnixNano()
}
4. 查询优化技巧
go
// ❌ 避免:SELECT * 查询
rows, err := db.Query("SELECT * FROM meters")
// ✅ 推荐:只查询需要的列
rows, err := db.Query("SELECT ts, current, voltage FROM meters")
// ✅ 推荐:使用时间范围过滤
rows, err := db.Query("SELECT ts, current FROM meters WHERE ts >= ? AND ts < ?",
startTime, endTime)
// ✅ 推荐:使用 LIMIT 限制结果集大小
rows, err := db.Query("SELECT * FROM meters ORDER BY ts DESC LIMIT 1000")
// ✅ 推荐:使用降采样减少数据量
rows, err := db.Query("SELECT _wstart, AVG(current) FROM meters WHERE ts >= ? AND ts < ? INTERVAL(1h)",
startTime, endTime)
参数绑定进阶用法
1. Stmt 查询(v3.5.1+)
Stmt 不仅可以用于写入,还可以用于参数化查询。
go
import "github.com/taosdata/driver-go/v3/af"
func stmtQuery(conn *af.Connector) error {
stmt := conn.Stmt()
defer stmt.Close()
// 准备查询语句
err := stmt.Prepare("SELECT * FROM meters WHERE ts >= ? AND ts < ? AND current > ?")
if err != nil {
return err
}
// 创建参数
params := param.NewParam(3)
params.SetTimestamp(0, startTime, common.PrecisionMilliSecond)
params.SetTimestamp(1, endTime, common.PrecisionMilliSecond)
params.SetFloat(2, 10.0)
// 绑定参数
err = stmt.BindRow(params)
if err != nil {
return err
}
// 添加批次并执行
err = stmt.AddBatch()
if err != nil {
return err
}
err = stmt.Execute()
if err != nil {
return err
}
// 获取结果
rows, err := stmt.UseResult()
if err != nil {
return err
}
defer rows.Close()
// 处理结果...
return nil
}
2. WebSocket Stmt 写入
go
import (
"github.com/taosdata/driver-go/v3/ws/stmt"
"github.com/taosdata/driver-go/v3/common/param"
)
func wsStmtInsert(connector *stmt.Connector) error {
stmt, err := connector.Init()
if err != nil {
return err
}
defer stmt.Close()
// 准备语句
err = stmt.Prepare("INSERT INTO ? USING meters TAGS(?) VALUES(?, ?, ?, ?)")
if err != nil {
return err
}
// 设置表名
err = stmt.SetTableName("d001")
if err != nil {
return err
}
// 设置 TAG
tags := param.NewParam(1).AddInt(1)
tagTypes := param.NewColumnType(1).AddInt()
err = stmt.SetTags(tags, tagTypes)
if err != nil {
return err
}
// 绑定多行数据
for i := 0; i < 100; i++ {
params := param.NewParam(4)
params.AddTimestamp(time.Now().Add(time.Duration(i)*time.Second), common.PrecisionMilliSecond)
params.AddFloat(10.0 + float32(i)*0.1)
params.AddInt(220 + i)
params.AddFloat(0.31 + float32(i)*0.01)
bindTypes := param.NewColumnType(4)
bindTypes.AddTimestamp().AddFloat().AddInt().AddFloat()
err = stmt.BindParam([]*param.Param{params}, bindTypes)
if err != nil {
return err
}
err = stmt.AddBatch()
if err != nil {
return err
}
}
// 执行
err = stmt.Exec()
if err != nil {
return err
}
affected := stmt.GetAffectedRows()
fmt.Printf("Inserted %d rows\n", affected)
return nil
}
3. 链式调用构建参数
go
func chainParamBuilding() *param.Param {
// 使用链式调用构建参数(更简洁)
params := param.NewParam(4).
AddTimestamp(time.Now(), common.PrecisionMilliSecond).
AddFloat(10.2).
AddInt(220).
AddFloat(0.31)
return params
}
func chainColumnTypeBuilding() *param.ColumnType {
// 使用链式调用构建列类型
columnTypes := param.NewColumnType(4).
AddTimestamp().
AddFloat().
AddInt().
AddFloat()
return columnTypes
}
数据订阅深入
1. 消费者配置优化
go
import "github.com/taosdata/driver-go/v3/af/tmq"
func createOptimizedConsumer() (*tmq.Consumer, error) {
config := tmq.ConfigMap{
"group.id": "group_1",
"client.id": "client_1",
"td.connect.ip": "127.0.0.1",
"td.connect.port": "6030",
"td.connect.user": "root",
"td.connect.pass": "taosdata",
"msg.with.table.name": "true", // 包含表名
"enable.auto.commit": "true", // 自动提交偏移量
"auto.commit.interval.ms": "5000", // 自动提交间隔
"auto.offset.reset": "earliest", // 从最早位置开始消费
"experimental.snapshot.enable": "false", // 不消费快照数据
"timezone": "Asia/Shanghai", // 设置时区(v3.7.4+)
}
return tmq.NewConsumer(config)
}
2. WebSocket 消费者配置
go
import "github.com/taosdata/driver-go/v3/ws/tmq"
func createWSConsumer() (*tmq.Consumer, error) {
config := tmq.ConfigMap{
"ws.url": "ws://localhost:6041",
"ws.message.channelLen": "1000", // 消息通道缓存长度
"ws.message.timeout": "5m", // 消息超时时间
"ws.message.writeWait": "10s", // 写入超时时间
"ws.message.enableCompression": "true", // 启用压缩
"ws.autoReconnect": "true", // 自动重连
"ws.reconnectIntervalMs": "2000", // 重连间隔(毫秒)
"ws.reconnectRetryCount": "3", // 重连重试次数
"group.id": "group_1",
"client.id": "client_1",
"auto.offset.reset": "earliest",
"timezone": "Asia/Shanghai",
}
return tmq.NewConsumer(config)
}
3. 完整的消费示例
go
func consumeMessages(consumer *tmq.Consumer) error {
defer consumer.Close()
// 订阅主题
err := consumer.SubscribeTopics([]string{"topic_meters"}, nil)
if err != nil {
return err
}
defer consumer.Unsubscribe()
// 消费消息
for {
ev := consumer.Poll(500) // 500ms 超时
if ev == nil {
continue
}
switch e := ev.(type) {
case *tmq.DataMessage:
// 处理数据消息
fmt.Printf("Topic: %s, DB: %s\n", e.Topic(), e.DBName())
data := e.Value().([]*tmq.Data)
for _, d := range data {
fmt.Printf("Table: %s, Rows: %d\n", d.TableName, len(d.Data))
for _, row := range d.Data {
// 处理每一行数据
processRow(row)
}
}
// 手动提交偏移量(如果 enable.auto.commit=false)
// _, err := consumer.Commit()
// if err != nil {
// log.Printf("Failed to commit offset: %v", err)
// }
case tmq.Error:
// 处理错误
fmt.Printf("Error: %v\n", e)
if e.Code() == tmq.ErrConsumerClosed {
return fmt.Errorf("consumer closed")
}
}
}
}
4. 手动偏移量管理
go
func manualOffsetManagement(consumer *tmq.Consumer) error {
// 获取当前分配的分区
partitions, err := consumer.Assignment()
if err != nil {
return err
}
fmt.Println("Current assignment:")
for _, p := range partitions {
fmt.Printf(" Topic: %s, Partition: %d, Offset: %v\n",
*p.Topic, p.Partition, p.Offset)
}
// 获取已提交的偏移量
committed, err := consumer.Committed(partitions, 5000)
if err != nil {
return err
}
fmt.Println("Committed offsets:")
for _, p := range committed {
fmt.Printf(" Topic: %s, Partition: %d, Offset: %v\n",
*p.Topic, p.Partition, p.Offset)
}
// 获取当前消费位置
position, err := consumer.Position(partitions)
if err != nil {
return err
}
fmt.Println("Current position:")
for _, p := range position {
fmt.Printf(" Topic: %s, Partition: %d, Offset: %v\n",
*p.Topic, p.Partition, p.Offset)
}
// 手动跳转到指定偏移量
for i := range partitions {
partitions[i].Offset = tmq.Offset(1000) // 跳转到偏移量 1000
err = consumer.Seek(partitions[i], 5000)
if err != nil {
return err
}
}
return nil
}
5. 按时间戳消费(v3.0.5.0+)
go
func seekToTimestamp(consumer *tmq.Consumer, timestamp int64) error {
partitions, err := consumer.Assignment()
if err != nil {
return err
}
// 将偏移量设置为时间戳
for i := range partitions {
partitions[i].Offset = tmq.Offset(timestamp)
err = consumer.Seek(partitions[i], 5000)
if err != nil {
return err
}
}
fmt.Printf("Seeked to timestamp: %d\n", timestamp)
return nil
}
连接池管理
1. 使用 database/sql 连接池
Go 的 database/sql 包自带连接池管理,合理配置可以提升性能。
go
func setupConnectionPool(db *sql.DB) {
// 设置最大打开连接数
db.SetMaxOpenConns(100)
// 设置最大空闲连接数
db.SetMaxIdleConns(10)
// 设置连接最大生命周期
db.SetConnMaxLifetime(time.Hour)
// 设置连接最大空闲时间
db.SetConnMaxIdleTime(10 * time.Minute)
}
func getOptimizedConnection() (*sql.DB, error) {
db, err := sql.Open("taosSql", "root:taosdata@tcp(localhost:6030)/test")
if err != nil {
return nil, err
}
setupConnectionPool(db)
// 测试连接
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := db.PingContext(ctx); err != nil {
db.Close()
return nil, err
}
return db, nil
}
2. 连接池监控
go
func monitorConnectionPool(db *sql.DB) {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for range ticker.C {
stats := db.Stats()
fmt.Printf("Connection Pool Stats:\n")
fmt.Printf(" MaxOpenConnections: %d\n", stats.MaxOpenConnections)
fmt.Printf(" OpenConnections: %d\n", stats.OpenConnections)
fmt.Printf(" InUse: %d\n", stats.InUse)
fmt.Printf(" Idle: %d\n", stats.Idle)
fmt.Printf(" WaitCount: %d\n", stats.WaitCount)
fmt.Printf(" WaitDuration: %v\n", stats.WaitDuration)
fmt.Printf(" MaxIdleClosed: %d\n", stats.MaxIdleClosed)
fmt.Printf(" MaxLifetimeClosed: %d\n", stats.MaxLifetimeClosed)
}
}
错误处理与重试策略
1. TDengine 错误码解析
go
import "github.com/taosdata/driver-go/v3/errors"
func handleTDengineError(err error) {
if err == nil {
return
}
// 判断是否是 TDengine 错误
tError, is := err.(*errors.TaosError)
if is {
errorCode := int(tError.Code)
errorMessage := tError.ErrStr
fmt.Printf("TDengine Error - Code: %d, Message: %s\n", errorCode, errorMessage)
// 根据错误码进行不同处理
switch errorCode {
case 0x2603: // 表不存在
fmt.Println("Table does not exist, creating...")
// 创建表逻辑
case 0x260D: // 数据库不存在
fmt.Println("Database does not exist, creating...")
// 创建数据库逻辑
case 0x2602: // 表已存在
fmt.Println("Table already exists, ignoring...")
case 0x0860: // 连接超时
fmt.Println("Connection timeout, retrying...")
// 重试逻辑
default:
fmt.Printf("Unhandled error code: 0x%X\n", errorCode)
}
} else {
// 其他错误
fmt.Printf("General error: %v\n", err)
}
}
2. 指数退避重试策略
go
func executeWithRetry(operation func() error, maxRetries int) error {
var err error
baseDelay := time.Second
for i := 0; i < maxRetries; i++ {
err = operation()
if err == nil {
return nil
}
// 判断是否是可重试的错误
if !isRetryableError(err) {
return err
}
// 指数退避
delay := baseDelay * time.Duration(1<<uint(i))
if delay > 30*time.Second {
delay = 30 * time.Second
}
fmt.Printf("Operation failed (attempt %d/%d), retrying in %v: %v\n",
i+1, maxRetries, delay, err)
time.Sleep(delay)
}
return fmt.Errorf("operation failed after %d retries: %w", maxRetries, err)
}
func isRetryableError(err error) bool {
if tError, ok := err.(*errors.TaosError); ok {
errorCode := int(tError.Code)
// 连接相关错误、超时错误等可以重试
retryableCodes := []int{0x0860, 0x0861, 0x0862, 0x2301, 0x2302}
for _, code := range retryableCodes {
if errorCode == code {
return true
}
}
}
// 网络错误也可以重试
if err == context.DeadlineExceeded || err == context.Canceled {
return true
}
return false
}
3. 断路器模式
go
type CircuitBreaker struct {
maxFailures int
resetTimeout time.Duration
failures int
lastFailTime time.Time
state string // "closed", "open", "half-open"
mu sync.Mutex
}
func NewCircuitBreaker(maxFailures int, resetTimeout time.Duration) *CircuitBreaker {
return &CircuitBreaker{
maxFailures: maxFailures,
resetTimeout: resetTimeout,
state: "closed",
}
}
func (cb *CircuitBreaker) Call(operation func() error) error {
cb.mu.Lock()
// 检查断路器状态
if cb.state == "open" {
if time.Since(cb.lastFailTime) > cb.resetTimeout {
cb.state = "half-open"
cb.failures = 0
} else {
cb.mu.Unlock()
return fmt.Errorf("circuit breaker is open")
}
}
cb.mu.Unlock()
// 执行操作
err := operation()
cb.mu.Lock()
defer cb.mu.Unlock()
if err != nil {
cb.failures++
cb.lastFailTime = time.Now()
if cb.failures >= cb.maxFailures {
cb.state = "open"
}
return err
}
// 操作成功
if cb.state == "half-open" {
cb.state = "closed"
}
cb.failures = 0
return nil
}
性能监控与调优
1. 查询性能监控
go
func monitorQueryPerformance(db *sql.DB, query string, args ...interface{}) (*sql.Rows, time.Duration, error) {
start := time.Now()
rows, err := db.Query(query, args...)
duration := time.Since(start)
if err != nil {
log.Printf("Query failed in %v: %v\n", duration, err)
return nil, duration, err
}
log.Printf("Query executed in %v\n", duration)
// 如果查询时间超过阈值,记录慢查询
if duration > 1*time.Second {
log.Printf("SLOW QUERY (%v): %s\n", duration, query)
}
return rows, duration, nil
}
2. 写入性能监控
go
type WriteMetrics struct {
TotalRows int64
SuccessRows int64
FailedRows int64
TotalDuration time.Duration
AvgLatency time.Duration
}
func monitorWritePerformance(writeFunc func() (int64, error)) (*WriteMetrics, error) {
metrics := &WriteMetrics{}
start := time.Now()
rows, err := writeFunc()
metrics.TotalDuration = time.Since(start)
if err != nil {
metrics.FailedRows = rows
return metrics, err
}
metrics.SuccessRows = rows
metrics.TotalRows = rows
if rows > 0 {
metrics.AvgLatency = metrics.TotalDuration / time.Duration(rows)
}
// 计算吞吐量(行/秒)
throughput := float64(rows) / metrics.TotalDuration.Seconds()
log.Printf("Write Performance:\n")
log.Printf(" Total Rows: %d\n", metrics.TotalRows)
log.Printf(" Success Rows: %d\n", metrics.SuccessRows)
log.Printf(" Failed Rows: %d\n", metrics.FailedRows)
log.Printf(" Total Duration: %v\n", metrics.TotalDuration)
log.Printf(" Avg Latency: %v\n", metrics.AvgLatency)
log.Printf(" Throughput: %.2f rows/sec\n", throughput)
return metrics, nil
}
3. 使用 pprof 进行性能分析
go
import (
"net/http"
_ "net/http/pprof"
)
func enableProfiling() {
// 启动 pprof HTTP 服务器
go func() {
log.Println("Starting pprof server on :6060")
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
}
// 使用方法:
// 1. 启动程序后访问 http://localhost:6060/debug/pprof/
// 2. 使用 go tool pprof 进行分析:
// go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
// go tool pprof http://localhost:6060/debug/pprof/heap
安全最佳实践
1. 安全的凭证管理
go
import "os"
func getSecureConnection() (*sql.DB, error) {
// ❌ 避免:硬编码凭证
// dsn := "root:taosdata@tcp(localhost:6030)/"
// ✅ 推荐:从环境变量读取
host := os.Getenv("TDENGINE_HOST")
port := os.Getenv("TDENGINE_PORT")
user := os.Getenv("TDENGINE_USER")
pass := os.Getenv("TDENGINE_PASSWORD")
db := os.Getenv("TDENGINE_DB")
if host == "" || port == "" || user == "" || pass == "" {
return nil, fmt.Errorf("missing required environment variables")
}
// 密码包含特殊字符需要转义
escapedPass := url.QueryEscape(pass)
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", user, escapedPass, host, port, db)
return sql.Open("taosSql", dsn)
}
2. SQL 注入防护
go
// ❌ 危险:字符串拼接 SQL(容易 SQL 注入)
func unsafeQuery(db *sql.DB, tableName string) error {
query := fmt.Sprintf("SELECT * FROM %s", tableName)
_, err := db.Query(query)
return err
}
// ✅ 推荐:使用参数化查询
func safeQuery(db *sql.DB, startTime, endTime time.Time, minCurrent float32) error {
query := "SELECT * FROM meters WHERE ts >= ? AND ts < ? AND current > ?"
_, err := db.Query(query, startTime, endTime, minCurrent)
return err
}
// ✅ 推荐:使用白名单验证表名/列名
func validateIdentifier(name string) bool {
// 只允许字母、数字、下划线
matched, _ := regexp.MatchString("^[a-zA-Z0-9_]+$", name)
return matched
}
3. TLS/SSL 连接(WebSocket)
go
// 使用 wss:// 协议进行加密连接
func secureWSConnection() (*sql.DB, error) {
dsn := "root:taosdata@wss(your-domain.com:443)/test"
return sql.Open("taosWS", dsn)
}
// 跳过 SSL 证书检查(仅开发环境,生产环境不推荐)
func insecureWSConnection() (*sql.DB, error) {
dsn := "root:taosdata@ws(localhost:6041)/test?skipVerify=true"
return sql.Open("taosWS", dsn)
}
并发编程
1. 并发查询
go
func concurrentQuery(db *sql.DB, queries []string) ([][]interface{}, error) {
var wg sync.WaitGroup
results := make([][]interface{}, len(queries))
errChan := make(chan error, len(queries))
for i, query := range queries {
wg.Add(1)
go func(index int, q string) {
defer wg.Done()
rows, err := db.Query(q)
if err != nil {
errChan <- err
return
}
defer rows.Close()
var result []interface{}
for rows.Next() {
var data interface{}
if err := rows.Scan(&data); err != nil {
errChan <- err
return
}
result = append(result, data)
}
results[index] = result
}(i, query)
}
wg.Wait()
close(errChan)
// 检查错误
for err := range errChan {
if err != nil {
return nil, err
}
}
return results, nil
}
2. 使用 Worker Pool 模式
go
type Job struct {
ID int
Data []Record
}
type Result struct {
JobID int
Err error
}
func workerPool(db *sql.DB, jobs <-chan Job, results chan<- Result, numWorkers int) {
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
err := batchInsert(db, job.Data)
results <- Result{JobID: job.ID, Err: err}
}
}()
}
wg.Wait()
close(results)
}
func processWithWorkerPool(db *sql.DB, allData [][]Record) error {
numWorkers := 10
jobs := make(chan Job, len(allData))
results := make(chan Result, len(allData))
// 启动 worker pool
go workerPool(db, jobs, results, numWorkers)
// 发送任务
for i, data := range allData {
jobs <- Job{ID: i, Data: data}
}
close(jobs)
// 收集结果
for i := 0; i < len(allData); i++ {
result := <-results
if result.Err != nil {
return fmt.Errorf("job %d failed: %w", result.JobID, result.Err)
}
}
return nil
}
3. 使用 Context 协调 Goroutine
go
func coordinated ProcessingWithContext(ctx context.Context, db *sql.DB) error {
var wg sync.WaitGroup
errChan := make(chan error, 1)
// Worker 1: 数据写入
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-ctx.Done():
log.Println("Write worker cancelled")
return
default:
// 执行写入操作
err := writeData(db)
if err != nil {
select {
case errChan <- err:
default:
}
return
}
time.Sleep(100 * time.Millisecond)
}
}
}()
// Worker 2: 数据查询
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-ctx.Done():
log.Println("Query worker cancelled")
return
default:
// 执行查询操作
err := queryData(db)
if err != nil {
select {
case errChan <- err:
default:
}
return
}
time.Sleep(1 * time.Second)
}
}
}()
// 等待完成或错误
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
select {
case <-done:
return nil
case err := <-errChan:
return err
case <-ctx.Done():
return ctx.Err()
}
}
生产环境部署建议
1. 配置文件管理
go
import (
"encoding/json"
"os"
)
type Config struct {
TDengine struct {
Host string `json:"host"`
Port int `json:"port"`
User string `json:"user"`
Password string `json:"password"`
Database string `json:"database"`
MaxOpenConns int `json:"max_open_conns"`
MaxIdleConns int `json:"max_idle_conns"`
ConnMaxLifetime string `json:"conn_max_lifetime"`
} `json:"tdengine"`
}
func loadConfig(configPath string) (*Config, error) {
file, err := os.Open(configPath)
if err != nil {
return nil, err
}
defer file.Close()
config := &Config{}
decoder := json.NewDecoder(file)
err = decoder.Decode(config)
if err != nil {
return nil, err
}
return config, nil
}
func createConnectionFromConfig(config *Config) (*sql.DB, error) {
escapedPass := url.QueryEscape(config.TDengine.Password)
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s",
config.TDengine.User,
escapedPass,
config.TDengine.Host,
config.TDengine.Port,
config.TDengine.Database)
db, err := sql.Open("taosSql", dsn)
if err != nil {
return nil, err
}
db.SetMaxOpenConns(config.TDengine.MaxOpenConns)
db.SetMaxIdleConns(config.TDengine.MaxIdleConns)
lifetime, _ := time.ParseDuration(config.TDengine.ConnMaxLifetime)
db.SetConnMaxLifetime(lifetime)
return db, nil
}
2. 结构化日志
go
import "go.uber.org/zap"
var logger *zap.Logger
func initLogger() {
var err error
logger, err = zap.NewProduction()
if err != nil {
panic(err)
}
}
func queryWithLogging(db *sql.DB, query string, args ...interface{}) (*sql.Rows, error) {
start := time.Now()
reqID := generateRequestID()
logger.Info("Executing query",
zap.Int64("request_id", reqID),
zap.String("query", query),
zap.Any("args", args))
rows, err := db.Query(query, args...)
duration := time.Since(start)
if err != nil {
logger.Error("Query failed",
zap.Int64("request_id", reqID),
zap.Duration("duration", duration),
zap.Error(err))
return nil, err
}
logger.Info("Query succeeded",
zap.Int64("request_id", reqID),
zap.Duration("duration", duration))
return rows, nil
}
3. 优雅关闭
go
func gracefulShutdown(db *sql.DB, consumer *tmq.Consumer, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
done := make(chan error, 1)
go func() {
var errs []error
// 关闭消费者
if consumer != nil {
logger.Info("Closing TMQ consumer...")
if err := consumer.Close(); err != nil {
errs = append(errs, fmt.Errorf("failed to close consumer: %w", err))
}
}
// 关闭数据库连接
if db != nil {
logger.Info("Closing database connection...")
if err := db.Close(); err != nil {
errs = append(errs, fmt.Errorf("failed to close database: %w", err))
}
}
if len(errs) > 0 {
done <- fmt.Errorf("shutdown errors: %v", errs)
} else {
done <- nil
}
}()
select {
case err := <-done:
return err
case <-ctx.Done():
return fmt.Errorf("shutdown timeout after %v", timeout)
}
}
func main() {
// 初始化资源...
db, _ := sql.Open("taosSql", "...")
consumer, _ := tmq.NewConsumer(...)
// 监听信号
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
// 等待信号
<-sigCh
logger.Info("Received shutdown signal")
// 优雅关闭
if err := gracefulShutdown(db, consumer, 30*time.Second); err != nil {
logger.Error("Shutdown error", zap.Error(err))
os.Exit(1)
}
logger.Info("Shutdown complete")
}
4. 健康检查
go
func healthCheck(db *sql.DB) error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
// 1. 检查数据库连接
if err := db.PingContext(ctx); err != nil {
return fmt.Errorf("database ping failed: %w", err)
}
// 2. 执行简单查询验证
var result int
err := db.QueryRowContext(ctx, "SELECT 1").Scan(&result)
if err != nil {
return fmt.Errorf("test query failed: %w", err)
}
// 3. 检查连接池状态
stats := db.Stats()
if stats.OpenConnections == 0 {
return fmt.Errorf("no open connections")
}
return nil
}
func healthCheckHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if err := healthCheck(db); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(map[string]string{
"status": "unhealthy",
"error": err.Error(),
})
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"status": "healthy",
})
}
}
5. 指标收集(Prometheus)
go
import "github.com/prometheus/client_golang/prometheus"
var (
queryDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "tdengine_query_duration_seconds",
Help: "Query duration in seconds",
},
[]string{"query_type"},
)
writeCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "tdengine_write_total",
Help: "Total number of writes",
},
[]string{"status"},
)
)
func init() {
prometheus.MustRegister(queryDuration)
prometheus.MustRegister(writeCounter)
}
func monitoredQuery(db *sql.DB, query string) (*sql.Rows, error) {
start := time.Now()
rows, err := db.Query(query)
duration := time.Since(start).Seconds()
queryDuration.WithLabelValues("select").Observe(duration)
return rows, err
}
func monitoredWrite(db *sql.DB, data []Record) error {
err := batchInsert(db, data)
if err != nil {
writeCounter.WithLabelValues("error").Inc()
} else {
writeCounter.WithLabelValues("success").Add(float64(len(data)))
}
return err
}
总结
本进阶指南涵盖了 TDengine Go 连接器的高级用法和最佳实践:
- 连接管理:选择合适的连接方式,优化连接参数
- 高性能写入:批量写入、Stmt2、无模式写入、并发写入
- 高性能查询:流式读取、超时控制、链路追踪、查询优化
- 参数绑定:Stmt 查询、WebSocket Stmt、链式调用
- 数据订阅:消费者配置优化、偏移量管理、时间戳消费
- 连接池:连接池配置、监控
- 错误处理:错误码解析、重试策略、断路器模式
- 性能监控:查询监控、写入监控、pprof 分析
- 安全实践:凭证管理、SQL 注入防护、TLS 连接
- 并发编程:并发查询、Worker Pool、Context 协调
- 生产部署:配置管理、结构化日志、优雅关闭、健康检查、指标收集
通过掌握这些高级特性和最佳实践,您可以构建高性能、高可用、易维护的 TDengine 应用程序。
参考资源
关于 TDengine
TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。