背景-LMLPHP

2000 年至今,MySQL[1] 一直是全球最受欢迎的 OLTP(联机事务处理)数据库,ClickHouse[2] 则是近年来受到高度关注的 OLAP(联机分析处理)数据库。那么二者之间是否会碰撞出什么火花呢?

本文将带领大家 打破异构数据库壁垒,将 MySQL 数据同步至 ClickHouse。

背景

1、MySQL 复制的发展历程

背景-LMLPHP

图 1-1 详细罗列了 MySQL 复制的发展历程。

2001 年的 MySQL 3.23 版本就已经支持了同构数据库 异步复制;由于是异步复制,根本无法在实际生产中大批量使用。

2013 年 MySQL 5.7.2 版本支持 增强半同步复制 能力,才勉强算得上是企业级可用的数据同步方案。

2016 年 MySQL 5.7.17 支持了 MGR,并不断地发展成熟,变成了一个金融级别可用的数据同步方案。

而对于同构的 MySQL 数据同步,接下来要做的就是不断地优化体验,提升同步时效性,解决网络异常下的各类问题。

基于此,各大厂商也开始做自己的高可用同步组件。例如由 QingCloud 数据库研发团队研发并开源的 Xenon,就具备了真正的强一致性和高可用能力。

2、MySQL + Xenon

背景-LMLPHP

图 1-2 中的 Xenon 是由类 Raft 算法来实现的高可用组件,用来管理 MySQL 选举和探活,并订正数据准确性。MySQL 数据同步则依然使用 Semi-Sync Replication 或者 MGR,从而达到数据强一致性、无中心化自动选主且主从秒级切换,以及依托于云的跨区容灾能力。

ClickHouse 同步 MySQL 数据

为了加速 OLAP 查询,QingCloud MySQL Plus[3](MySQL + Xenon) 借用 ClickHouse 来同步 MySQL 数据。

1、ClickHouse 概述

背景-LMLPHP

ClickHouse 是一个用于联机分析 (OLAP) 的列式数据库管理系统 (DBMS)。ClickHouse 构思于 2008 年,最初是为 YandexMetrica(世界第二大Web分析平台)而开发的。多年来一直作为该系统的核心组件被该系统持续使用着,并于 2016 年宣布开源。

背景-LMLPHP

从目前最新的 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、借用第三方软件同步

背景-LMLPHP

除去上面提到的 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 同步账户的密码

2、MaterializeMySQL 的设计思路

  • Check MySQL Vars
  • Select history data
  • Consume new data

MaterializeMySQL 的设计思路如下:

  1. 首先检验源端 MySQL 参数是否符合规范;
  2. 再将数据根据 GTID 分割为历史数据和增量数据;
  3. 同步历史数据至 GTID 点;
  4. 持续消费增量数据。

3、MaterializeMySQL 的函数流程

背景-LMLPHP

如图 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<MaterializeMetadata> 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 呢?

背景-LMLPHP

在图 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 的。

08-13 16:33