TDengine — 原理 + 应用 + 实战案例

目录

[一、TDengine 是什么?](#一、TDengine 是什么?)

和普通数据库的区别

核心特性(面试要点)

二、核心组件(架构角度)

[2.1 taosd --- 主服务进程](#2.1 taosd — 主服务进程)

[2.2 taos --- 原生命令行客户端](#2.2 taos — 原生命令行客户端)

[2.3 超级表(STable / Super Table)](#2.3 超级表(STable / Super Table))

[2.4 VNode(虚拟节点)](#2.4 VNode(虚拟节点))

[2.5 MNode(管理节点)](#2.5 MNode(管理节点))

[2.6 QNode(查询节点)](#2.6 QNode(查询节点))

[2.7 时间分区](#2.7 时间分区)

三、工作原理(面试怎么答)

[四、实战案例:监控 100 台服务器 CPU 使用率](#四、实战案例:监控 100 台服务器 CPU 使用率)

[4.1 安装 TDengine](#4.1 安装 TDengine)

[4.2 创建数据库和超级表](#4.2 创建数据库和超级表)

[4.3 创建子表(每台服务器一张)](#4.3 创建子表(每台服务器一张))

[4.4 写入数据](#4.4 写入数据)

[4.5 查询数据(各种场景)](#4.5 查询数据(各种场景))

[4.6 Java(Spring Boot)完整集成](#4.6 Java(Spring Boot)完整集成)

[4.7 对接 Grafana 可视化](#4.7 对接 Grafana 可视化)

[4.8 对接 Prometheus(面试加分项)](#4.8 对接 Prometheus(面试加分项))

[五、面试高频题 & 参考答案](#五、面试高频题 & 参考答案)

[Q1: TDengine 和 InfluxDB 怎么选?](#Q1: TDengine 和 InfluxDB 怎么选?)

[Q2: 超级表为什么快?](#Q2: 超级表为什么快?)

[Q3: TDengine 如何处理大量设备同时写入?](#Q3: TDengine 如何处理大量设备同时写入?)

[Q4: 和常规关系型数据库比有什么缺陷?](#Q4: 和常规关系型数据库比有什么缺陷?)

[Q5: 数据过期删除怎么做的?](#Q5: 数据过期删除怎么做的?)

[Q6: TDengine 3.x 和 2.x 的区别?](#Q6: TDengine 3.x 和 2.x 的区别?)

[Q7: 怎么保证数据不丢?](#Q7: 怎么保证数据不丢?)

[六、30 分钟快速上手指南(自检清单)](#六、30 分钟快速上手指南(自检清单))

七、学习路线推荐


一、TDengine 是什么?

TDengine 是涛思数据(TAOS Data)打造的高性能、云原生、时序数据库(Time-Series Database, TSDB)。

一句话定位 :专为物联网(IoT)、工业互联网、车联网、运维监控等场景设计的时序数据平台,核心解决海量时间序列数据的高效写入、存储、查询和分析问题。

和普通数据库的区别

维度 传统关系库(MySQL/PostgreSQL) 普通时序库(InfluxDB) TDengine
数据模型 松散的行/列 标签+字段 无模式 超级表(STable)+ 子表,强模式
写入性能 一般(万级/秒) 较好(十万级/秒) 千万级/秒/节点
压缩比 低(一般 2~5x) 中(5~10x) 高(10~20x)
查询能力 标准 SQL 类 SQL 有限 全功能 SQL,支持窗口、聚合、降采样
部署运维 成熟 一般 单进程一体,无外部依赖

核心特性(面试要点)

  1. SQL 兼容 --- 支持标准 SQL(SELECT, INSERT, CREATE, JOIN, 窗口函数等),学习成本极低

  2. 超级表(STable) --- 核心抽象:一张模板表 + 按标签(tags)自动分表,实现千万级设备统一管理

  3. 列式存储 + 高效压缩 --- 按列存储,时序特有压缩算法(有损/无损),压缩比 10~20x

  4. 时间分区 --- 自动按时间分片(VNODES),支持过期自动删除

  5. 预计算 --- 窗口聚合、降采样、连续查询内建高效

  6. 云原生架构 --- 支持集群、多副本、弹性伸缩

  7. 无外部依赖 --- 一个 taosd 进程搞定所有,不依赖 ZooKeeper/HDFS

  8. 多种接入 --- JDBC/RESTful/MQTT/Telegraf/Prometheus 等


二、核心组件(架构角度)

2.1 taosd --- 主服务进程

单进程包含全部功能:SQL 解析、存储引擎、时序引擎、集群通信、数据均衡。

启动方式:

复制代码
systemctl start taosd   # Linux
docker start tdengine   # Docker

2.2 taos --- 原生命令行客户端

连接 taosd 执行 SQL。

复制代码
taos                          # 默认连接本地
taos -h 192.168.1.100 -P 6030 # 连接远程

2.3 超级表(STable / Super Table)

这是 TDengine 的灵魂概念,面试必问。

java 复制代码
CREATE STABLE meters (
    ts    TIMESTAMP,   -- 时间戳(主键)
    value FLOAT,       -- 采集值
    phase INT          -- 相别
) TAGS (
    location BINARY(64),  -- 标签:地理位置
    groupId INT           -- 标签:分组
);

超级表 = 模板,定义:

  • 数据列(Data Columns) --- 随时间变化的值,如 CPU 使用率、温度、电压

  • 标签列(Tags) --- 固定不变的元数据,如设备型号、地理位置、所属分组

子表 = 超级表的实例化

  • 一个具体设备对应一张子表

  • 标签值在子表创建时固定,后续不变

  • 所有子表共享超级表的数据列结构

查询优势:按标签过滤时,TDengine 自动只扫描匹配的子表,性能极佳。

2.4 VNode(虚拟节点)

  • 数据分布的最小逻辑单元

  • 一个 VNode 包含多个子表的数据

  • 多个 VNode 组成 VGroup,VGroup 是数据复制的最小单元

2.5 MNode(管理节点)

  • 集群元数据管理(表结构、节点信息、用户权限)

  • 支持多副本,使用 RAFT 协议选主维持一致性

2.6 QNode(查询节点)

  • 3.x 新增

  • 分布式 SQL 查询协调

  • 将查询计划拆分到各 VNode 并行执行,再合并结果

2.7 时间分区

  • 数据自动按时间分区(默认 1440 分钟/天)

  • 分区内数据顺序写入,保证极高的写入性能

  • 查询时通过分区裁剪只扫相关分区


三、工作原理(面试怎么答)

面试话术示例(1 分钟版):

"TDengine 之所以快,我认为核心在于三点:

第一,数据模型设计得好 --- 超级表将设备元数据(标签)和数据分离,同一设备的数据物理上连续存储,写入时顺序追加,天然避免了随机 IO。每台设备一张子表,查一台设备就是扫一个文件,不是扫整张表。

第二,列式存储 + 时序压缩 --- 按列存储,时间戳用差值编码(只存 delta),浮点数用施普林格编码或二阶差分,实测压缩比能达到 10~20 倍。存储成本低,查的时候 IO 量也少。

第三,计算下推 --- 窗口聚合、降采样直接在存储层完成,不需要把原始数据拉到应用层算。比如 SELECT AVG(cpu) INTERVAL(5m),数据在扫盘时就聚合完了,查询性能比 MySQL 快几十倍。而且它完全兼容 SQL,团队不需要额外学习新语法。"


四、实战案例:监控 100 台服务器 CPU 使用率

端到端实战,从安装到查询,每一步都写清楚。

4.1 安装 TDengine

方式一:Docker(推荐,5 分钟上手)

java 复制代码
# 拉取并启动
docker run -d --name tdengine \
  -p 6030:6030 -p 6041:6041 -p 6043:6043 -p 6044:6044 \
  tdengine/tdengine:3.3.5.4
​
# 验证
docker exec -it tdengine taos -V

端口说明:

  • 6030 → 原生 TCP 协议(JDBC 连接)

  • 6041 → RESTful HTTP 接口

  • 6043 → 集群内部通信

  • 6044 → 集群同步

方式二:直接安装(Linux)

java 复制代码
# Ubuntu/Debian
wget https://www.tdengine.com/assets-download/TDengine-3.3.5.4-Linux-x64.deb
sudo dpkg -i TDengine-3.3.5.4-Linux-x64.deb
sudo systemctl start taosd
taos -V

4.2 创建数据库和超级表

sql 复制代码
# 进入 TDengine Shell
taos
-- 创建数据库
-- KEEP 30: 数据保留 30 天自动删除
-- DURATION 10: 每 10 天一个数据文件分区
-- BUFFER 32: 写入缓存 32MB
-- CACHESIZE 4: 元数据缓存 4MB
CREATE DATABASE monitor KEEP 30 DURATION 10 BUFFER 32 CACHESIZE 4;
USE monitor;
​
-- 创建超级表:服务器 CPU 监控
CREATE STABLE cpu_metrics (
    ts        TIMESTAMP,      -- 采集时间
    cpu_usage FLOAT,          -- CPU 使用率 (%)
    mem_usage FLOAT,          -- 内存使用率 (%)
    load_1m   FLOAT           -- 1 分钟负载
) TAGS (
    hostname  BINARY(32),     -- 主机名
    region    BINARY(16),     -- 区域
    env       BINARY(8)       -- 环境 (prod/test/staging)
);

4.3 创建子表(每台服务器一张)

sql 复制代码
-- 为每台服务器创建子表
CREATE TABLE server_web_01 USING cpu_metrics TAGS('web-01', 'us-east-1', 'prod');
CREATE TABLE server_web_02 USING cpu_metrics TAGS('web-02', 'us-east-1', 'prod');
CREATE TABLE server_db_01  USING cpu_metrics TAGS('db-01',  'us-east-2', 'prod');
CREATE TABLE server_cache_01 USING cpu_metrics TAGS('cache-01', 'ap-southeast-1', 'staging');
CREATE TABLE server_db_02   USING cpu_metrics TAGS('db-02',  'eu-west-1',  'prod');
​
-- 查一下有多少个子表
SELECT COUNT(*) FROM information_schema.ins_tables WHERE db_name = 'monitor';

4.4 写入数据

方式一:单条写入

sql 复制代码
INSERT INTO server_web_01 (ts, cpu_usage, mem_usage, load_1m) 
VALUES (NOW, 45.2, 62.1, 2.3);

方式二:单行写入多张表(事务性)

sql 复制代码
INSERT INTO 
    server_web_01 (ts, cpu_usage, mem_usage, load_1m) VALUES (NOW, 47.8, 63.0, 2.5)
    server_db_01  (ts, cpu_usage, mem_usage, load_1m) VALUES (NOW, 32.1, 78.4, 1.8)
    server_cache_01 (ts, cpu_usage, mem_usage, load_1m) VALUES (NOW, 12.3, 45.6, 0.9);

方式三:批量造数据(用于测试,造 1000 条)

sql 复制代码
-- 生成从 NOW - 1000s 到 NOW 每秒一条的伪随机数据
INSERT INTO server_web_01 
SELECT 
    _wstart AS ts,
    RANDOM() % 100 AS cpu_usage,
    RANDOM() % 100 AS mem_usage,
    (RANDOM() % 100) / 10.0 AS load_1m
FROM 
    (SELECT _wstart FROM FILL(ts, 1s) RANGE(NOW - 1000s, NOW));

-- 验证写入成功
SELECT COUNT(*) FROM server_web_01;

4.5 查询数据(各种场景)

场景 1:查一台服务器最近 1 小时的数据

sql 复制代码
SELECT * FROM server_web_01
WHERE ts >= NOW - 1h
ORDER BY ts DESC;

场景 2:查所有服务器的平均 CPU(过去 1 小时)

sql 复制代码
SELECT 
    AVG(cpu_usage) AS avg_cpu,
    MAX(cpu_usage) AS max_cpu,
    MIN(cpu_usage) AS min_cpu,
    COUNT(*) AS data_points
FROM cpu_metrics
WHERE ts >= NOW - 1h;

场景 3:按区域聚合

sql 复制代码
SELECT 
    region, 
    AVG(cpu_usage) AS avg_cpu, 
    AVG(mem_usage) AS avg_mem
FROM cpu_metrics
WHERE ts >= NOW - 1d
PARTITION BY region;

场景 4:降采样 --- 每 5 分钟一个聚合点

sql 复制代码
SELECT 
    _wstart AS window_start,
    AVG(cpu_usage) AS avg_cpu,
    MAX(cpu_usage) AS peak_cpu,
    MIN(cpu_usage) AS min_cpu
FROM cpu_metrics
WHERE ts >= NOW - 1h
INTERVAL(5m);

场景 5:找出 CPU > 90% 的异常时刻

sql 复制代码
SELECT 
    tbname, hostname, ts, cpu_usage, mem_usage
FROM cpu_metrics
WHERE cpu_usage > 90 AND ts >= NOW - 1d
ORDER BY ts DESC;

场景 6:TOP N 分析 --- CPU 最高的前 10 台服务器

sql 复制代码
SELECT 
    tbname, hostname, 
    AVG(cpu_usage) AS avg_cpu
FROM cpu_metrics
WHERE ts >= NOW - 1h
PARTITION BY tbname
ORDER BY avg_cpu DESC
LIMIT 10;

场景 7:跨表 JOIN 查询(高级)

sql 复制代码
-- 查 web-01 和 db-01 在相同时刻的 CPU 对比
SELECT 
    a.ts, 
    a.cpu_usage AS web_cpu, 
    b.cpu_usage AS db_cpu,
    (a.cpu_usage - b.cpu_usage) AS diff
FROM server_web_01 a 
JOIN server_db_01 b 
    ON a.ts = b.ts;

4.6 Java(Spring Boot)完整集成

pom.xml 添加依赖:

XML 复制代码
<dependency>
    <groupId>com.taosdata.jdbc</groupId>
    <artifactId>taos-jdbcdriver</artifactId>
    <version>3.3.0</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

application.yml 配置:

XML 复制代码
spring:
  datasource:
    url: jdbc:TAOS-RS://localhost:6041/monitor
    username: root
    password: taosdata
    driver-class-name: com.taosdata.jdbc.rs.RestfulDriver

实体类:

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CpuMetric {
    private Date ts;
    private Double cpuUsage;
    private Double memUsage;
    private Double load1m;
    private String hostname;
    private String region;
    private String env;
}

DAO 层:

java 复制代码
@Repository
public class CpuMetricDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    /** 单条写入(自动根据 hostname 选子表) */
    public void insert(CpuMetric metric) {
        String tableName = "server_" + metric.getHostname().replace("-", "_");
        String sql = String.format(
            "INSERT INTO %s (ts, cpu_usage, mem_usage, load_1m) VALUES (?, ?, ?, ?)",
            tableName
        );
        jdbcTemplate.update(sql, 
            metric.getTs(), metric.getCpuUsage(), metric.getMemUsage(), metric.getLoad1m()
        );
    }
    
    /** 批量写入(按 hostname 分组,每组一个 batch --- 性能最好) */
    public void batchInsert(List<CpuMetric> metrics) {
        // 按 hostname 分组
        Map<String, List<CpuMetric>> grouped = metrics.stream()
            .collect(Collectors.groupingBy(m -> "server_" + m.getHostname().replace("-", "_")));
        
        grouped.forEach((tableName, list) -> {
            String sql = String.format(
                "INSERT INTO %s (ts, cpu_usage, mem_usage, load_1m) VALUES (?, ?, ?, ?)",
                tableName
            );
            jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
                @Override
                public void setValues(PreparedStatement ps, int i) throws SQLException {
                    CpuMetric m = list.get(i);
                    ps.setTimestamp(1, new Timestamp(m.getTs().getTime()));
                    ps.setDouble(2, m.getCpuUsage());
                    ps.setDouble(3, m.getMemUsage());
                    ps.setDouble(4, m.getLoad1m());
                }
                @Override
                public int getBatchSize() {
                    return list.size();
                }
            });
        });
    }
    
    /** 查最近 N 条记录 */
    public List<CpuMetric> queryRecent(String hostname, int limit) {
        String tableName = "server_" + hostname.replace("-", "_");
        String sql = String.format(
            "SELECT ts, cpu_usage, mem_usage, load_1m FROM %s ORDER BY ts DESC LIMIT ?",
            tableName
        );
        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(CpuMetric.class), limit);
    }
    
    /** 按区域聚合查询 */
    public List<Map<String, Object>> aggregateByRegion(int hoursAgo) {
        String sql = """
            SELECT region, 
                   AVG(cpu_usage) AS avg_cpu, 
                   AVG(mem_usage) AS avg_mem,
                   COUNT(*) AS total_points
            FROM cpu_metrics
            WHERE ts >= NOW - ?h
            PARTITION BY region
        """;
        return jdbcTemplate.queryForList(sql, hoursAgo);
    }
}

4.7 对接 Grafana 可视化

  1. 在 Grafana 中安装 TDengine 数据源插件:
复制代码
grafana-cli plugins install tdengine-datasource
  1. 添加数据源:

    • URL: http://你的服务器:6041

    • 数据库:monitor

    • 用户名/密码:root / taosdata

  2. 一个示例面板的查询 SQL:

sql 复制代码
SELECT _wstart AS t, AVG(cpu_usage) AS avg_cpu
FROM cpu_metrics
WHERE ts >= NOW - 1h
AND region = 'us-east-1'
INTERVAL(30s)

4.8 对接 Prometheus(面试加分项)

TDengine 支持 Prometheus remote write/read 协议。

Prometheus 配置:

复制代码
# prometheus.yml
remote_write:
  - url: "http://你的服务器:6041/prometheus/v1/write?db=monitor"

remote_read:
  - url: "http://你的服务器:6041/prometheus/v1/read?db=monitor"

原理:Prometheus 采集的指标数据直接写入 TDengine,Grafana 再从 TDengine 查询,实现采集和存储解耦,解决 Prometheus 单机存储瓶颈。


五、面试高频题 & 参考答案

Q1: TDengine 和 InfluxDB 怎么选?

答: 选型不只看性能。

  • 选 TDengine:团队有 SQL 经验、数据有固定标签结构(1 万+ 设备有相同采集维度),需要高吞吐写入。超级表模型更匹配,SQL 兼容降低学习成本。

  • 选 InfluxDB:多变的无模式数据、临时探索分析为主、生态成熟度要求高。

  • 性能差距:TDengine 写入吞吐(千万级/秒)通常比 InfluxDB 高一个数量级,压缩比也更好。

Q2: 超级表为什么快?

答: 三个核心原因:

  1. 写即顺序 --- 一张子表只存一个设备的数据,写入是顺序追加,天然无随机 IO

  2. 标签→分区裁剪 --- 标签和数据分离,按标签过滤时只扫描匹配的子表,不需要全表扫

  3. 时间分区 --- 数据按时间分区存储,查询时剪切无关分区

Q3: TDengine 如何处理大量设备同时写入?

答: 三层缓冲机制:

  • 数据先进入内存的 WriteCache

  • 达到一定量或超时后合并写入列式文件

  • VNode 分担写入压力 单节点实测 800 万+ 数据点/秒。

Q4: 和常规关系型数据库比有什么缺陷?

答: 主要三点:

  1. 事务能力弱 --- 不支持跨表事务,ACID 不如 MySQL

  2. 小数据量无优势 --- 100 万行以内 MySQL 够用,TDengine 优势体现不出来

  3. 生态仍在成长 --- 不如 InfluxDB/Prometheus 成熟,某些工具对接需要自己写适配

Q5: 数据过期删除怎么做的?

答: 建库时指定 KEEP 参数(如 KEEP 30 天),TDengine 自动在后台清理过期数据文件。不需要写 DELETE 语句,避免了传统数据库大数据量删除的性能灾难。

Q6: TDengine 3.x 和 2.x 的区别?

答: 3.x 是架构重写,主要变化:

  • 从 2.x 的"一个节点一个 dnode"变为"计算存储分离"架构

  • 新增 QNode(查询节点)解耦查询负载

  • 支持 Kubernetes 部署(Operator)

  • 更好的 SQL 兼容性(窗口函数等)

Q7: 怎么保证数据不丢?

答: 三种机制配合:

  1. 多副本 --- 每个 VGroup 配置 1~3 副本,写入同步复制

  2. WAL --- 先写 WAL 日志再写缓存,宕机可恢复

  3. RAFT --- 管理节点用 RAFT 保证元数据一致性


六、30 分钟快速上手指南(自检清单)

步骤 操作 预期结果
1. 安装 docker run tdengine/tdengine taosd 启动
2. 连接 taos 进入 shell 看到 taos> 提示符
3. 建库 CREATE DATABASE demo; USE demo; 切换数据库成功
4. 建超级表 CREATE STABLE ... 超级表创建成功
5. 建子表 CREATE TABLE ... USING ... TAGS(...) 子表创建成功
6. 写入 INSERT INTO ... VALUES(...) 返回 >0 受影响行数
7. 查询 SELECT * FROM cpu_metrics 返回数据
8. 降采样 SELECT ... INTERVAL(1m) 分钟级聚合结果
9. 标签过滤 WHERE region='us-east-1' 只返回该区域数据
10. Java 集成 引入 taos-jdbcdriver CRUD 正常

七、学习路线推荐

复制代码
Day 1:  安装 + 建库建表 + 写入查询(今天的内容)
Day 2:  超级表 + 子表机制深入 + 批量写入性能测试
Day 3:  降采样聚合 + 连续查询 + 数据保留策略
Day 4:  Java/Spring Boot 接入 + 批量写入优化
Day 5:  Grafana 可视化 + Prometheus 对接
Day 6:  集群搭建 + 多副本 + 扩容缩容
Day 7:  性能调优 + 面试题复盘