Flink SQL 实时读取 kafka 数据写入 Clickhouse —— 日志处理(三)

前言

在之前的文章中,我们总结了如何在 Django 项目中进行日志配置,以及如何在 k8s 上部署 Filebeat 采集 PVC 中的日志发送至 Kafka:

本文将总结如何使用 Flink SQL 实时将 kafka 中的日志消息发送至 Clickhouse 表中。

说明
限于文章主题和篇幅,本文不会将如何部署和使用 Flink SQL, 关于这些内容过多而且网上资料也很多,就不再赘述。
本文的核心是说明如何设计 Clickhouse 表结构,以及对应的 Flink SQL 说明。

Clickhouse 表设计


上图中的JSON 内容是kafka 中的日志消息,我们需要读取该消息中的 message 字段(我们的日志信息),然后将该字段中的 time, level, func, trace_id, message 保存至 clickhouse 中。
这里我使用两张表保存日志:

  • adlp_log_local本地表
  • adlp_log分布式表,FlinkSQL 实时写入分布式表

adlp_log_local 本地表

create table if not exists cloud_data.adlp_log_local on cluster perftest_5shards_2replicas
(
    `dt`             DateTime64(3),
    `level`          LowCardinality(String),
    `trace_id`       String,
    `func`           String,
    `message`        String,

    -- 建立索引加速低命中率内容的查询
    INDEX idx_trace_id `trace_id` TYPE tokenbf_v1(4096, 2, 0) GRANULARITY 2,
    INDEX idx_message `message` TYPE tokenbf_v1(30720, 2, 0) GRANULARITY 1
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{layer}-{shard}/cloud_data/adlp_log_local', '{replica}')
    PARTITION BY toYYYYMMDD(dt)
    PRIMARY KEY (dt, trace_id)
    ORDER BY (dt, trace_id)
    TTL toDateTime(dt) + toIntervalDay(30);

字段说明

  • dt (DateTime64(3)): 存储日志时间戳,精确到毫秒。
  • level (LowCardinality(String)): 存储日志级别,如 INFOERROR 等,使用 LowCardinality 优化存储和查询。
  • trace_id (String): 存储追踪 ID,通常用于关联一系列相关的日志记录。
  • func (String): 存储函数或方法名称,表示日志产生的位置。
  • message(String): 存储日志消息的具体内容。

索引

  • idx_trace_id: 使用 tokenbf_v1 类型的布隆过滤器索引(tokenbf_v1(4096, 2, 0)),在 trace_id 字段上创建,粒度为 2。布隆过滤器索引适合低命中率的查询,能够快速过滤出大多数不匹配的记录。
  • idx_message: 使用 tokenbf_v1 类型的布隆过滤器索引(tokenbf_v1(30720, 2, 0)),在 message 字段上创建,粒度为 1。同样用于加速低命中率的查询。

存储引擎

  • ReplicatedMergeTree: 使用分布式和复制的存储引擎,路径模板为 /clickhouse/tables/{layer}-{shard}/cloud_data/adlp_log_local,副本名称为 {replica},保证数据的高可用性和一致性。

分区和排序

  • 分区 (PARTITION BY): 按 dt 字段的年月日(toYYYYMMDD(dt))进行分区,有助于管理和查询按天划分的数据。
  • 主键 (PRIMARY KEY): 主键由 dttrace_id 组成,有助于高效查询。
  • 排序 (ORDER BY): 按 dttrace_id 字段排序,优化基于时间和 trace ID 的查询。

数据生命周期 (TTL)

  • TTL (Time To Live): 配置数据的生存时间,数据在 dt 字段的时间加上 30 天后自动过期删除,保持数据表的清洁和高效。

adlp_log 分布式表

create table if not exists cloud_data.adlp_log on cluster perftest_5shards_2replicas
(
    `dt`             DateTime64(3),
    `level`          LowCardinality(String),
    `trace_id`       String,
    `func`           String,
    `message`        String
)
ENGINE = Distributed('perftest_5shards_2replicas', 'cloud_data', 'adlp_log_local', rand());

字段说明
与本地表 adlp_log_local 相同,包含以下字段:

  • dt (DateTime64(3))
  • level (LowCardinality(String))
  • trace_id (String)
  • func (String)
  • message (String)

存储引擎
Distributed: 分布式引擎,允许将数据分布到多个分片和副本中。参数解释如下:

  • 集群名称 (perftest_5shards_2replicas): 指定集群的名称。
  • 数据库 (cloud_data): 数据库名称。
  • 表 (adlp_log_local): 本地表的名称。
  • 分片键 (rand()): 使用随机函数进行数据分片,保证数据均匀分布。

Flink SQL 说明

创建 Source Table (Kafka) 连接器表

CREATE TEMPORARY TABLE source_table (
    message STRING
) WITH (
    'connector' = 'kafka',
    'topic' = 'filebeat_logs',
    'properties.bootstrap.servers' = '127.0.0.1:9092',
    'properties.group.id' = 'prod-logs-k2c',
    'scan.startup.mode' = 'earliest-offset',
    'format' = 'json',
    'json.ignore-parse-errors' = 'false',
    'json.fail-on-missing-field' = 'false',
    'properties.security.protocol' = 'SASL_PLAINTEXT',
    'properties.sasl.mechanism' = 'PLAIN',
    'properties.sasl.jaas.config' = 'org.apache.kafka.common.security.plain.PlainLoginModule required username="admin" password="admin";'
);

创建 Sink Table (Clickhouse) 连接器

CREATE TEMPORARY TABLE sink_table (
    `dt` TIMESTAMP(3),
    `level` STRING ,
    `trace_id` STRING ,
    `func` STRING ,
    `message` STRING
) WITH (
  'connector' = 'clickhouse',
  'url' = 'clickhouse://127.0.0.1:8123',
  'username' = 'admin',
  'password' = 'admin',
  'database-name' = 'cloud_data',
  'table-name' = 'adlp_log',
  'use-local' = 'true',
  'sink.batch-size' = '1000',
  'sink.flush-interval' = '1000',
  'sink.max-retries' = '10',
  'sink.update-strategy' = 'insert',
  'sink.sharding.use-table-definition' = 'true',
  'sink.parallelism' = '1'
);

解析 Message 写入 Sink

INSERT INTO sink_table
SELECT 
    TO_TIMESTAMP(JSON_VALUE(message, '$.time'), 'yyyy-MM-dd HH:mm:ss') AS dt,
    JSON_VALUE(message, '$.level') AS level,
    JSON_VALUE(message, '$.trace_id') AS trace_id,
    JSON_VALUE(message, '$.func') AS func,
    JSON_VALUE(message, '$.message') AS message
FROM source_table;

注意:
这里在写入的时候默认我们的日志格式是 JSON 的,如果我们的日志发送到 kafka 不是 JSON 格式的,上边的 JSON_VALUE 可能会报错。当然,我们也可以在条件中加上是否为 JSON 判断,但是我觉得没必要。

日志查询演示

我们的日志导入成功后,可以通过第三方查询工具查询 clickhouse 数据源,我这里使用的是 superset 去查询 clickhouse 数据源。
通过 trace_id 查询整个执行链路的日志
image.png
查询错误日志信息
image.png

全文检索 message 日志信息
image.png

更多扩展

  • superset 是一个强大的 BI 工具,可以将我们的日志中的一些指标做成看板,比如说关键错误日志数量,然后设置告警,发送通知。
  • 通过 Flink SQL 实时将我们的日志从 kafka 中写入 clickhouse ,结合 clickhouse 强大的查询功能,以及 superset 强大的 BI 功能,可以充分挖掘业务日志中的潜在价值。

总结

本文总结了如何使用使用 Clickhouse 保存日志数据,以及如何通过 Flink SQL 将我们的日志实时从 kafka 同步至 clickhouse,然后在结合强大的第三方查询 BI 工具 superset,玩转业务日志,挖掘业务日志的潜在价值。
本文设计到的技能知识点比较多,需要熟悉 Clickhouse, Kafka, FlinkSQL, Superset 等,我之前的文章中总结了一些关于 Clickhouse 和 Kafka 相关的内容,感兴趣的读者可以看看:

clickhouse

kafka

superset

### 使用 Flink SQLKafka 消费数据写入 StarRocks 的实现方法 #### 解决方案概述 为了实现从 Kafka 消费数据并通过 Flink SQL 将其写入 StarRocks,需要配置 Kafka 和 StarRocks 的连接器,并编写相应的 Flink SQL 查询语句。以下是详细的实现过程。 --- #### 1. 配置环境依赖 在 Maven 工程中引入必要的依赖项,包括 Kafka 连接器和 StarRocks 连接器: ```xml <dependencies> <!-- Flink SQL Core --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-sql-client_${scala.binary.version}</artifactId> <version>${flink.version}</version> </dependency> <!-- Kafka Connector --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-connector-kafka_${scala.binary.version}</artifactId> <version>${flink.version}</version> </dependency> <!-- StarRocks JDBC Driver --> <dependency> <groupId>com.starrocks</groupId> <artifactId>starrocks-jdbc</artifactId> <version>${starrocks.jdbc.version}</version> </dependency> <!-- StarRocks Flink Connector (if available) --> <dependency> <groupId>com.starrocks.connector</groupId> <artifactId>flink-starrocks-connector</artifactId> <version>${starrocks.connector.version}</version> </dependency> </dependencies> ``` --- #### 2. 创建 Kafka 表 定义一个用于消费 Kafka 数据的表结构。假设 Kafka 主题名为 `kafka_topic`,消息格式为 JSON。 ```sql CREATE TABLE kafka_source ( id BIGINT, name STRING, age INT, event_time TIMESTAMP(3), WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND ) WITH ( 'connector' = 'kafka', 'topic' = 'kafka_topic', 'properties.bootstrap.servers' = 'localhost:9092', 'format' = 'json' ); ``` 此部分说明了如何创建一个基于 Kafka 的源表[^1]。 --- #### 3. 创建 StarRocks 表 定义目标表以存储来自 Kafka数据。假设 StarRocks 表名是 `starrocks_table`。 ```sql CREATE TABLE starrocks_sink ( id BIGINT, name STRING, age INT, PRIMARY KEY(id) NOT ENFORCED ) WITH ( 'connector' = 'starrocks', 'jdbc-url' = 'jdbc:mysql://starrocks_host:9030?load balancing=hash&table_location=prefixed', 'username' = 'root', 'password' = '', 'database-name' = 'test_db', 'table-name' = 'starrocks_table', 'sink.properties.format' = 'parquet', -- 可选参数 'sink.buffer-flush.max-bytes' = '10485760', -- 缓冲区大小,默认1MB 'sink.buffer-flush.interval' = '1s' -- 刷新间隔时间 ); ``` 此处展示了如何设置 StarRocks 作为目标表[^3]。 --- #### 4. 插入数据到 StarRocks 通过 INSERT INTO 语句将 Kafka 中的数据写入 StarRocks。 ```sql INSERT INTO starrocks_sink SELECT id, name, age FROM kafka_source; ``` 该操作实现了从 Kafka 源表向 StarRocks 目标表的数据传输[^4]。 --- #### 示例代码总结 完整的流程如下所示: ```sql -- Step 1: 定义 Kafka Source Table CREATE TABLE kafka_source ( id BIGINT, name STRING, age INT, event_time TIMESTAMP(3), WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND ) WITH ( 'connector' = 'kafka', 'topic' = 'kafka_topic', 'properties.bootstrap.servers' = 'localhost:9092', 'format' = 'json' ); -- Step 2: 定义 StarRocks Sink Table CREATE TABLE starrocks_sink ( id BIGINT, name STRING, age INT, PRIMARY KEY(id) NOT ENFORCED ) WITH ( 'connector' = 'starrocks', 'jdbc-url' = 'jdbc:mysql://starrocks_host:9030?load balancing=hash&table_location=prefixed', 'username' = 'root', 'password' = '', 'database-name' = 'test_db', 'table-name' = 'starrocks_table', 'sink.properties.format' = 'parquet', 'sink.buffer-flush.max-bytes' = '10485760', 'sink.buffer-flush.interval' = '1s' ); -- Step 3: 执行插入操作 INSERT INTO starrocks_sink SELECT id, name, age FROM kafka_source; ``` --- ### 注意事项 - 确保使用的 Flink 版本与 CDC 版本兼容。 - 如果遇到版本不匹配问题,请自行编译适合的 CDC 版本或调整 Flink 版本。 - 星环科技官方文档提供了更多关于 StarRocks Flink Connector 的详细信息。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一切如来心秘密

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值