作者:TCeason 青云科技数据库研发工程师
2000 年至今,MySQL[1] 一直是全球最受欢迎的 OLTP(联机事务处理)数据库,ClickHouse[2] 则是近年来受到高度关注的 OLAP(联机分析处理)数据库。那么二者之间是否会碰撞出什么火花呢?
本文将带领大家 打破异构数据库壁垒,将 MySQL 数据同步至 ClickHouse。
背景
1、MySQL 复制的发展历程
图 1-1 详细罗列了 MySQL 复制的发展历程。
2001 年的 MySQL 3.23 版本就已经支持了同构数据库 异步复制;由于是异步复制,根本无法在实际生产中大批量使用。
2013 年 MySQL 5.7.2 版本支持 增强半同步复制 能力,才勉强算得上是企业级可用的数据同步方案。
2016 年 MySQL 5.7.17 支持了 MGR,并不断地发展成熟,变成了一个金融级别可用的数据同步方案。
而对于同构的 MySQL 数据同步,接下来要做的就是不断地优化体验,提升同步时效性,解决网络异常下的各类问题。
基于此,各大厂商也开始做自己的高可用同步组件。例如由 QingCloud 数据库研发团队研发并开源的 Xenon,就具备了真正的强一致性和高可用能力。
2、MySQL + Xenon
图 1-2 中的 Xenon 是由类 Raft 算法来实现的高可用组件,用来管理 MySQL 选举和探活,并订正数据准确性。MySQL 数据同步则依然使用 Semi-Sync Replication 或者 MGR,从而达到数据强一致性、无中心化自动选主且主从秒级切换,以及依托于云的跨区容灾能力。
ClickHouse 同步 MySQL 数据
为了加速 OLAP 查询,QingCloud MySQL Plus[3](MySQL + Xenon) 借用 ClickHouse 来同步 MySQL 数据。
1、ClickHouse 概述
ClickHouse 是一个用于联机分析 (OLAP) 的列式数据库管理系统 (DBMS)。ClickHouse 构思于 2008 年,最初是为 YandexMetrica(世界第二大Web分析平台)而开发的。多年来一直作为该系统的核心组件被该系统持续使用着,并于 2016 年宣布开源。
从目前最新的 DB-Engines 中可以看到其排名曲线一路高涨,并且各大厂在重要业务上已经大量部署,这是一个很明显的趋势。因此,我们似乎可以认定 ClickHouse 的火热并不只是一时现象,它将长久地存活下去。而且,ClickHouse 灵活的外部表引擎,可轻松实现与 MySQL 的数据同步,接下来让我们了解一下。
2、MySQL Table Engine
MySQL Table Engine 的特性。
- Mapping to MySQL table
- Fetch table struct from MySQL
- Fetch data from MySQL when executing query
ClickHouse 最开始支持表级别同步 MySQL 数据,通过外部表引擎 MySQL Table Engine 来实现同 MySQL 表的映射。从 information_schema
表中获取对应表的结构,将其转换为 ClickHouse 支持的数据结构,此时在 ClickHouse 端,表结构建立成功。但是此时,并没有真正去同步数据。只有向 ClickHouse 中的该表发起请求时,才会主动的拉取要同步的 MySQL 表的数据。
MySQL Table Engine 使用起来非常简陋,但它是非常有意义的。因为这是第一次打通 ClickHouse 和 MySQL 的数据通道。但是,缺点异常明显:
i. 仅仅是对 MySQL 表关系的映射;
ii. 查询时传输 MySQL 数据到 ClickHouse,会给 MySQL 可能造成未知的网络压力和读压力,可能影响 MySQL 在生产中正常使用。
基于 MySQL Table Engine 只能映射 MySQL 表关系的缺点,QingCloud ClickHouse 团队实现了 MySQL Database Engine。
3、MySQL Database Engine
MySQL Database Engine 的特性。
- Mapping to MySQL Database
- Fetch table list from MySQL
- Fetch table struct from MySQL
- Fetch data from MySQL when executing query
MySQL Database Engine 是库级别的映射,要从 information_schema
中拉取待同步库中包含的所有 MySQL 表的结构,解决了需要建立多表的问题。但仍然还有和 MySQL Table Engine 一样的缺点:查询时传输 MySQL 数据到 ClickHouse,给 MySQL 可能造成未知的网络压力和读压力,可能影响 MySQL 在生产中正常使用。
4、借用第三方软件同步
除去上面提到的 MySQL Table Engine 、MySQL Database Engine 两种方式,还有可以采用第三方软件来同步数据,比如 Canal 或者 Kafka,通过解析 MySQL binlog,然后编写程序控制向 ClickHouse 写入。这样做有很大的优势,即同步流程自主可控。但是也带来了额外的问题:
i. 增加了数据同步的复杂度。
ii. 增加了第三方软件,使得运维难度指数级增加。
基于此,我们又可以思考一个问题,ClickHouse 能否主动同步并订阅 MySQL 数据呢?
Materialize MySQL
为了解决 MySQL Database Engine 依然存留的问题,支持 ClickHouse 主动同步并订阅 MySQL 数据,QingCloud ClickHouse 团队自主研发了 MaterializeMySQL[4] 引擎。
1、简述 MaterializeMySQL
MaterializeMySQL 引擎是由 QingCloud ClickHouse 团队自主研发的库引擎,目前作为实验特性合并到 ClickHouse 20.8 版本中,是对 MySQL 库级别关系的映射,通过消费 binlog 存储到 MergeTree 的方式来订阅 MySQL 数据。
具体使用方式就是一条简单的 CREATE DATABASE SQL 示例:
CREATE DATABASE test ENGINE = MaterializeMySQL(
"172.17.0.3:3306", "demo", "root", "123"
)
# 172.17.0.3:3306 - MySQL 地址和端口
# demo - MySQL 库的名称
# root - MySQL 同步账户
# 123 - MySQL 同步账户的密码
MaterializeMySQL 的设计思路
- Check MySQL Vars
- Select history data
- Consume new data
MaterializeMySQL 的设计思路如下:
- 首先检验源端 MySQL 参数是否符合规范;
- 再将数据根据 GTID 分割为历史数据和增量数据;
- 同步历史数据至 GTID 点;
- 持续消费增量数据。
3、MaterializeMySQL 的函数流程
如图 3-1 所示,MaterializeMySQL 函数的主体流程为:
CheckMySQLVars -> prepareSynchronized -> Synchronized
(1)CheckMySQLVars
检验参数比较简单,就是要查询这些参数是否符合预期。
SHOW VARIABLES WHERE (Variable_name = "log_bin"
AND upper(Value) = "ON")
OR (Variable_name = "binlog_format"
AND upper(Value) = "ROW")
OR (Variable_name = "binlog_row_image"
AND upper(Value) = "FULL")
OR (Variable_name = "default_authentication_plugin"
AND upper(Value) = "MYSQL_NATIVE_PASSWORD")
OR (Variable_name = "log_bin_use_v1_row_events"
AND upper(Value) = "OFF");
(2)prepareSynchronized
这一步来实现历史数据的拉取。
- 为先初始化 gtid 信息;
- 为了保证幂等性每次重新同步时,都要清理 ClickHouse MaterializeMySQL 引擎库下的表;
- 重新拉取历史数据,并将 MySQL 表结构在 ClickHouse 端进行改写;
- 建立与 MySQL 的 Binlog 传输通道。
std::optional MaterializeMySQLSyncThread::prepareSynchronized()
{
connection = pool.get();
MaterializeMetadata metadata(
connection, DatabaseCatalog::instance().getDatabase(database_name)->getMetadataPath() + "/.metadata", mysql_database_name, opened_transaction);
if (!metadata.need_dumping_tables.empty())
{
Position position;
position.update(metadata.binlog_position, metadata.binlog_file, metadata.executed_gtid_set);
metadata.transaction(position, [&]()
{
cleanOutdatedTables(database_name, global_context);
dumpDataForTables(connection, metadata, query_prefix, database_name, mysql_database_name, global_context, [this] { return isCancelled(); });
});
}
connection->query("COMMIT").execute();
}
在 MySQL 中,demo 库下有一个表 t ,主键为 ID , 普通列 col_1。
CREATE TABLE demo.t (
id int(11) NOT NULL,
col_1 varchar(20) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE = InnoDB;
在 ClickHouse 中,依然是id 作为主键列,但是,多了隐藏列 _sign 和 _version。
i. _sign:值只有 1 和 -1。其中,1 代表这行数据存在,-1 代表这行数据被删除。
ii. _version:只会读到 version 高的值,会在后台不断合并主键相同的行,最终保留 Version 最高的行。
CREATE TABLE test.t
(
`id` Int32,
`col_1` Nullable(String),
`_sign` Int8 MATERIALIZED 1,
`_version` UInt64 MATERIALIZED 1
)
ENGINE = ReplacingMergeTree(_version)
PARTITION BY intDiv(id, 4294967)
ORDER BY tuple(id)
(3)Synchronized
在 prepareSynchronized 中,我们得到了历史数据以及历史数据位点信息,并且获得了与 MySQL 的 Binlog 传输通道。接下来就是从该位点同步增量数据。通过 readOneBinlogEvent 函数读取每一条 binlog 内容,然后使用 onEvent 转换成 ClickHouse 的语句格式即可。最终为了数据安全性,调用 flushBuffersData 函数将数据落盘。
client.connect();
client.startBinlogDumpGTID(randomNumber(), mysql_database_name, metadata.executed_gtid_set, metadata.binlog_checksum);
Buffers buffers(database_name);
while (!isCancelled())
{
BinlogEventPtr binlog_event = client.readOneBinlogEvent(std::max(UInt64(1), max_flush_time - watch.elapsedMilliseconds()));
if (binlog_event)
onEvent(buffers, binlog_event, *metadata);
if (!buffers.data.empty())
flushBuffersData(buffers, *metadata);
}
HTAP 应用场景
当我们打通了 ClickHouse 和 MySQL 的复制通道,而 ClickHouse 的分析能力又是如此让人惊喜,那么我们是不是可以用 MySQL + ClickHouse 实现 HTAP 呢?
在图 4-1 中的架构,依然使用高可用组件 Xenon 来管理 MySQL 复制,同时 Xenon 增加了对 ClickHouse 的监管,通过 MaterializeMySQL 来同步 MySQL 数据。
在之前的架构图中,使用 MySQL 只读实例来进行商务分析、用户画像等分析业务。而现在可以直接将 ClickHouse 作为一个分析实例加入到 MySQL 复制中,替代一部分只读实例进行分析计算。同时 ClickHouse 本身支持了海量函数来支持分析能力的同时还支持标准 SQL,相信可以让使用者享受到很好的体验。
目前的 ClickHouse 可以支持同步 MySQL 5.7 和 8.0 的数据,不支持同步 MySQL 5.6 的数据。不过,作为一个实验特性, MaterializeMySQL 的时间线相当于是 2001 年刚刚支持复制的 MySQL。欢迎大家一起来贡献和维护 MaterializeMySQL。
[1]. MySQL : https://www.mysql.com/
[2]. ClickHouse : https://clickhouse.tech/docs/en/
[3]. MySQL Plus:https://www.qingcloud.com/products/mysql-plus/
[4]. MaterializeMySQL:https://clickhouse.tech/docs/en/engines/database-engines/materialize-mysql/
关于 RadonDB
RadonDB 开源社区是一个面向云原生、容器化的数据库开源社区, 为数据库技术爱好者提供围绕主流开源数据库(MySQL、PostgreSQL、Redis、MongoDB、ClickHouse 等)的技术分享平台,并提供企业级 RadonDB 开源产品及服务。
目前 RadonDB 开源数据库系列产品已被 光大银行、浦发硅谷银行、哈密银行、泰康保险、太平保险、安盛保险、阳光保险、百年人寿、安吉物流、安畅物流、蓝月亮、天财商龙、罗克佳华、升哲科技、无锡汇跑体育、北京电信、江苏交通控股、四川航空、昆明航空、国控生物 等上千家企业及社区用户采用。
RadonDB 可基于云平台与 Kubernetes 容器平台交付,不仅提供覆盖多场景的数据库产品解决方案,而且提供专业的集群管理和自动化运维能力,主要功能特性包括:高可用主从切换、数据强一致性、读写分离、一键安装部署、多维指标监控&告警、弹性扩容&缩容、横向自由扩展、自动备份&恢复、同城多活、异地灾备 等。RadonDB 仅需企业及社区用户专注于业务层逻辑开发,无需关注集群高可用选型、管理和运维等复杂问题,帮助企业及社区用户大幅度提升业务开发与价值创新的效率!
GitHub:
https://github.com/radondb
本文将介绍 MaterializeMySQL 引擎是如何实现 MySQL 数据同步至 ClickHouse 的。