Ubuntu 22.04 安装 Apache TsFile 指南

在 Ubuntu 22.04 服务器从零部署 Apache TsFile 2.4.0:APT 安装 OpenJDK 17 与 Maven,创建工程引入依赖,完成时序数据写入、查询与堆参数验证,并实测 Python SDK 读写闭环。

Apache TsFileApache TsFile2026/10/12

Ubuntu 22.04 安装 Apache TsFile 指南

一、环境信息

本文所有命令均在以下环境实测通过,输出均为真实回显摘录。

项目实测值
操作系统Ubuntu 22.04.5 LTS(内核 6.8.0-138-generic)
CPU / 内存12 vCPU / 32 GiB(可用约 19 GiB)
磁盘根分区可用 ≥ 50 GB
JDKopenjdk-17-jdk-headless 17.0.20.1+1-1~22.04
构建工具Apache Maven 3.6.3(APT 源版本,满足 tsfile 2.4.0 工程要求)
Python3.10.12 / pip 22.0.2(用于 Python SDK 实测)
TsFile 版本org.apache.tsfile:tsfile 2.4.0;Python 包 tsfile 2.4.0
网络可访问 repo1.maven.org 与 pypi.org

二、前置条件

  1. root 权限或 sudo 账号;本文统一以 root 演示,实验口令 q1w2e3r4 仅用于本地验证场景。
  2. 服务器可访问 Maven Central(https://repo1.maven.org/maven2/)与 PyPI(https://pypi.org/simple/)。
  3. 磁盘预留约 2 GB:JDK 约 300 MB、Maven 约 10 MB、本地仓库 tsfile 及依赖约 40 MB、Python 轮子约 30 MB。

三、版本选型

3.1 版本线与 JDK 要求

Maven Central 上 org.apache.tsfile:tsfile 当前可选 1.0.0 至 2.4.0 共 17 个版本。各版本线对 JDK 的要求(实测自 Maven Central 父 POM 的 maven.compiler.release):

版本线最新版本编译目标说明
1.x1.1.48JDK 8+
2.0.x – 2.3.x2.3.18JDK 8+
2.4.x2.4.017强制 JDK 17+

本文选 2.4.0 + OpenJDK 17。如需兼容 JDK 8 运行时,固定使用 2.3.1 及以下。

3.2 与 IoTDB 的版本对应关系

生成文件最终交给 IoTDB 加载时,tsfile 版本应与 IoTDB 对齐(对应关系实测自 IoTDB 父 POM):

IoTDB 版本依赖的 tsfile 版本
1.3.41.1.1
2.0.22.0.2

若只做独立文件读写或对接自研系统,直接选最新稳定版 2.4.0。

四、部署步骤

4.1 安装 JDK 17 与 Maven

apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y openjdk-17-jdk-headless maven

实测说明:安装过程会连带装入 openjdk-11 相关包(作为 APT 依赖链被拉入),但默认 java 替代项指向 17,mvn -version 亦运行在 17 上,无需处理;如默认指向被改动,用 update-alternatives --config java 交互选择 17 即可。

验证:

java -version
mvn -version | head -3

实测回显:

openjdk version "17.0.20.1" 2026-08-18
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.20.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64

编辑 /root/.bashrc 追加环境变量:

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
export MAVEN_OPTS="-Xms256m -Xmx512m"

执行 source /root/.bashrc 生效。MAVEN_OPTS 控制 Maven 进程(含 exec:java 运行的业务代码)的堆大小,后续统一使用 256–512 MB。

4.2 创建工程并引入依赖

mkdir -p /root/tsfile-demo/src/main/java/demo
cd /root/tsfile-demo

创建 pom.xml,完整内容如下(tsfile.version 抽为属性便于后续切换版本):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>tsfile-demo</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <tsfile.version>2.4.0</tsfile.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.tsfile</groupId>
            <artifactId>tsfile</artifactId>
            <version>${tsfile.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>2.0.17</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>3.2.0</version>
            </plugin>
        </plugins>
    </build>
</project>

拉取依赖并确认依赖树:

mvn -q dependency:resolve
mvn dependency:tree 2>&1 | sed -n '/com.example:tsfile-demo/,/BUILD/p'

实测回显:

com.example:tsfile-demo:jar:1.0.0
+- org.apache.tsfile:tsfile:jar:2.4.0:compile
|  +- org.apache.tsfile:common:jar:2.4.0:compile
|  +- com.github.luben:zstd-jni:jar:1.5.6-3:compile
|  +- org.xerial.snappy:snappy-java:jar:1.1.10.5:compile
|  +- at.yawk.lz4:lz4-java:jar:1.10.1:compile
|  +- org.tukaani:xz:jar:1.10:compile
|  +- org.slf4j:slf4j-api:jar:2.0.17:compile
|  \- org.antlr:antlr4-runtime:jar:4.13.2:compile
\- org.slf4j:slf4j-simple:jar:2.0.17:compile

4.3 编写写入程序

创建 /root/tsfile-demo/src/main/java/demo/WriteDemo.java。要点:2.x 的 TSDataType 在 org.apache.tsfile.enums 包;设备与测点通过 Schema.registerMeasurementGroup 注册;Tablet 每批不超过 1024 行,行数用 setRowSize 设置:

package demo;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.apache.tsfile.common.conf.TSFileConfig;
import org.apache.tsfile.common.conf.TSFileDescriptor;
import org.apache.tsfile.enums.TSDataType;
import org.apache.tsfile.file.metadata.enums.CompressionType;
import org.apache.tsfile.file.metadata.enums.TSEncoding;
import org.apache.tsfile.write.TsFileWriter;
import org.apache.tsfile.write.record.Tablet;
import org.apache.tsfile.write.schema.IMeasurementSchema;
import org.apache.tsfile.write.schema.MeasurementSchema;
import org.apache.tsfile.write.schema.Schema;

public class WriteDemo {

  public static void main(String[] args) throws Exception {
    TSFileConfig config = TSFileDescriptor.getInstance().getConfig();
    config.setPageSizeInByte(64 * 1024);
    config.setGroupSizeInByte(8 * 1024 * 1024);

    File f = new File("demo.tsfile");
    if (f.exists()) {
      f.delete();
    }

    Schema schema = new Schema();
    String device1 = "root.demo.device1";
    String device2 = "root.demo.device2";
    List<IMeasurementSchema> m1 = new ArrayList<>();
    m1.add(new MeasurementSchema("temperature", TSDataType.FLOAT, TSEncoding.RLE, CompressionType.SNAPPY));
    m1.add(new MeasurementSchema("humidity", TSDataType.DOUBLE, TSEncoding.GORILLA, CompressionType.ZSTD));
    m1.add(new MeasurementSchema("status", TSDataType.TEXT, TSEncoding.PLAIN, CompressionType.UNCOMPRESSED));
    schema.registerMeasurementGroup(new org.apache.tsfile.read.common.Path(device1),
        new org.apache.tsfile.utils.MeasurementGroup(false, m1));

    List<IMeasurementSchema> m2 = new ArrayList<>();
    m2.add(new MeasurementSchema("voltage", TSDataType.INT32, TSEncoding.TS_2DIFF, CompressionType.LZ4));
    m2.add(new MeasurementSchema("current", TSDataType.INT64, TSEncoding.TS_2DIFF, CompressionType.GZIP));
    schema.registerMeasurementGroup(new org.apache.tsfile.read.common.Path(device2),
        new org.apache.tsfile.utils.MeasurementGroup(false, m2));

    long start = System.currentTimeMillis();
    try (TsFileWriter writer = new TsFileWriter(f, schema, config)) {
      long baseTime = 1700000000000L;
      int batch = 500;
      for (long t = 0; t < 2000; t += batch) {
        Tablet tab1 = new Tablet(device1, m1);
        for (int r = 0; r < batch; r++) {
          long ts = baseTime + (t + r) * 1000L;
          tab1.addTimestamp(r, ts);
          tab1.addValue("temperature", r, 20.0f + (t + r) % 10);
          tab1.addValue("humidity", r, 45.0 + (t + r) % 5);
          tab1.addValue("status", r, ((t + r) % 2 == 0) ? "OK" : "WARN");
        }
        tab1.setRowSize(batch);
        writer.writeTree(tab1);

        Tablet tab2 = new Tablet(device2, m2);
        for (int r = 0; r < batch; r++) {
          long ts = baseTime + (t + r) * 1000L;
          tab2.addTimestamp(r, ts);
          tab2.addValue("voltage", r, (int) (220 + (t + r) % 3));
          tab2.addValue("current", r, 1000L + (t + r) % 7);
        }
        tab2.setRowSize(batch);
        writer.writeTree(tab2);
      }
      writer.flush();
    }
    System.out.println("写入完成: " + f.getAbsolutePath()
        + " 大小=" + f.length() + " 字节");
  }
}

4.4 编写读取程序

创建 /root/tsfile-demo/src/main/java/demo/ReadDemo.java:

package demo;

import java.util.List;
import org.apache.tsfile.file.metadata.IDeviceID;
import org.apache.tsfile.read.TsFileReader;
import org.apache.tsfile.read.TsFileSequenceReader;
import org.apache.tsfile.read.common.Field;
import org.apache.tsfile.read.common.Path;
import org.apache.tsfile.read.common.RowRecord;
import org.apache.tsfile.read.expression.IExpression;
import org.apache.tsfile.read.expression.QueryExpression;
import org.apache.tsfile.read.expression.impl.BinaryExpression;
import org.apache.tsfile.read.expression.impl.GlobalTimeExpression;
import org.apache.tsfile.read.filter.factory.TimeFilterApi;
import org.apache.tsfile.read.query.dataset.QueryDataSet;

public class ReadDemo {

  public static void main(String[] args) throws Exception {
    String file = "demo.tsfile";

    TsFileSequenceReader seqReader = new TsFileSequenceReader(file);
    System.out.println("== 设备列表 ==");
    for (IDeviceID dev : seqReader.getAllDevices()) {
      System.out.println("device: " + dev);
    }
    seqReader.close();

    TsFileSequenceReader reader = new TsFileSequenceReader(file);
    try (TsFileReader tsFileReader = new TsFileReader(reader)) {
      QueryExpression query = QueryExpression.create()
          .addSelectedPath(new Path("root.demo.device1", "temperature", true))
          .addSelectedPath(new Path("root.demo.device1", "humidity", true));
      long start = 1700000000000L;
      IExpression timeFilter = BinaryExpression.and(
          new GlobalTimeExpression(TimeFilterApi.gtEq(start)),
          new GlobalTimeExpression(TimeFilterApi.lt(start + 3000)));
      query.setExpression(timeFilter);

      QueryDataSet dataSet = tsFileReader.query(query);
      int rows = 0;
      while (dataSet.hasNext() && rows < 3) {
        RowRecord record = dataSet.next();
        StringBuilder sb = new StringBuilder();
        sb.append(record.getTimestamp());
        for (Field field : record.getFields()) {
          sb.append(" | ").append(field.getDataType()).append(":")
              .append(field.getObjectValue(field.getDataType()));
        }
        System.out.println(sb);
        rows++;
      }
      System.out.println("共命中 " + rows + " 行(本次仅展示 3 行)");
    }
  }
}

4.5 运行与验证

cd /root/tsfile-demo
export MAVEN_OPTS="-Xms256m -Xmx512m -Dfile.encoding=UTF-8"
mvn -q compile exec:java -Dexec.mainClass=demo.WriteDemo
mvn -q compile exec:java -Dexec.mainClass=demo.ReadDemo

实测回显(写入):

写入完成: /root/tsfile-demo/demo.tsfile 大小=9984 字节, 耗时=125ms

实测回显(读取):

device: root.demo.device1
device: root.demo.device2
1700000000000 | FLOAT:20.0 | DOUBLE:45.0
1700000001000 | FLOAT:21.0 | DOUBLE:46.0
1700000002000 | FLOAT:22.0 | DOUBLE:47.0
共命中 3 行(本次仅展示 3 行)

文件魔数校验(首尾均为 TsFile 字符串):

head -c 6 demo.tsfile && echo && tail -c 6 demo.tsfile && echo

实测回显:

TsFile
TsFile

4.6 JVM 堆参数验证

写入程序运行期间另开一个会话:

ps -ef | grep [j]ava

实测回显(节选,-Xms256m -Xmx512m 生效):

root  7582  1 33 12:11 ?  00:00:04 /usr/bin/java -Xms256m -Xmx512m -classpath /usr/share/maven/boot/plexus-classworlds-2.x.jar -Dclassworlds.conf=/usr/share/maven/bin/m2.conf ... org.codehaus.plexus.classworlds.launcher.Launcher -q exec:java -Dexec.mainClass=demo.HeapHolder

jps -v 回显:

7582 Launcher -Xms256m -Xmx512m -Dclassworlds.conf=/usr/share/maven/bin/m2.conf ...

配合验证程序输出 Runtime.getRuntime().maxMemory() 实测为 maxMemory=512MB,与堆上限一致。

4.7 Python SDK 实测(可选)

Ubuntu 22.04 自带 Python 3.10,PyPI 上有独立的 tsfile 包(当前最新 2.4.0,与 Java 版本同步发布):

apt-get install -y python3-pip
pip3 install tsfile
pip3 show tsfile | head -2

实测回显:

Name: tsfile
Version: 2.4.0
Summary: TsFile Python

创建 /root/py_demo.py(Python 端 Tablet 无需手动设置行数;树模型写入前必须 set_table_name 指定目标设备,否则抛 DeviceNotExistError):

# -*- coding: utf-8 -*-
import os
from tsfile import TsFileWriter, TsFileReader, DeviceSchema
from tsfile import TimeseriesSchema, Tablet, TSDataType

path = "/tmp/py_demo.tsfile"
if os.path.exists(path):
    os.remove(path)

w = TsFileWriter(path)
ts_list = [
    TimeseriesSchema("temp", TSDataType.FLOAT),
    TimeseriesSchema("hum", TSDataType.DOUBLE),
]
w.register_device(DeviceSchema("root.py.device1", ts_list))
tablet = Tablet(["temp", "hum"], [TSDataType.FLOAT, TSDataType.DOUBLE])
tablet.set_table_name("root.py.device1")
for i in range(100):
    tablet.add_timestamp(i, 1700000000000 + i * 1000)
    tablet.add_value_by_name("temp", i, 20.0 + i % 5)
    tablet.add_value_by_name("hum", i, 50.0 + i % 3)
w.write_tablet(tablet)
w.close()
print("写入完成, 大小:", os.path.getsize(path), "字节")

r = TsFileReader(path)
print("设备:", list(r.get_all_devices()))
rs = r.query_tree_by_row(["root.py.device1"], ["temp", "hum"], 0, 5)
print("结果集列:", rs.get_result_column_info())
while rs.next():
    print("temp =", rs.get_value_by_name("temp"), ", hum =", rs.get_value_by_name("hum"))
rs.close()
r.close()

运行 python3 /root/py_demo.py,实测回显:

写入完成, 大小: 1646 字节
设备: [DeviceID(path='root.py.device1', table_name='root.py', segments=('root.py', 'device1'))]
结果集列: {'time': <TSDataType.INT64: 2>, 'root.py.device1.temp': <TSDataType.FLOAT: 3>, 'root.py.device1.hum': <TSDataType.DOUBLE: 4>}
temp = 20.0 , hum = 50.0
temp = 21.0 , hum = 51.0
temp = 22.0 , hum = 52.0
temp = 23.0 , hum = 50.0
temp = 24.0 , hum = 51.0

补充说明:Python 端未显式指定编码/压缩时,实测默认使用 GORILLA 编码 + LZ4 压缩(get_all_timeseries_schemas 回显 TimeseriesSchema(temp, FLOAT, GORILLA, LZ4))。

五、配置说明

  1. JVM 堆:统一通过 MAVEN_OPTS(Maven/exec:java 场景)或 java -Xms256m -Xmx512m(打包直跑场景)限定在 256–512 MB,修改位置与验证方式见 4.1 与 4.6 节。
  2. 写端文件参数:代码内经 TSFileDescriptor.getInstance().getConfig() 的 setter 修改;或把 iotdb-system.properties 放到 classpath(如 src/main/resources/)覆盖默认值,键名如 page_size_in_byte、group_size_in_byte、compressor、float_precision 等,详见《Apache TsFile 配置参数详解》。
  3. 日志绑定:tsfile 仅依赖 slf4j-api,需自行加入实现包(本文用 slf4j-simple 2.0.17),否则日志静默。
  4. 字符集:Maven 控制台输出中文乱码时,在 MAVEN_OPTS 中追加 -Dfile.encoding=UTF-8。

六、升级与多版本管理

  1. 切版本只需修改 pom 属性 <tsfile.version>,例如改为 1.1.4 后执行 mvn -q dependency:resolve(实测可正常解析 1.1.4)。
  2. 降级到 2.3.1 及以下需把 maven.compiler.source/target 改为 8。
  3. 2.4.0 相对旧版的显性 API 差异:TSDataType 包迁移至 org.apache.tsfile.enums;Tablet 行数方法为 setRowSize;新增 v4 表模型 API(org.apache.tsfile.write.v4.TsFileWriterBuilder)。
  4. 本地仓库冲突时删除 ~/.m2/repository/org/apache/tsfile 对应版本目录后重新拉取。

七、注意事项

  1. JDK 与版本线必须匹配:2.4.0 强制 JDK 17+,低版本 JDK 编译会报 class 版本错误。
  2. Tablet 默认 1024 行上限,超限抛 ArrayIndexOutOfBoundsException(实测复现),务必分批。
  3. 无 Schema 构造的 TsFileWriter 写入未注册设备会抛 NoDeviceException(实测复现)。
  4. Python 端与 Java 端生成的文件互相可读(同为 TsFile 格式),但 Python 端默认编码/压缩与 Java 端默认(SNAPPY 系)不同,混用场景建议显式指定。
  5. 对接 IoTDB 联调时,实验环境可直接使用默认账号 root、密码 q1w2e3r4。
  6. PyPI 上的 apache-iotdb 是 IoTDB 客户端包,独立 TsFile 读写请安装 tsfile 包,二者用途不同。

八、常见问题

Q1:mvn -version 显示的 Java 版本不是 17?

A:系统存在多版本 JDK 时,执行 update-alternatives --config java 选择 java-17 对应项,并确认 JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 已写入 /root/.bashrc。

Q2:运行类时报 NoClassDefFoundError?

A:多见于 java -cp 的通配符 * 未加引号,被 shell 展开后 classpath 被截断。正确写法:java -cp 'target/tsfile-demo-1.0.0.jar:target/dependency/*' demo.ReadDemo。

Q3:exec:java 跑中文输出乱码?

A:在 MAVEN_OPTS 追加 -Dfile.encoding=UTF-8,或改用打包后 java -Dfile.encoding=UTF-8 -cp ... 直跑。

Q4:Python 安装 tsfile 失败或没有 2.4.0?

A:pip 版本过旧(如 9.x)无法读取新轮子元数据,先升级 pip3 install --upgrade pip;确认系统 Python ≥ 3.8。Python SDK 为可选组件,核心链路以 Java SDK 为准。

Q5:写入抛 NoDeviceException: No device for root.bench.d1?

A:TsFileWriter 未注册该设备。先构造 Schema 并 registerMeasurementGroup,再以 new TsFileWriter(file, schema, config) 创建写入器。

九、卸载方式

  1. 删除工程与本地仓库缓存:
rm -rf /root/tsfile-demo /root/py_demo.py
rm -rf /root/.m2/repository/org/apache/tsfile
pip3 uninstall -y tsfile
  1. 卸载 APT 包:
apt-get remove -y --purge maven openjdk-17-jdk-headless
apt-get autoremove -y
  1. 清理环境变量:删除 /root/.bashrc 中 JAVA_HOME、MAVEN_OPTS 相关行。

十、参考链接