CentOS 8 安装 Apache TsFile 指南
在 CentOS 8 服务器从零部署 Apache TsFile 2.4.0:安装 OpenJDK 17 与 Maven 3.9.16,创建工程引入 tsfile 依赖,完成时序数据写入、查询与魔数校验,覆盖版本选型与典型报错处理。
CentOS 8 安装 Apache TsFile 指南
一、环境信息
本文所有命令均在以下环境实测通过,输出均为真实回显摘录。
| 项目 | 实测值 |
|---|---|
| 操作系统 | CentOS Linux 8(内核 6.8.0-138-generic) |
| CPU / 内存 | 12 vCPU / 32 GiB(可用约 19 GiB) |
| 磁盘 | 根分区可用 ≥ 50 GB(实测 331 GB 可用) |
| JDK | java-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 |
二、前置条件
- 拥有 root 权限或可使用 sudo 的账号;本文统一使用 root 账号演示,示例密码 q1w2e3r4 仅用于本地实验环境。
- 服务器可访问公网 Maven 仓库
https://repo1.maven.org/maven2/。 - CentOS 8 原厂已停止维护,需确认 dnf 能正常刷新元数据(官方 base/appstream 或归档 vault 源均可)。执行
dnf makecache无报错即可继续。 - 预留至少 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.x | 1.1.4 | 8 | JDK 8 及以上均可运行 |
| 2.0.x – 2.3.x | 2.3.1 | 8 | JDK 8 及以上均可运行 |
| 2.4.x | 2.4.0 | 17 | 强制要求 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.4 | 1.1.1 |
| 2.0.2 | 2.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。
六、升级与多版本管理
- 升级只涉及 pom 中
<tsfile.version>属性。工程 pom 已将该值抽为属性,切换版本只需改一处,例如2.4.0改为1.1.4后执行mvn -q dependency:resolve实测可正常解析。 - 降级到 2.3.1 及以下时,同步把
maven.compiler.source/target从 17 改为 8;否则混编可能产生 class 版本不匹配。 - 大版本升级注意 API 变化:2.x 中
TSDataType从org.apache.tsfile.file.metadata.enums迁移到org.apache.tsfile.enums;Tablet设置行数的方法是setRowSize(不存在setRowNumber)。 - 多版本并存时,用
mvn dependency:purge-local-repository -Dre-include=org.apache.tsfile或手动删除~/.m2/repository/org/apache/tsfile下对应目录,再重新 resolve,避免旧快照干扰。
七、注意事项
- JDK 版本必须与 tsfile 版本线匹配:2.4.0 强制 JDK 17;用 JDK 8 编译调用 2.4.0 的代码会在编译期报 unsupported class file version。
Tablet默认最大 1024 行,超量写入抛ArrayIndexOutOfBoundsException(实测复现),必须分批调用writeTree并tablet.reset(),或构造时指定容量。- 使用无 Schema 的
new TsFileWriter(File)时,若直接writeTree未注册设备的 Tablet,会抛NoDeviceException: No device for ...(实测复现);应先构造Schema并registerMeasurementGroup。 - 控制台中文乱码时在 JVM 参数加
-Dfile.encoding=UTF-8(实测 Maven 默认输出平台字符集导致乱码)。 - Apache CDN(dlcdn.apache.org)只保留最新版本,老版本 Maven 下载链接会 404,可从 archive.apache.org 获取历史版本。
- 若后续对接 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。
九、卸载方式
- 删除工程与本地缓存:
rm -rf /root/tsfile-demo
rm -rf /root/.m2/repository/org/apache/tsfile
- 卸载 Maven 二进制包:
rm -rf /opt/maven
并移除 /root/.bashrc 中 MAVEN_HOME/PATH 相关两行。
- 卸载 JDK(确认无其他组件依赖后再执行):
dnf remove -y java-17-openjdk-devel
- 清理环境变量:编辑
/root/.bashrc删除JAVA_HOME、MAVEN_HOME两处 export,重新登录生效。
十、参考链接
- TsFile 官网:https://tsfile.apache.org/
- TsFile GitHub 仓库:https://github.com/apache/tsfile
- Maven Central:https://central.sonatype.com/artifact/org.apache.tsfile/tsfile
- Maven Central 版本元数据:https://repo1.maven.org/maven2/org/apache/tsfile/tsfile/maven-metadata.xml
- IoTDB 官方文档(TsFile 章节):https://iotdb.apache.org/zh/UserGuide/latest/API/Programming-Java-Native-SDK.html
- Apache Maven 下载:https://dlcdn.apache.org/maven/maven-3/