1.数据采集
1. 车辆传感器的数据采集
车辆上安装了各种类型的传感器,用于监测车辆运行状态、环境数据、和驾驶行为。这些传感器的主要作用包括:
发动机状态监测:如转速、温度、燃油消耗等。
驾驶员行为监测:如座椅压力传感器、方向盘力矩传感器。
安全功能监测:如胎压监测(TPMS)、刹车系统状态。
2. TBOX(车载终端)作为数据的汇聚与中转
TBOX(Telematics Box)是车载终端设备,主要功能是采集、处理和传输车辆的传感器数据。其作用包括:
数据汇聚:将车辆内各类传感器的数据统一接入,通过多种通信协议与接口(如CAN总线、以太网等)接收数据。
数据处理:对采集的数据进行预处理(如压缩、筛选),以减少冗余数据量和提升传输效率。
通信桥梁:负责车辆与外界网络(云平台)的通信,支持蜂窝网络(4G/5G)、Wi-Fi等。
3. 数据传输到云端:使用MQTT协议
为了将数据从车辆安全传输到云端,使用了MQTT协议,该协议适合物联网设备。其特点包括:
轻量级:适合低带宽、高延迟的网络环境(如远程地区或弱信号区域)。
发布/订阅模型:车辆的TBOX作为数据发布者,云端服务器作为订阅者,支持大规模设备的并发通信。
QoS机制:通过不同的服务质量等级(QoS 0/1/2),保证数据传输的可靠性。
4. 使用Kafka协议接入终端设备
云端服务器接收到来自多个车辆的数据后,利用Kafka协议将这些数据接入其终端设备。
2.数据聚集
数据中心搭建好Kafka框架,接入各家开放的端口。具体分为如下几步。
1.Kafka 配置
- 针对各车企开放统一的接入端口:
- 开放 Kafka 的监听端口(默认 9092)。
- 设置 advertised.listeners,定义外部设备(如车企端)访问 Kafka 的外部地址。
- 提供可供连接的 Kafka Broker 地址和认证方式。
- 定义特定的 Topic,用于不同车企上传数据。
- 约定传输协议(如 MQTT/Kafka Producer API)。
2.Kafka Producer 接入
MQTT 传输的数据一般都是 JSON
- 各车企开发 Kafka Producer 客户端:
- 配置 Kafka Broker 地址和端口。
- 定义数据主题(Topic):
- 不同车企可使用独立主题。
- 也可根据车辆类型、传感器类型分组。
3.数据ETL
几乎所有数据都是结构化的,已经和各家提前协调好。
基于Flink Sql做ETL
- 数据源数据结构
{
"vehicle_id": "12345",
"timestamp": 1672531200000,
"engine_status": {
"rpm": 3000,
"temperature": 85.5
},
"location": {
"latitude": 37.7749,
"longitude": -122.4194
}
}
- 使用 Flink SQL 定义 Kafka 源表,解析 JSON 数据:
CREATE TABLE vehicle_data (
vehicle_id STRING, -- JSON 中的 "vehicle_id"
timestamp BIGINT, -- JSON 中的 "timestamp"
engine_status ROW< -- 嵌套字段 "engine_status"
rpm INT, -- "engine_status.rpm"
temperature DOUBLE -- "engine_status.temperature"
>,
location ROW< -- 嵌套字段 "location"
latitude DOUBLE, -- "location.latitude"
longitude DOUBLE -- "location.longitude"
>
) WITH (
'connector' = 'kafka', -- 使用 Kafka 作为数据源
'topic' = 'vehicle-topic', -- Kafka 主题名
'properties.bootstrap.servers' = 'localhost:9092', -- Kafka 地址
'format' = 'json', -- 指定数据格式为 JSON
'json.fail-on-missing-field' = 'false', -- 忽略缺失字段
'json.ignore-parse-errors' = 'true' -- 忽略解析错误
);
);
- 解析和查询 JSON 数据
-- 过滤并清洗数据
SELECT
vehicle_id, -- 从 JSON 数据中提取车辆唯一标识符
engine_status.rpm AS engine_rpm, -- 提取嵌套字段 "engine_status.rpm",重命名为 "engine_rpm"
location.latitude AS lat, -- 提取嵌套字段 "location.latitude",并重命名为 "lat"
location.longitude AS lon, -- 提取嵌套字段 "location.longitude",并重命名为 "lon"
CAST(FROM_UNIXTIME(timestamp / 1000) -- 将 UNIX 时间戳(以毫秒为单位)转换为标准时间格式
AS TIMESTAMP(3)) AS event_time, -- 定义为 TIMESTAMP 类型,精确到毫秒
CASE -- 添加派生字段 "rpm_category"
WHEN engine_status.rpm > 4000 THEN 'High'
WHEN engine_status.rpm BETWEEN 1000 AND 4000 THEN 'Medium'
ELSE 'Low'
END AS rpm_category, -- 根据 rpm 的值分类
location.latitude + 0.0001 AS adj_lat, -- 计算派生字段 "adj_lat",假设需要对纬度进行微调
location.longitude + 0.0001 AS adj_lon -- 计算派生字段 "adj_lon",假设需要对经度进行微调
FROM vehicle_data
WHERE
engine_status.rpm > 0 -- 过滤条件:仅保留发动机转速 (rpm) 大于 0 的数据
AND location.latitude IS NOT NULL -- 确保位置数据的纬度不为空
AND location.longitude IS NOT NULL; -- 确保位置数据的经度不为空
- 写到TDengine里,TDengine是为时序数据专门设计的数据库,使用 Flink SQL 定义 TDengine 的目标表。TDengine 的 Flink Connector 支持表的直接映射。
CREATE TABLE processed_data (
engine_rpm INT, -- 发动机转速
latitude DOUBLE, -- 纬度
longitude DOUBLE, -- 经度
event_time TIMESTAMP(3), -- 事件时间
rpm_category STRING, -- RPM 分类
vehicle_id STRING -- 标签字段:车辆 ID
) WITH (
'connector' = 'jdbc', -- 使用 JDBC Connector
'url' = 'jdbc:TAOS-RS://localhost:6030/demo', -- TDengine JDBC URL
'table-name' = 'vehicle_data', -- 超级表名称
'driver' = 'com.taosdata.jdbc.TSDBDriver', -- TDengine JDBC 驱动
'username' = 'root', -- TDengine 用户名
'password' = 'taosdata', -- TDengine 密码
'sink.buffer-flush.max-rows' = '500', -- 每次批量写入最多500行
'sink.buffer-flush.interval' = '2s' -- 每2秒强制写入
);
- TDengine的表结构:
CREATE DATABASE IF NOT EXISTS demo; -- 创建数据库
USE demo;
-- 创建超级表,标签字段替换为 "vehicle_id"
CREATE STABLE vehicle_data (
ts TIMESTAMP, -- 时间戳
engine_rpm INT, -- 发动机转速
latitude DOUBLE, -- 纬度
longitude DOUBLE, -- 经度
rpm_category NCHAR(16) -- RPM 分类
) TAGS (
vehicle_id NCHAR(32) -- 标签:车辆 ID
);
- 写入目标表
INSERT INTO processed_data
SELECT
engine_status.rpm AS engine_rpm, -- 发动机转速
location.latitude AS latitude, -- 纬度
location.longitude AS longitude, -- 经度
CAST(FROM_UNIXTIME(timestamp / 1000) -- 时间戳转换为标准时间格式
AS TIMESTAMP(3)) AS event_time,
CASE -- 根据 RPM 值分类
WHEN engine_status.rpm > 4000 THEN 'High'
WHEN engine_status.rpm BETWEEN 1000 AND 4000 THEN 'Medium'
ELSE 'Low'
END AS rpm_category,
vehicle_id -- 标签字段
FROM vehicle_data
WHERE engine_status.rpm > 0 -- 过滤条件
AND location.latitude IS NOT NULL
AND location.longitude IS NOT NULL;
- 提交运行
import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.TableEnvironment;
public class FlinkSqlToTDengine {
public static void main(String[] args) throws Exception {
// 初始化 Flink Table 环境
EnvironmentSettings settings = EnvironmentSettings.newInstance()
.inStreamingMode()
.build();
TableEnvironment tableEnv = TableEnvironment.create(settings);
// 创建 Kafka 源表
tableEnv.executeSql(
"CREATE TABLE vehicle_data ( " +
" vehicle_id STRING, " +
" timestamp BIGINT, " +
" engine_status ROW<rpm INT, temperature DOUBLE>, " +
" location ROW<latitude DOUBLE, longitude DOUBLE> " +
") WITH ( " +
" 'connector' = 'kafka', " +
" 'topic' = 'vehicle-topic', " +
" 'properties.bootstrap.servers' = 'localhost:9092', " +
" 'format' = 'json', " +
" 'json.fail-on-missing-field' = 'false', " +
" 'json.ignore-parse-errors' = 'true' " +
")"
);
// 创建 TDengine Sink 表
tableEnv.executeSql(
"CREATE TABLE processed_data ( " +
" engine_rpm INT, " +
" latitude DOUBLE, " +
" longitude DOUBLE, " +
" event_time TIMESTAMP(3), " +
" rpm_category STRING, " +
" vehicle_id STRING " + // 标签字段
") WITH ( " +
" 'connector' = 'jdbc', " +
" 'url' = 'jdbc:TAOS-RS://localhost:6030/demo', " +
" 'table-name' = 'vehicle_data', " +
" 'driver' = 'com.taosdata.jdbc.TSDBDriver', " +
" 'username' = 'root', " +
" 'password' = 'taosdata', " +
" 'sink.buffer-flush.max-rows' = '500', " +
" 'sink.buffer-flush.interval' = '2s' " +
")"
);
// 数据处理并写入 TDengine
tableEnv.executeSql(
"INSERT INTO processed_data " +
"SELECT " +
" engine_status.rpm AS engine_rpm, " +
" location.latitude AS latitude, " +
" location.longitude AS longitude, " +
" CAST(FROM_UNIXTIME(timestamp / 1000) AS TIMESTAMP(3)) AS event_time, " +
" CASE " +
" WHEN engine_status.rpm > 4000 THEN 'High' " +
" WHEN engine_status.rpm BETWEEN 1000 AND 4000 THEN 'Medium' " +
" ELSE 'Low' " +
" END AS rpm_category, " +
" vehicle_id " + // 标签字段
"FROM vehicle_data " +
"WHERE engine_status.rpm > 0 " +
" AND location.latitude IS NOT NULL " +
" AND location.longitude IS NOT NULL"
);
System.out.println("Flink SQL ETL job with Super Table and vehicle_id as tags started.");
}
}
4.基于TDengine的数据做一些分析统计
- 获取某个区域最新的车辆状态:
SELECT *
FROM vehicle_data
WHERE region = 'beijing'
ORDER BY ts DESC
LIMIT 10;
- 获取各个区域过去五分钟在线车辆的数量(可以用来制作热力图)
select
region,count(*)
from (
SELECT
visit_type,vehicle_id
from vehicle_data
where ds= now() - 5m
group by region, vehicle_id
) group by region;
- 计算车辆的累计行驶距离
SELECT vehicle_id,
SUM(ST_DISTANCE(latitude, longitude, LAG(latitude), LAG(longitude))) AS total_distance
FROM vehicle_data
GROUP BY vehicle_id;
- 车辆轨迹回放
SELECT
TAGS.vehicle_id AS vehicle_id,
ts,
latitude,
longitude
FROM vehicle_data
WHERE vehicle_id = '12345'
ORDER BY ts ASC;
至此整个数仓就建立完成了,上面的etl 脚本和kafka 维持正常运行即可,失败时自动重启。定时监看 flink 的 web ui 可以获知项目集群的工作状态。