CentOS 8 安装 Apache TsFile 指南

在 CentOS 8 服务器从零部署 Apache TsFile 2.4.0:安装 OpenJDK 17 与 Maven 3.9.16,创建工程引入 tsfile 依赖,完成时序数据写入、查询与魔数校验,覆盖版本选型与典型报错处理。

Apache TsFileApache TsFile2026/10/12

CentOS 8 安装 Apache TsFile 指南

一、环境信息

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

项目实测值
操作系统CentOS Linux 8(内核 6.8.0-138-generic)
CPU / 内存12 vCPU / 32 GiB(可用约 19 GiB)
磁盘根分区可用 ≥ 50 GB(实测 331 GB 可用)
JDKjava-17-openjdk-devel 17.0.1.0.12-2.el8_5(实测 javac -version 回显 javac 17.0.1)
构建工具Apache Maven 3.9.16(二进制包安装于 /opt/maven)
TsFile 版本org.apache.tsfile:tsfile 2.4.0(Maven Central 最新稳定版)
网络可访问 repo1.maven.org 与 dlcdn.apache.org

二、前置条件

  1. 拥有 root 权限或可使用 sudo 的账号;本文统一使用 root 账号演示,示例密码 q1w2e3r4 仅用于本地实验环境。
  2. 服务器可访问公网 Maven 仓库 https://repo1.maven.org/maven2/。
  3. CentOS 8 原厂已停止维护,需确认 dnf 能正常刷新元数据(官方 base/appstream 或归档 vault 源均可)。执行 dnf makecache 无报错即可继续。
  4. 预留至少 2 GB 磁盘空间:JDK 约 300 MB,本地 Maven 仓库中 tsfile 及其依赖约 40 MB,工程编译产物约 1 MB。

三、版本选型

Apache TsFile 是面向时序数据的列式文件格式,已从 Apache IoTDB 中独立为顶层库(代码仓库 apache/tsfile),通过 Maven 坐标 org.apache.tsfile:tsfile 发布。

3.1 版本线与 JDK 要求

版本线最新版本编译目标(实测自父 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 的版本对应关系

TsFile 格式与 IoTDB 深度绑定,选型时需与下游 IoTDB 版本对齐(以下对应关系取自两者发布到 Maven Central 的父 POM 实测值):

IoTDB 版本其依赖的 tsfile 版本
1.3.41.1.1
2.0.22.0.2

即:生成的 TsFile 文件若要交给 IoTDB 1.3.x 加载,建议用 tsfile 1.x 系列写入;交给 IoTDB 2.0.x,用 tsfile 2.0.x。2.4.0 适合纯独立场景(自写自读、第三方系统集成)。

四、部署步骤

4.1 安装 JDK 17

dnf install -y java-17-openjdk-devel

实测验证:

java -version
javac -version

回显:

openjdk version "17.0.1" 2021-10-19 LTS
javac 17.0.1

写入环境变量,编辑 /root/.bashrc 追加以下两行:

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
export PATH=$JAVA_HOME/bin:$PATH

执行 source /root/.bashrc 生效。注意:/usr/lib/jvm/java-17-openjdk 是系统维护的符号链接,升级小版本后无需改动。

4.2 安装 Maven

CentOS 8 的 dnf 仓库提供 Maven 3.5.4(实测包名 maven-3.5.4-5.module_el8),存在两个问题:一是 maven-compiler-plugin 3.13.0 要求 Maven ≥ 3.6.3,用 3.5.4 编译工程会直接报错;二是安装 maven 时会连带装入 JDK 8 并把默认 java 指向 1.8。因此推荐直接使用官方二进制包安装新版 Maven。

(1)下载并解压。注意 Apache CDN 只保留最新版本,历史版本会返回 404(实测下载 3.9.9 时返回 404,改用 3.9.16 成功):

curl -L --max-time 300 -o /tmp/maven.tgz \
  https://dlcdn.apache.org/maven/maven-3/3.9.16/binaries/apache-maven-3.9.16-bin.tar.gz
mkdir -p /opt/maven
tar -xzf /tmp/maven.tgz -C /opt/maven --strip-components=1

(2)编辑 /root/.bashrc 追加:

export MAVEN_HOME=/opt/maven
export PATH=$MAVEN_HOME/bin:$PATH

(3)若曾通过 dnf 装过 maven,需把默认 Java 切回 17(实测 dnf install maven 后 java -version 变为 1.8.0_312):

alternatives --set java java-17-openjdk.x86_64
alternatives --set javac java-17-openjdk.x86_64

(4)验证:

source /root/.bashrc
mvn -version

实测回显:

Apache Maven 3.9.16 (2bdd9fddda4b155ebf8000e807eb73fd829a51d5)
Maven home: /opt/maven
Java version: 17.0.1, vendor: Red Hat, Inc., runtime: /usr/lib/jvm/java-17-openjdk-17.0.1.0.12-2.el8_5.x86_64

4.3 创建工程并引入依赖

(1)创建目录结构:

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

(2)创建 pom.xml(完整内容如下):

<?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>

说明:tsfile 2.4.0 会传递引入 common-2.4.0、zstd-jni 1.5.6-3、snappy-java 1.1.10.5、lz4-java 1.10.1、xz 1.10、slf4j-api 2.0.17、antlr4-runtime 4.13.2(实测 mvn dependency:tree 回显)。slf4j-simple 用于绑定日志输出,不加入时日志静默但不影响功能。

(3)解析依赖:

export MAVEN_OPTS="-Xms256m -Xmx512m"
mvn -q dependency:resolve

实测退出码 0,依赖全部下载成功。

4.4 编写写入程序

创建 /root/tsfile-demo/src/main/java/demo/WriteDemo.java:注册两个设备共 5 个测点,分批写入 4000 行数据,实测 2.4.0 API(注意 2.x 中 TSDataType 位于 org.apache.tsfile.enums 包):

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();
    }
    long cost = System.currentTimeMillis() - start;
    System.out.println("写入完成: " + f.getAbsolutePath()
        + " 大小=" + f.length() + " 字节, 耗时=" + cost + "ms");
  }
}

4.5 编写读取程序

创建 /root/tsfile-demo/src/main/java/demo/ReadDemo.java:先用 TsFileSequenceReader 遍历文件元数据,再用 TsFileReader 按时间窗口条件查询:

package demo;

import java.util.List;
import org.apache.tsfile.enums.TSDataType;
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("== 设备列表 ==");
    List<IDeviceID> devices = seqReader.getAllDevices();
    for (IDeviceID dev : devices) {
      System.out.println("device: " + dev);
    }
    System.out.println("== 设备测点(类型) ==");
    for (IDeviceID dev : devices) {
      seqReader.getMeasurement(dev).forEach((m, t) ->
          System.out.println(dev + " -> " + m + " : " + t));
    }
    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);
      System.out.println("== 查询结果(root.demo.device1 temperature/humidity, 3 秒窗口) ==");
      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.6 编译、运行与验证

(1)编译并运行写入程序:

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

实测回显:

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

(2)运行读取程序:

mvn -q compile exec:java -Dexec.mainClass=demo.ReadDemo

实测回显:

device: root.demo.device1
device: root.demo.device2
== 设备测点(类型) ==
root.demo.device1 -> temperature : FLOAT
root.demo.device1 -> humidity : DOUBLE
root.demo.device1 -> status : TEXT
root.demo.device2 -> current : INT64
root.demo.device2 -> voltage : INT32
== 查询结果(root.demo.device1 temperature/humidity, 3 秒窗口) ==
1700000000000 | FLOAT:20.0 | DOUBLE:45.0
1700000001000 | FLOAT:21.0 | DOUBLE:46.0
1700000002000 | FLOAT:22.0 | DOUBLE:47.0
共命中 3 行(本次仅展示 3 行)

(3)文件格式校验。TsFile 文件头尾都有魔数 TsFile,用 od 查看实测:

head -c 10 demo.tsfile | od -c | head -1
tail -c 10 demo.tsfile | od -c

实测回显(首部 TsFile + 版本号字节,尾部 TsFile):

0000000   T   s   F   i   l   e 004  \0 002 022
0000000  \0  \0  \0 302   T   s   F   i   l   e

至此安装与读写验证全部完成。

五、配置说明

5.1 JVM 堆限制

TsFile 写入过程中的内存缓冲受 TSFileConfig 控制,JVM 堆建议按数据吞吐预留。本指南统一使用 256–512 MB 堆:

  • Maven 运行(exec:java 在 Maven 进程内执行业务代码):修改位置为环境变量 MAVEN_OPTS,建议写入 /root/.bashrc:export MAVEN_OPTS="-Xms256m -Xmx512m"。
  • 打包后用 java 直跑:修改位置为命令行参数 java -Xms256m -Xmx512m -cp ...。

验证(程序运行期间另开一个会话执行):

ps -ef | grep [j]ava

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

root  1625  1 31 12:32 ?  00:00:04 /usr/lib/jvm/java-17-openjdk/bin/java --enable-native-access=ALL-UNNAMED -Xms256m -Xmx512m -classpath /opt/maven/boot/plexus-classworlds-2.11.0.jar ... org.codehaus.plexus.classworlds.launcher.Launcher -q exec:java -Dexec.mainClass=demo.HeapHolder

也可以用 jps -v:

1625 Launcher --enable-native-access=ALL-UNNAMED -Xms256m -Xmx512m -Dclassworlds.conf=/opt/maven/bin/m2.conf ...

配套验证程序打印 Runtime.getRuntime().maxMemory(),实测回显 maxMemory=512MB,三者一致。

5.2 TsFile 文件内配置

写端参数通过 TSFileDescriptor.getInstance().getConfig() 获取后用 setter 调整(如 4.4 中 setPageSizeInByte / setGroupSizeInByte),也可通过 classpath 下的 iotdb-system.properties 覆盖默认值(详见《Apache TsFile 配置参数详解》)。注意 2.4.0 的配置文件名是 iotdb-system.properties,旧版资料中的 tsfile-format.properties 已不再是入口。

5.3 编码与压缩选择

MeasurementSchema 的第 3、4 个参数分别是编码器与压缩器。类型与编码的合法组合可用 TSEncoding.TYPE_SUPPORTED_ENCODINGS 在运行期查询,例如 FLOAT 支持 GORILLA/CHIMP/SPRINTZ/RLE/PLAIN/RLBE/TS_2DIFF/GORILLA_V1,TEXT 仅支持 PLAIN/DICTIONARY。压缩器支持 UNCOMPRESSED/SNAPPY/GZIP/LZ4/ZSTD/LZMA2。

六、升级与多版本管理

  1. 升级只涉及 pom 中 <tsfile.version> 属性。工程 pom 已将该值抽为属性,切换版本只需改一处,例如 2.4.0 改为 1.1.4 后执行 mvn -q dependency:resolve 实测可正常解析。
  2. 降级到 2.3.1 及以下时,同步把 maven.compiler.source/target 从 17 改为 8;否则混编可能产生 class 版本不匹配。
  3. 大版本升级注意 API 变化:2.x 中 TSDataType 从 org.apache.tsfile.file.metadata.enums 迁移到 org.apache.tsfile.enums;Tablet 设置行数的方法是 setRowSize(不存在 setRowNumber)。
  4. 多版本并存时,用 mvn dependency:purge-local-repository -Dre-include=org.apache.tsfile 或手动删除 ~/.m2/repository/org/apache/tsfile 下对应目录,再重新 resolve,避免旧快照干扰。

七、注意事项

  1. JDK 版本必须与 tsfile 版本线匹配:2.4.0 强制 JDK 17;用 JDK 8 编译调用 2.4.0 的代码会在编译期报 unsupported class file version。
  2. Tablet 默认最大 1024 行,超量写入抛 ArrayIndexOutOfBoundsException(实测复现),必须分批调用 writeTree 并 tablet.reset(),或构造时指定容量。
  3. 使用无 Schema 的 new TsFileWriter(File) 时,若直接 writeTree 未注册设备的 Tablet,会抛 NoDeviceException: No device for ...(实测复现);应先构造 Schema 并 registerMeasurementGroup。
  4. 控制台中文乱码时在 JVM 参数加 -Dfile.encoding=UTF-8(实测 Maven 默认输出平台字符集导致乱码)。
  5. Apache CDN(dlcdn.apache.org)只保留最新版本,老版本 Maven 下载链接会 404,可从 archive.apache.org 获取历史版本。
  6. 若后续对接 IoTDB 做联调,IoTDB 命令行默认账号 root / 密码 q1w2e3r4 可直接用于实验环境写入验证。

八、常见问题

Q1:mvn compile 报 "The plugin org.apache.maven.plugins:maven-compiler-plugin:3.13.0 requires Maven version 3.6.3"?

A:dnf 源里的 Maven 3.5.4 版本过低。按 4.2 节安装 Maven 3.9.16 二进制包,并把 PATH 指向 /opt/maven/bin 后重试。

Q2:安装 Maven 后 mvn -version 显示 Java version: 1.8.0_312?

A:dnf 安装 maven 连带装入了 JDK 8 并改写了默认 java 指向。执行 alternatives --set java java-17-openjdk.x86_64 与 alternatives --set javac java-17-openjdk.x86_64 切回 17,并确认 JAVA_HOME 指向 java-17-openjdk。

Q3:下载 Maven 时 404?

A:dlcdn.apache.org 只保留当前维护的版本。访问 https://dlcdn.apache.org/maven/maven-3/ 查看现存版本(实测当前为 3.9.16 与 3.10.0),或改用 https://archive.apache.org/dist/maven/maven-3/ 获取历史版本。

Q4:运行时报 NoClassDefFoundError: org/apache/tsfile/read/filter/basic/Filter,但依赖里明明有 tsfile?

A:常见于 java -cp 的 classpath 通配符未加引号,bash 抢先展开 * 导致部分 jar 丢失。写成 java -cp 'target/tsfile-demo-1.0.0.jar:target/dependency/*' ...(引号交给 JVM 展开)。

Q5:写入抛 ArrayIndexOutOfBoundsException: Index 1024 out of bounds for length 1024?

A:单条 Tablet 默认上限 1024 行。控制每批写入不超过容量,写满即调用 writer.writeTree(tablet) 后 tablet.reset() 复用;或构造 Tablet 时显式传入更大的 maxRowNumber。

九、卸载方式

  1. 删除工程与本地缓存:
rm -rf /root/tsfile-demo
rm -rf /root/.m2/repository/org/apache/tsfile
  1. 卸载 Maven 二进制包:
rm -rf /opt/maven

并移除 /root/.bashrc 中 MAVEN_HOME/PATH 相关两行。

  1. 卸载 JDK(确认无其他组件依赖后再执行):
dnf remove -y java-17-openjdk-devel
  1. 清理环境变量:编辑 /root/.bashrc 删除 JAVA_HOME、MAVEN_HOME 两处 export,重新登录生效。

十、参考链接