【Flink任务改造】结合OLAP之Clickhosue对Flink任务的改造经历

1. 背景

在企业大数据处理系统中,Flink 和 ClickHouse 都是强大的数据处理工具,各自在数据流处理和实时分析方面发挥着重要作用。然而,在实际应用中,部分业务(尤其是需求多变的场景)更适合将更多的数据处理逻辑移至下游的 ClickHouse SQL 层,而不是在 Flink 中进行复杂的处理,具有显著的优势。

2. 现状

在这里插入图片描述

2.1 车联网会将不同主题的数据写入Kafka

比如驾驶信息主题(Kafka)、能耗信息主题(Kafka)、车辆项目流转主题(MYSQL)。
注:由于有个服务专门对这里的话题进行了基础的清洗,所以大数据部门这里直接将这三个话题作为DWD层

2.2 基于Flink消费和回写Kafka构建实时数仓

DWB层将两个驾驶信息主题和能耗信息主题双流Join,然后再和车辆项目流转主题进行异步维表查询关联。
DWS层基于DWB的宽表进行聚合,提取时间戳生成WaterMark,制定WaterMark策略,然后进行分组,开窗,聚合。

3. 为什么改造

Flink实时消费Kafka的主题,经过Flink编码实现了实时指标写入业务DB,这个过程发现几个问题:

  1. 业务DB所在的主从节点磁盘占用异常高。
  2. 业务需求经常变化需要重新大量编码和发版,不是很好维护。
  3. DWB制作好之后需要回写Kafka,增加了和Kafka的交互周期
  4. Flink最优秀的WaterMark处理乱序流在我们业务场景不能最佳实践,因为车端数据上报存在跨天的补发,如果做到严格不丢数据,最大乱序时间需要设置非常大,窗口关闭会很漫长,失去了实时的意义。

对于问题1,Flink实时写入业务DB不是标准的OLAP流程,会导致Binlog大量滚动,导致磁盘占用过多;
对于问题2,业务需求增加或者变化时我们需要想办法迅速满足业务需求;
对于问题3&4,如果将DWS层的聚合移到OLAP,那么DWB制作好后可以不回写Kafka,而是直接写入CK,在Kafka中减少了层数,而且CK天然擅长的就是多列的聚合。

3. 怎么改造

在调研了几款OLAP后,选择了表引擎丰富而且社区成熟性能优秀的Clickhouse,将采用标准的实时写入OLAP,接口调用OALP数据的方式进行改造。
改造主旨就是Flink轻逻辑,改造主旨就是Flink数据处理逻辑移至下游的CK层,既避免了业务DB磁盘问题,又避免了业务需求新增无法敏捷开发的问题。

3.1 第一期

  1. CK建表过程
-- 本地表
CREATE TABLE bigdata.fast_drive_base_local_v1 on cluster xxx_cluster --注意这里加入on cluster xxx_cluster才可以自动在每个节点建表
(
    `create_time` DateTime,
    `local_time` DateTime64(3, 'Asia/Shanghai'),
    `productName` String,
    `time` String,
    `deviceName` String,
    `mode` String,
    `sequence` Int32,
    `throttle` Float64,
    `pilot` String,
    `gnss` String,
    `serverTime` String,
    `steer` Float64,
    `speed` Float64,
    `_ver`  UInt32 --在去重的基础上防止乱序
)
ENGINE = ReplicatedReplacingMergeTree('/clickhouse/tables/{shard}/fast_drive_base_local_v1', '{replica}',_ver)
PARTITION BY toYYYYMMDD(create_time)
PRIMARY KEY time
ORDER BY (time, deviceName) --根据车辆时间和车辆号去重
TTL create_time + toIntervalDay(7) --实时数据处理不需要保存太久的数据
-- 分布式表
CREATE TABLE bigdata.fast_drive_base_distributed on cluster xxx_cluster
(
    `create_time` DateTime,
    `local_time`  DateTime64(3, 'Asia/Shanghai'),
    `productName` String,
    `time`        String,
    `deviceName`  String,
    `mode`        String,
    `sequence`    Int32,
    `throttle`    Float64,
    `pilot`       String,
    `gnss`        String,
    `serverTime`  String,
    `steer`       Float64,
    `speed`       Float64
)
    ENGINE = Distributed('xxx_cluster', 'bigdata', 'fast_drive_base_local_v1', cityHash64(deviceName)); --根据车辆编号hash分布
-- 这里注意关联本地表:fast_drive_base_local_v1
  1. 接口层调用CK的SQL示例/实时指标示例
-- 今日实时里程指标
select deviceName,
       round(CAST(max(manual) as Float64) - CAST(min(manual) as Float64), 2)         as manual,
       round(CAST(max(autonomous) as Float64) - CAST(min(autonomous) as Float64), 2) as autonomous
from (
         select deviceName,
         	    autonomous,
         	    manual
         from default.fast_drive_base_distributed --累计里程明细表
         where toDate(toUnixTimestamp(create_time)) = toDate(now())
           and cast(manual as Float64) != 0
         ) l1
group by deviceName;
  1. 问题
    总体来说第一期已经解决了老业务的大部分问题,但是业务繁忙时CK的CPU会打的有点高,虽然不影响业务我还是进行了第二期的优化。

3.2 第二期

  1. CK建表过程
-- 本地表
CREATE TABLE bigdata.fast_drive_base_local_v2 on cluster xxx_cluster
(
    `create_time` DateTime,
    `local_time`  DateTime64(3, 'Asia/Shanghai'),
    `productName` String,
    `time`        String,
    `deviceName`  String,
    `mode`        String,
    `sequence`    Int32,
    `throttle`    Float64,
    `pilot`       String,
    `gnss`        String,
    `serverTime`  String,
    `steer`       Float64,
    `speed`       Float64,
    `_ver`        UInt32
    PROJECTION    mqtt_drive (
        SELECT *
        ORDER BY
        deviceName,
        time
        ) -- 这里加入了CK的高阶功能:投影
)
    ENGINE = ReplicatedReplacingMergeTree('/clickhouse/tables/{shard}/fast_drive_base_local_v2', '{replica}',_ver)
        PARTITION BY toYYYYMMDD(create_time)
        PRIMARY KEY time
        ORDER BY (time, deviceName)
        TTL create_time + toIntervalDay(7)
-- 分布式表
CREATE TABLE bigdata.fast_drive_base_distributed_v2 on cluster xxx_cluster
(
    `create_time` DateTime,
    `local_time`  DateTime64(3, 'Asia/Shanghai'),
    `productName` String,
    `time`        String,
    `deviceName`  String,
    `mode`        String,
    `sequence`    Int32,
    `throttle`    Float64,
    `pilot`       String,
    `gnss`        String,
    `serverTime`  String,
    `steer`       Float64,
    `speed`       Float64
)
    ENGINE = Distributed('xxx_cluster', 'bigdata', 'fast_drive_base_local_v2', cityHash64(deviceName)); --根据车辆编号hash分布,注意这里对应的本地表
  1. 接口层柔性增加指标
select carNo,
       latitude_nan_cnt, --纬度异常数量/经度同理
       latitude_null_cnt --纬度为空数量/经度同理
from (
         select carNo,
                sum(latitude_nan)  latitude_nan_cnt,
                sum(latitude_null) latitude_null_cnt
         from (
                  select carNo,
                         case when latitude = 'NaN' then 1 else 0 end latitude_nan,
                         case when latitude = '' then 1 else 0 end    latitude_null
                  from bigdata.fast_drive_base_distributed_v2
                  where toDate(toUnixTimestamp(create_time)) = toDate(today())
                  ) l1
         group by carNo
         ) l2
where latitude_nan_cnt != 0
  and latitude_null_cnt != 0
having latitude_nan_cnt > 600
    or latitude_null_cnt > 600
order by carNo;
  1. 结论
    有效解决了一期的问题:
    由于该业务大量指标会按照车辆编号和时间查询/过滤,所有我优化了这种特定的查询,通过Projection预计算和组织数据,显著提高查询效率。
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值