快速上手 - Go
快速上手 - Go
依赖与安装
Go 包 github.com/apache/tsfile/go/tsfile 是基于 C++ 实现的 cgo 绑定,因此必须先有一份 C/C++ 构建产物,Go 代码才能编译。获取方式有两种:
- 安装我们发布的 C/C++ 包或 SDK,无需自行构建 C++,适用于 Linux、macOS 和 Windows。
- 从源码构建 C++ 库,适用于需要修改 TsFile 或所在平台没有发布包的情况。
两种方式下 Go 模块都从 GitHub 获取,目前尚未发布到模块代理。
依赖
- Go >= 1.22,且
CGO_ENABLED=1 - C/C++ 编译工具链和 CMake,仅在从源码构建原生库时需要
- JDK >= 17 和 Maven,仅在从源码构建原生库时需要
- Windows 上受支持的 cgo 工具链为 MinGW-w64
方式一:使用已发布的 C/C++ 包(Linux、macOS、Windows)
按下载与安装页面安装运行库和开发包,可以是 SDK 压缩包、DEB/RPM 包、Homebrew Formula 或 Windows ZIP。头文件安装在 include/tsfile 子目录下,因此需要把 cgo 的头文件路径指向该目录,库路径指向安装前缀的库目录:
export TSFILE_HOME=/path/to/tsfile-sdk # 或:$(brew --prefix local/tsfile250/tsfile)
export CGO_CFLAGS="-I$TSFILE_HOME/include/tsfile"
export CGO_LDFLAGS="-L$TSFILE_HOME/lib -Wl,-rpath,$TSFILE_HOME/lib"其中 -rpath 让编译出的程序在运行时能找到 libtsfile;如果共享库安装在系统标准目录下可以去掉。对于安装在 /usr 下的系统包,链接器无需 -L 就能找到库,但头文件仍需要 -I/usr/include/tsfile。这些环境变量会追加到包内自带的编译参数之后,而包内参数指向源码检出的 cpp/target/build 目录;使用已安装的包时该目录并不存在,链接器会打印一条搜索路径警告,可以忽略。
Windows 上解压 ZIP 后,在 PowerShell 中设置同样的变量即可。ZIP 内含 bin\tsfile.dll 和 lib\tsfile.lib 导入库, MinGW 的链接器可以直接使用它,-ltsfile 照常通过 -L 找到:
$env:TSFILE_HOME = "C:\path\to\tsfile-2.5.0-windows-x86_64"
$env:CGO_CFLAGS = "-I$env:TSFILE_HOME\include\tsfile"
$env:CGO_LDFLAGS = "-L$env:TSFILE_HOME\lib -ltsfile"
$env:Path = "$env:TSFILE_HOME\bin;$env:Path"Windows 没有运行时的库搜索路径,编译出的程序通过 PATH 查找 tsfile.dll:保持 bin 在 PATH 中,或者把 tsfile.dll 复制到可执行文件旁边。该 DLL 静态链接了 C 运行库,只依赖系统库,因此不需要安装 Visual C++ 运行库。
cgo 只在编译该包时读取这些变量,因此需要在每个构建程序的终端里设置,或者用 go env -w CGO_CFLAGS=... CGO_LDFLAGS=... 记录下来。运行已经编译好的程序时不需要它们。
方式二:从源码构建原生库
克隆源码,并在仓库根目录构建 C++ 库:
git clone https://github.com/apache/tsfile.git
cd tsfile
./mvnw -P with-cpp -DskipTests package构建完成后会在 cpp/target/build/include 生成头文件、在 cpp/target/build/lib 生成库,Go 包默认就链接这里。
Windows 上需要使用 MinGW 构建。MinGW-w64 既是本项目在 Windows 上默认的 C++ 生成器,也是 cgo 编译 Go 包所用的编译器,因此 -Dcpp.toolchain=mingw 构建出的 DLL 和导入库可以直接配合 Go 工具链使用。源码构建会把 tsfile.dll 输出到 cpp\target\build\lib(而不是发布包的 bin 目录),因此除了编译器所在的 MinGW-w64 bin 目录外,还要把该目录加入 PATH(或把 DLL 复制到可执行文件旁边):
.\mvnw.cmd -P with-cpp -Dcpp.toolchain=mingw -DskipTests package
$env:Path = "$PWD\cpp\target\build\lib;C:\mingw64\bin;$env:Path"每打开一个新的终端,都要在构建或运行 Go 程序前执行上面的 PATH 设置。
在项目中使用该模块
使用已安装的包(方式一)时,可以直接在你的 Go 项目中拉取模块:
go get github.com/apache/tsfile/go@develop
go mod tidy模块目前还没有发布 tag,@develop 会解析成分支头部的伪版本号。为了构建可复现,请把该伪版本号记录在 go.mod 中,或者固定到某个具体 commit。
使用源码构建(方式二)时,用 replace 指向检出目录下的 go 目录,Go 包就会使用它旁边构建好的头文件和库:
go mod edit -replace github.com/apache/tsfile/go=/path/to/tsfile/go
go get github.com/apache/tsfile/go如果想直接运行自带的示例,在检出目录的 go 目录下执行:
cd go
go run ./examples/table_read_write写入流程
构造 Writer
schema := tsfile.TableSchema{
Table: "table1",
Columns: []tsfile.ColumnSchema{
{Name: "id1", DataType: tsfile.DataTypeString, Category: tsfile.ColumnCategoryTag},
{Name: "id2", DataType: tsfile.DataTypeString, Category: tsfile.ColumnCategoryTag},
{Name: "s1", DataType: tsfile.DataTypeInt32, Category: tsfile.ColumnCategoryField},
{Name: "s2", DataType: tsfile.DataTypeBoolean, Category: tsfile.ColumnCategoryField},
},
}
writer, err := tsfile.NewWriter("test.tsfile", schema, tsfile.WithMemoryThreshold(10*1024*1024))
if err != nil {
return err
}
defer writer.Close()NewWriter 会创建或截断文件。WithMemoryThreshold 是可选参数,默认为 128 MB。
写入数据
tablet, err := tsfile.NewTablet([]tsfile.TabletColumn{
{Name: "id1", DataType: tsfile.DataTypeString},
{Name: "id2", DataType: tsfile.DataTypeString},
{Name: "s1", DataType: tsfile.DataTypeInt32},
{Name: "s2", DataType: tsfile.DataTypeBoolean},
}, 5)
if err != nil {
return err
}
defer tablet.Close()
for row := 0; row < 5; row++ {
if err := tablet.AddTimestamp(row, int64(row)); err != nil {
return err
}
tablet.SetString(row, 0, "id1_filed_1")
tablet.SetString(row, 1, "id2_filed_1")
tablet.SetInt32(row, 2, int32(row))
tablet.SetBool(row, 3, true)
}
if err := writer.WriteTableTablet(tablet); err != nil {
return err
}Tablet 的行和列下标都从 0 开始。每个 setter 都会校验下标和列类型,当下标越界或写入值与声明类型不符时返回错误,应与 AddTimestamp 一样检查这些错误。
也可以通过 Arrow record batch 或 table 写入数据。其 schema 必须包含 int64 或 timestamp[ns](不带时区)类型的 time 列,其后紧跟表 schema 中的全部列:
arrowSchema := arrow.NewSchema([]arrow.Field{
{Name: "time", Type: arrow.PrimitiveTypes.Int64},
{Name: "id1", Type: arrow.BinaryTypes.String},
{Name: "id2", Type: arrow.BinaryTypes.String},
{Name: "s1", Type: arrow.PrimitiveTypes.Int32},
{Name: "s2", Type: arrow.FixedWidthTypes.Boolean},
}, nil)
builder := array.NewRecordBuilder(memory.DefaultAllocator, arrowSchema)
defer builder.Release()
timeBuilder := builder.Field(0).(*array.Int64Builder)
id1Builder := builder.Field(1).(*array.StringBuilder)
id2Builder := builder.Field(2).(*array.StringBuilder)
s1Builder := builder.Field(3).(*array.Int32Builder)
s2Builder := builder.Field(4).(*array.BooleanBuilder)
for row := 5; row < 10; row++ {
timeBuilder.Append(int64(row))
id1Builder.Append("id1_filed_1")
id2Builder.Append("id2_filed_1")
s1Builder.Append(int32(row))
s2Builder.Append(true)
}
record := builder.NewRecord()
defer record.Release()
if err := writer.WriteArrowBatch(record); err != nil {
return err
}关闭文件
writer.Close()示例代码
使用这些接口的示例代码可以在以下链接中找到 https://github.com/apache/tsfile/blob/develop/go/examples/table_read_write/main.go
查询流程
构造 Reader
reader, err := tsfile.NewReader("test.tsfile")
if err != nil {
return err
}
defer reader.Close()构造 Query Request
result, err := reader.Query(
"table1",
[]string{"id1", "id2", "s1", "s2"},
tsfile.WithTimeRange(2, 8),
tsfile.WithTagFilter(tsfile.And(tsfile.Eq("id1", "id1_filed_1"), tsfile.Eq("id2", "id2_filed_1"))),
tsfile.WithLimit(100),
)
if err != nil {
return err
}
defer result.Close()时间范围、Tag 过滤、offset、limit 和批次大小都是可选参数,均会下推到原生读取器。
查询数据
metadata := result.Metadata()
for _, column := range metadata {
fmt.Printf("%s(%d) ", column.Name, column.DataType)
}
fmt.Println()
for {
hasNext, err := result.Next()
if err != nil {
return err
}
if !hasNext {
break
}
for column := 1; column <= len(metadata); column++ {
isNull, err := result.IsNull(column)
if err != nil {
return err
}
if isNull {
fmt.Print("null ")
continue
}
switch metadata[column-1].DataType {
case tsfile.DataTypeBoolean:
value, err := result.Bool(column)
if err != nil {
return err
}
fmt.Print(value, " ")
case tsfile.DataTypeInt32, tsfile.DataTypeDate:
value, err := result.Int32(column)
if err != nil {
return err
}
fmt.Print(value, " ")
case tsfile.DataTypeInt64, tsfile.DataTypeTimestamp:
value, err := result.Int64(column)
if err != nil {
return err
}
fmt.Print(value, " ")
case tsfile.DataTypeFloat:
value, err := result.Float32(column)
if err != nil {
return err
}
fmt.Print(value, " ")
case tsfile.DataTypeDouble:
value, err := result.Float64(column)
if err != nil {
return err
}
fmt.Print(value, " ")
case tsfile.DataTypeString, tsfile.DataTypeText:
value, err := result.String(column)
if err != nil {
return err
}
fmt.Print(value, " ")
}
}
fmt.Println()
}结果集的列下标从 1 开始:第 1 列是时间戳,其后按查询顺序排列所选列。类型化 getter(Bool、Int32、Int64、 Float32、Float64、String、Bytes)在列类型不匹配时返回错误,因此需要像上例一样按元数据分发。
以 Arrow 批次查询数据
当 WithBatchSize 为正数时,查询运行在批次模式下,数据以 Arrow record batch 的形式返回:
result, err := reader.Query("table1", []string{"id1", "id2", "s1", "s2"}, tsfile.WithBatchSize(1024))
if err != nil {
return err
}
defer result.Close()
for {
record, err := result.ReadArrowRecordBatch()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return err
}
fmt.Println(record.NumRows())
record.Release()
}关闭文件
result.Close()
reader.Close()关闭 reader 时也会关闭由它创建的所有结果集。
示例代码
使用这些接口的示例代码可以在以下链接中找到 https://github.com/apache/tsfile/blob/develop/go/examples/table_read_write/main.go
注意:以上读写示例均基于表模型接口,接口定义介绍可见Go 接口定义。若需了解树模型相关内容,请联系我们。