我们想要解决的问题

让我们深入一个现实场景:

设想你负责汇总多个销售点系统产生的大量数据。这些数据需要被实时处理并在高级分析仪表板上展示,以提供全面的洞察。

在数据处理领域,速度至关重要。ClickHouse 作为速度之王,

它从不减速且异常迅速。其在并发处理方面的高效性以及成本效益使其成为构建快速数据洞察的首选。

这就引出了一个简单的解决方案:

对于每个销售点,我们添加一段终端代码,将数据插入到 ClickHouse 中。

简单吗?

是的。

能用吗?

不行。

为何向 ClickHouse 写入数据如此困难

这种简单的解决方案让你踏入了 ClickHouse 的第一个致命误区:这里有一系列的误区

当你插入数据时经常会遇到这个错误,它会出现在 ClickHouse 的日志中或对 INSERT 请求的响应中。为了理解这个错误,用户需要基本了解 ClickHouse 中“部分”(part)的概念:

我们不必深入技术细节;只需认识到这一点即可。向 ClickHouse 写入数据时,控制速度和并行性至关重要。ClickHouse 喜欢这样的数据摄入过程:

理想情况下,一个主要的数据源可以以任意速度插入所有数据,但需要有控制的平行性和缓冲。这与 ClickHouse 的偏好完美契合。

因此,在实践中,通常会在 ClickHouse 前面引入一个缓冲区:

这时我们就引入了 Kafka,它是数据缓冲解决方案的灯塔。由于 Kafka 无缝地充当缓冲区的能力,它成为了 ClickHouse 强大的最佳拍档。经过迭代后,我们的解决方案如下所示:

我们在销售点系统中加入一些代码来将数据写入 Kafka,然后设置从 Kafka 到 ClickHouse 的传输。在这个流程图中还有一些魔法般的操作,因为从 Kafka 到 ClickHouse 的传输本身就是一项挑战,但我们稍后会详细讨论。

强大吗?

是的。

可扩展吗?

当然。

简单吗?

并不简单。

如何实现 Kafka 与 ClickHouse 之间的数据传输

从 Kafka 向 ClickHouse 传输数据的关键阶段包括读取 Kafka 主题、将数据转换为 ClickHouse 兼容的格式以及将这些格式化的数据写入 ClickHouse 表中。这里的权衡在于决定在何处执行每个阶段。

每个阶段都会消耗一些资源:

读取阶段:这一初始阶段会消耗 CPU 和网络带宽来从 Kafka 主题拉取数据。

转换过程:转换数据需要 CPU 和内存使用。这是一个直接的资源利用阶段,计算能力重塑数据以符合 ClickHouse 的规范。

写入阶段:最后一步涉及将数据写入 ClickHouse 表中,这也需要 CPU 功率和网络带宽。这是一个常规的过程,确保数据按照分配的资源找到其在 ClickHouse 存储中的位置。

每种集成方法都有其自身的权衡,因此你应该明智选择。

让我们探索实现 Kafka 与 ClickHouse 之间连接的不同选项:

ClickHouse Kafka 引擎

利用 Kafka 内置的 ClickHouse 引擎将数据直接写入 ClickHouse 表中。从高层次来看,它是这样的:

假设我们的销售点终端生成带有新行分隔符的 JSON 数据。

{"user_ts": "SOME_DATE", "id": 123, "message": "SOME_TEXT"}
{"user_ts": "SOME_DATE", "id": 1234, "message": "SOME_TEXT"}

让我们来实现这个 Kafka 引擎:

首先,我们需要通过 Kafka 引擎在 ClickHouse 中为该主题创建一个包装器:

example kafka_stream_engine.sql

   -- Clickhouse queue wrapper
CREATE TABLE demo_events_queue ON CLUSTER '{cluster}' (
-- JSON content schema
user_ts String,
id UInt64,
message String
) ENGINE = Kafka SETTINGS kafka_broker_list = 'KAFKA_HOST:9091',
kafka_topic_list = 'TOPIC_NAME',
kafka_group_name = 'uniq_group_id',
kafka_format = 'JSONEachRow';

在这个查询中,我们设置了三个主要的内容:

  1. 数据 schema:一个包含 3 列的表。
  2. 数据格式:JSON 每行。
  3. Kafka host + Kafka topic。

    接下来,我们需要指定将承载结果数据的目标表:

    /example_projects/clickstream/kafka_stream_engine.sql#L12-L23

-- Table to store data
CREATE TABLE demo_events_table ON CLUSTER '{cluster}' (
topic String,
offset UInt64,
partition UInt64,
timestamp DateTime64,
user_ts DateTime64,
id UInt64,
message String
) Engine = ReplicatedMergeTree('/clickhouse/tables/{shard}/{database}/demo_events_table', '{replica}')
PARTITION BY toYYYYMM(timestamp)
ORDER BY (topic, partition, offset);

这张表以 ReplicatedMergeTree 的形式保存相同的数据,但增加了一些额外的列。这些列将从 KafkaEngine 的元数据中获取。

/example_projects/clickstream/kafka_stream_engine.sql#L25-L34

  -- Delivery pipeline
CREATE MATERIALIZED VIEW readings_queue_mv TO demo_events_table AS
SELECT
-- kafka engine virtual column
_topic as topic,
_offset as offset,
_partition as partition,
_timestamp as timestamp,
-- example of complex date parsing
toDateTime64(parseDateTimeBestEffort(user_ts), 6, 'UTC') as user_ts,
id,
message
FROM demo_events_queue;

作为最后一步,创建一个物化视图,将 KafkaEngine 表与目标表连接起来。

所有这些步骤结合起来产生最终的结果:

 SELECT count(*)
FROM demo_events_table
Query id: f2637cee-67a6-4598-b160-b5791566d2d8 ┌─count()─┐
│ 6502 │
└─────────┘ 1 row in set. Elapsed: 0.336 sec.

在这种选项中,所有三个阶段都在 ClickHouse 内部完成。这对于较小的工作负载是合适的,但在大规模下可能会导致不可靠的性能。此外,当面临资源短缺时,ClickHouse 倾向于优先处理查询工作负载而非非查询工作负载,这可能在高负载下造成额外的交付延迟。

虽然 KafkaEngine 的使用是强大的,但它也带来了一些未解决的挑战:

  • Offset(偏移量)管理:如果 Kafka 中出现格式错误的数据,ClickHouse 可能会变得无响应,直到管理员手动删除偏移量,这是一项固有的劳动密集型任务。

  • 有限的 Observability(可观测性):监控成为一个挑战,因为所有操作都在 ClickHouse 内部进行,需要依赖 ClickHouse 日志作为了解系统活动的唯一途径。

  • Scalability(可扩展性)问题:在 ClickHouse 集群内部处理解析和读取可能会妨碍在需求高峰期读写操作的无缝扩展,可能导致 CPU 和 I/O 并发问题。

在 Kafka Connect 内部

另一方面,Kafka Connect 改变了剧本,将复杂性从 ClickHouse 转移到 Kafka。

这是一种策略游戏,决定在哪里安置数据管理的复杂性。我们将读取/解析/写入的努力放在 Kafka Connect 内部,而 Kafka Connect 本身由 Kafka 托管。

这里的优缺点基本上是一样的,只是将额外的负载从存储转移到缓冲区一侧。你可以在这里查看如何连接的一个示例:这里

外部 Writer(写入器)

对于愿意投资的人来说,External Writer 选项成为顶级选择,承诺以额外的成本换取无与伦比的性能。一个过度简化的解决方案可能看起来像这样:

让我们尝试使用 DoubleCloud 数据传输 来建立这个传输。

资源模型由两个端点(源和目标)和一个传输组成。为了创建它们,我们将使用 Terraform provider。

配置中的关键部分是源端点的解析规则:

/example_projects/clickstream/transfer.tf#L16-L43

    parser {
json {
schema {
fields {
field {
name = "user_ts"
type = "datetime"
key = false
required = false
}
field {
name = "id"
type = "uint64"
key = false
required = false
}
field {
name = "message"
type = "utf8"
key = false
required = false
}
}
}
null_keys_allowed = false
add_rest_column = true
}
}

这个解析器类似于我们在 ClickHouse 中通过 DDL 指定的内容。

一旦我们创建了源端点,添加目标数据库就是一个简单的过程:

/example_projects/clickstream/transfer.tf#L54-L63

          clickhouse_target {
clickhouse_cleanup_policy = "DROP"
connection {
address {
cluster_id = doublecloud_clickhouse_cluster.target-clickhouse.id
}
database = "default"
user = "admin"
}
}

最后,将它们链接在一起形成一个传输:

/example_projects/clickstream/transfer.tf#L67-L75

      resource "doublecloud_transfer" "clickstream-transfer" {
name = "clickstream-transfer"
project_id = var.project_id
source = doublecloud_transfer_endpoint.clickstream-source[count.index].id
target = doublecloud_transfer_endpoint.clickstream-target[count.index].id
type = "INCREMENT_ONLY"
activated = true
}

就这样!您的传输已经启动并运行了。

我们在 DoubleCloud 设计了自己的强大 EL (t) 引擎,Transfer,其中有一个关键特性:Queue Engine -> ClickHouse 传输。在开发这种传输机制时,我们积极应对了持续存在的挑战:

  • 自动偏移量管理:我们实现了自动未解析表,简化了处理损坏数据的过程,并消除了在偏移量管理中需要人工干预的需求。

  • 增强的可观察性:为了克服 ClickHouse 固有的有限可见性问题,我们开发了专用的仪表板和警报,可以实时洞察特定的传输指标。这包括对数据延迟、已传输行数和已传输字节数的全面监控。

  • 动态可扩展性:Transfer 在 Kubernetes、EC2 或 GCP 实例中外部部署传输作业,允许独立于 ClickHouse 集群进行扩展。这确保了在不牺牲性能的情况下满足不同需求的最佳可扩展性。

此外,Transfer 还提供了开箱即用的支持:

  • 自动 scheme 演变:向后兼容的模式更改会自动同步到目标存储。

  • 自动死信队列:任何损坏的数据都会由 Transfer 处理,并组织到 DLQ ClickHouse 表中。

通过 ClickPipes 的外部写入器

ClickPipes 是一个托管集成平台,它使得从多样化的数据源中摄取数据变得像点击几下那么简单。专为最苛刻的工作负载设计,ClickPipes 强大且可扩展的架构确保了一致的性能和可靠性。

我不会复制设置此写入器的完整说明,但您可以在此处找到全面的指南:这里

此写入器类似于任何 DoubleCloud Transfer,但没有自动 scheme 演变功能。

总结

但是在这一系列可能性的迷宫中,如何选择正确的路径呢?

Cue the Graph 是您在这个选择景观中的可靠指南针。可视化优缺点、权衡和收益,图表成为您的指路明星,照亮适合您特定需求的理想路线。

在这份全面的指南中,我们将探索 Kafka-ClickHouse 组合的各个方面,深入研究细节,突出潜在陷阱,并提供做出关键决策的路线图。准备好揭开这对动态组合背后的秘密吧,随着我们探索快节奏的数据传输和处理世界。

要探索 Kafka + Clickhouse 的强大功能,请自由探索 DoubleCloud 堆栈,我们有一些很好的 Terraform 示例

更多

解锁强强组合: 使用 Kafka + ClickHouse 快速搭建流数据实时处理平台(DoubleCloud 博客)的更多相关文章

  1. 如何用MoveIt快速搭建机器人运动规划平台?

    MoveIt = RobotGo,翻译成中文就是“机器人,走你!”所以,MoveIt的主要就是一款致力于让机器人能够自主运动及其相关技术的软件,它的所有模块都是围绕着运动规划的实现而设计的. 两个月前 ...

  2. 使用Jekyll搭建免费的Github Pages个人博客

    一.Git 1.Git概述 Git is a free and open source distributed version control system designed to handle ev ...

  3. 技术人如何利用 github+Jekyll ,搭建一个独立免费的技术博客

    上次有人留言说,技术博客是程序员的标配,但据我所知绝大部分技术同学到现在仍然没有自己的技术博客.原因有很多,有的是懒的写,有的是怕写不好,还有的是一直想憋个大招,幻想做到完美再发出来,结果一直胎死腹中 ...

  4. ELK+kafka docker快速搭建+.NetCore中使用

    ELK开源实时日志分析平台.ELK是Elasticsearch,Logstash,Kibana 的缩写. Elasticsearch:是个开源分布式搜索引擎,简称ESLogstash:是一个完全开源的 ...

  5. 小白入门AI教程:教你快速搭建大数据平台『Hadoop+Spark』

    Apache Spark 简介 Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎.Spark是UC Berkeley AMP lab (加州大学伯克利分校的AMP实验室)所开源 ...

  6. 【分分钟内搭建一个带用户系统的博客程序(一)用户系统】asp.net core的Identity真香,EF真香!

    不用不知道,一用香到爆. 老哥是个屌丝前端,但也想写点web应用耍一耍.之前弄过了NodeJs,也弄过JAVA,最近由于写游戏的原因用C#,索性上手一波asp.net core. 这篇博客记录的是,如 ...

  7. 怎样搭建一个自有域名的 WORDPRESS 博客?

    博客搭建并不复杂,只是过程有点繁琐,适合喜欢折腾的人,主要有下面几个步骤: 新建一个博客文件 购买域名(Domain Name) 注册一个主机空间(Web Host) 域名解析(DNSPod) 安装W ...

  8. 用Hexo搭建属于自己的iOS技术博客,搬家了

    搬家了,本来还打算在博客园混一段时间的,可是当我看到Hexo的时候,已经难以抵挡它的诱惑,简单不简约的界面让我花了整整一天的时间,买域名的过程中发生一点小问题导致DNS解析错误了,但还是成功了.欢迎朋 ...

  9. 强大博客搭建全过程(1)-hexo博客搭建保姆级教程

    1. 前言 本人本来使用国内的开源项目solo搭建了博客,但感觉1核CPU2G内存的服务器,还是稍微有点重,包括服务器内还搭建了数据库.如果自己开发然后搭建,耗费时间又比较多,于是乎开始寻找轻量型的博 ...

  10. 搭建了个人的github.io博客

    地址:http://www.shutu.tech 说明: 基于github + hexo简易搭建的个人博客,用于收藏经典博文及技术文章,也会用于个人的技术成长记录.我是看到这篇文章搭建的:http:/ ...

随机推荐

  1. 【论文阅读】End-to-End Model-Free Reinforcement Learning for Urban Driving Using Implicit Affordances

    文章名:CVPR2020: End-to-End Model-Free Reinforcement Learning for Urban Driving Using Implicit Affordan ...

  2. Spring Reactor基本介绍和案例

    1. Reactor 对比 1.1 Reactor 线程模型 Reactor 线程模型就是通过 单个线程 使用 Java NIO 包中的 Selector 的 select()方法,进行监听.当获取到 ...

  3. 跟我一起学习和开发动态表单系统-后端用spring boot、mybatis实现方法(4)

    ## 动态表单系统:利用 Spring Boot 和 MyBatis 实现后端服务 在现代企业应用中,表单是数据收集和处理的核心部分.然而,传统的表单系统难以适应快速变化的需求.为了解决这个问题,我们 ...

  4. Linux使用Mysql数据库

    启动MySQL: service mysqld start 或者使用如下命令: /etc/init.d/mysqld start 重新启动MySQL: service mysqld restart 或 ...

  5. Qt 学习笔记 - 第四章 - Qt的三驾马车之 - 网络编程

    Qt 学习笔记全系列传送门: Qt 学习笔记 - 第一章 - 快速开始.信号与槽 Qt 学习笔记 - 第二章 - 添加图片.布局.界面切换 Qt 学习笔记 - 第三章 - Qt的三驾马车之一 - 串口 ...

  6. Spring MVC 中 HttpMessageConverter 转换器

    1. Spring MVC 中 HttpMessageConverter 转换器 @ 目录 1. Spring MVC 中 HttpMessageConverter 转换器 2. 补充:什么是 HTT ...

  7. PowerBuilder现代编程方法X01:PowerPlume的X模式

    临渊羡鱼,不如退而结网. PB现代编程方法X01:PowerPlume的X模式 前言 PowerPlume是PowerBuilder深度创新的扩展开发框架(免费商用). 它不是一个大而全的类库(取决于 ...

  8. SQL_left join 和from 两个表的区别

    一个是普通的联接,结果中的记录在两个表中都有.一个是左外联接,结果中的记录在A表中存在,B表中不一定有.相当于a表为主体表,b为辅助表. 例子: mysql> select * from a;+ ...

  9. BigDecimal的精度与刻度

    BigDecimal是Java中用于高精度算术运算的类.当您需要精确地处理非常大或非常小的数字时,例如在金融计算中,它特别有用.由于众所周知得原因,Double这种类型在某些情况下会出现丢失精度的问题 ...

  10. 使用JavaScript编写vue指令v-model,v-model原理实现

    首先先要知道的是v-model的作用是实现数据的双向绑定,即: 数据在视图层的双向响应. 实现思路主要分为两步: 第一步:数据层到视图层的响应 将数据响应到视图层的方式,在vue2使用的是Object ...