从 MySQL 到 ClickHouse 实时数据同步 —— MaterializeMySQL + Materialized View

目录

一、总体架构

二、安装配置 MySQL

1. 创建 mysql 用户

2. 建立 MySQL 使用的目录

3. 解压安装包

4. 配置环境变量

5. 创建 MySQL 配置文件

6. MySQL 系统初始化

7. 启动 mysql 服务器

8. 创建 dba 用户

三、配置 MySQL 主从复制

四、在 ClickHouse 中创建 MySQL 引擎数据库

五、在 ClickHouse 中创建物化视图

六、物化视图数据刷新

1. 初始数据装载

2. 增量数据刷新

参考:


        本篇演示使用 ClickHouse 的 MaterializeMySQL 数据库引擎和物化视图,实时将 MySQL 库表中的数据同步到 ClickHouse 的库表中。相关软件版本如下:

  • MySQL:8.0.16
  • ClickHouse:24.1.8

        这种方案的好处是操作简单,几乎不需要额外配置即可实现。


一、总体架构

        总体结构如下图所示。

        ClickHouse 是由四个实例构成的两分片、没分片两副本集群,票选和协调器使用 ClickHouse 自带的 keeper 组件。分片、副本、keeper 节点部署如下表所示。

IP

主机名

实例角色

ClickHouse Keeper

172.18.4.126

node1

分片1副本1

*

172.18.4.188

node2

分片1副本2

*

172.18.4.71

node3

分片2副本1

*

172.18.4.86

node4

分片2副本2

        ClickHouse 集群部署过程参见“ClickHouse 集群部署(不需要 Zookeeper)”。另外在 172.18.16.156 上安装 MySQL,并启动两个实例做主从复制,主库实例用 3306 端口,从库实例用 3307 端口。

二、安装配置 MySQL

        安装配置 MySQL 一主一从双实例。

1. 创建 mysql 用户

# root 用于执行
useradd mysql
passwd mysql

2. 建立 MySQL 使用的目录

# 创建数据目录,确保数据目录 mysqldata 为空
mkdir -p /data/3306/mysqldata
 
# 创建 binlog 目录
mkdir -p /data/3306/dblog
 
# 创建临时目录
mkdir -p /data/3306/tmp
 
# 修改目录属主为 mysql
chown -R mysql:mysql /data
 
# 使用 mysql 用户执行下面的安装过程
su - mysql

3. 解压安装包

# 进入安装目录
cd ~
 
# 从tar包中把提取文件
tar xvf mysql-8.0.16-linux-glibc2.12-x86_64.tar.xz
 
# 建立软连接
ln -s mysql-8.0.16-linux-glibc2.12-x86_64 mysql-8.0.16

4. 配置环境变量

# 将 MySQL 可执行文件所在目录添加到 $PATH 环境变量中
# 编辑文件
vim ~/.bash_profile
 
# 修改或添加如下两行
PATH=$PATH:$HOME/.local/bin:$HOME/bin:/home/mysql/mysql-8.0.16/bin
export PATH
 
# 使配置生效
source ~/.bash_profile

5. 创建 MySQL 配置文件

# 编辑文件
vim /home/mysql/my_3306.cnf

文件内容如下:

[mysqld]
max_allowed_packet=1G
log_timestamps=system
binlog_transaction_dependency_tracking  = WRITESET
transaction_write_set_extraction        = XXHASH64
 
binlog_expire_logs_seconds=259200
lower_case_table_names=1
secure_file_priv=''
log_bin_trust_function_creators=on
character-set-server = utf8mb4
default_authentication_plugin=mysql_native_password
basedir=/home/mysql/mysql-8.0.16-linux-glibc2.12-x86_64
datadir=/data/3306/mysqldata
socket=/data/3306/mysqldata/mysql.sock
 
wait_timeout=30
innodb_buffer_pool_size = 16G
max_connections = 1000
 
default-time-zone = '+8:00'
 
port = 3306
skip-name-resolve 
user=mysql
 
innodb_print_all_deadlocks=1
log_output='table'
slow_query_log = 1
long_query_time = 1
 
tmp_table_size = 32M
 
# 开启 binlog
log-bin=/data/3306/dblog/mysql-bin
log-bin-index = /data/3306/dblog/mysql-bin.index 
 
tmpdir = /data/3306/tmp
 
server-id = 1563306
 
innodb_data_file_path = ibdata1:1G:autoextend
innodb_data_home_dir = /data/3306/mysqldata
 
innodb_log_buffer_size = 16M
innodb_log_file_size = 1G
innodb_log_files_in_group = 3
innodb_log_group_home_dir=/data/3306/dblog
innodb_max_dirty_pages_pct = 90
innodb_lock_wait_timeout = 120
 
gtid-mode = on
enforce_gtid_consistency=true
 
local_infile=0
log_error='/data/3306/mysqldata/master.err'
skip_symbolic_links=yes
 
[mysqldump]
quick
max_allowed_packet = 1G
 
[mysqld_safe]
open-files-limit = 8192

        以下这三个参数必须设置:

default_authentication_plugin=mysql_native_password
gtid-mode = on
enforce_gtid_consistency=true

        如果不设置 default_authentication_plugin,在 ClickHouse 中创建 MySQL 引擎数据库会报以下错误:

Received exception from server (version 24.1.8):
Code: 695. DB::Exception: Received from localhost:9000. DB::Exception: There was an error on [node2:9000]: Code: 695. DB::Exception: Load job 'startup MaterializedMySQL database test_mysql' failed: Code: 537. DB::Exception: Illegal MySQL variables, the MaterializedMySQL engine requires default_authentication_plugin='mysql_native_password'. (ILLEGAL_MYSQL_VARIABLE),. (ASYNC_LOAD_FAILED) (version 24.1.8.22 (official build)). (ASYNC_LOAD_FAILED)

        如果不启用 GTID,在 ClickHouse 中创建 MySQL 引擎数据库会报以下错误:

Received exception from server (version 24.1.8):
Code: 1002. DB::Exception: Received from localhost:9000. DB::Exception: The replication sender thread cannot start in AUTO_POSITION mode: this server has GTID_MODE = OFF instead of ON.. ()

6. MySQL 系统初始化

mysqld --defaults-file=/home/mysql/my_3306.cnf --initialize

7. 启动 mysql 服务器

mysqld_safe --defaults-file=/home/mysql/my_3306.cnf &

8. 创建 dba 用户

# 连接 mysql 服务器
mysql -u root -p -S /data/3306/mysqldata/mysql.sock
 
-- 修改 root 用户密码
alter user user() identified by "123456";
 
-- 创建一个新的 dba 账号
create user 'dba'@'%' identified with mysql_native_password by '123456';
grant all on *.* to 'dba'@'%' with grant option;

        重复执行 2 - 8 步,将 3306 换成 3307,创建从库实例。

三、配置 MySQL 主从复制

        3306 主库实例执行:

-- 查看复制位点
show master status;
-- 创建复制用户并授权
create user 'repl'@'%' identified with mysql_native_password by '123456';
grant replication client,replication slave on *.* to 'repl'@'%';
-- 创建测试库表及数据
create database test;
create table test.t1 (
  id bigint(20) not null auto_increment,
  remark varchar(32) default null comment '备注',
  createtime timestamp not null default current_timestamp comment '创建时间',
  primary key (id));
insert into test.t1 (remark) values ('第一行:row1'),('第二行:row2'),('第三行:row3');

        输出:

mysql> show master status;
+------------------+----------+--------------+------------------+------------------------------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                        |
+------------------+----------+--------------+------------------+------------------------------------------+
| mysql-bin.000001 |      977 |              |                  | ba615057-e11c-11ee-b80e-246e961c91f8:1-3 |
+------------------+----------+--------------+------------------+------------------------------------------+
1 row in set (0.00 sec)
 
mysql> create user 'repl'@'%' identified with mysql_native_password by '123456';
Query OK, 0 rows affected (0.01 sec)
 
mysql> grant replication client,replication slave on *.* to 'repl'@'%';
Query OK, 0 rows affected (0.00 sec)
 
mysql> create database test;
Query OK, 1 row affected (0.00 sec)
 
mysql> create table test.t1 (
    ->   id bigint(20) not null auto_increment,
    ->   remark varchar(32) default null comment '备注',
    ->   createtime timestamp not null default current_timestamp comment '创建时间',
    ->   primary key (id));
Query OK, 0 rows affected (0.01 sec)
 
mysql> insert into test.t1 (remark) values ('第一行:row1'),('第二行:row2'),('第三行:row3');
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

        3307 从库实例执行:

change master to
master_host='172.18.16.156',
master_port=3306,
master_user='repl',
master_password='123456',
master_log_file='mysql-bin.000001',
master_log_pos=977;
 
start slave;
show slave status\G
select user,host from mysql.user;
select * from test.t1;

        输出:

mysql> change master to
    -> master_host='172.18.16.156',
    -> master_port=3306,
    -> master_user='repl',
    -> master_password='123456',
    -> master_log_file='mysql-bin.000001',
    -> master_log_pos=977;
Query OK, 0 rows affected, 2 warnings (0.00 sec)
 
mysql> start slave;
Query OK, 0 rows affected (0.01 sec)
 
mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 172.18.16.156
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000001
          Read_Master_Log_Pos: 2431
               Relay_Log_File: vvgg-z2-music-mysqld-relay-bin.000002
                Relay_Log_Pos: 1776
        Relay_Master_Log_File: mysql-bin.000001
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 2431
              Relay_Log_Space: 1999
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 1563306
                  Master_UUID: ba615057-e11c-11ee-b80e-246e961c91f8
             Master_Info_File: mysql.slave_master_info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: ba615057-e11c-11ee-b80e-246e961c91f8:4-8
            Executed_Gtid_Set: ba615057-e11c-11ee-b80e-246e961c91f8:4-8,
c2df1946-e11c-11ee-8026-246e961c91f8:1-3
                Auto_Position: 0
         Replicate_Rewrite_DB: 
                 Channel_Name: 
           Master_TLS_Version: 
       Master_public_key_path: 
        Get_master_public_key: 0
            Network_Namespace: 
1 row in set (0.00 sec)
 
mysql> select user,host from mysql.user;
+------------------+-----------+
| user             | host      |
+------------------+-----------+
| dba              | %         |
| repl             | %         |
| mysql.infoschema | localhost |
| mysql.session    | localhost |
| mysql.sys        | localhost |
| root             | localhost |
+------------------+-----------+
6 rows in set (0.00 sec)
 
mysql> select * from test.t1;
+----+------------------+---------------------+
| id | remark           | createtime          |
+----+------------------+---------------------+
|  1 | 第一行:row1     | 2024-04-19 08:46:25 |
|  2 | 第二行:row2     | 2024-04-19 08:46:25 |
|  3 | 第三行:row3     | 2024-04-19 08:46:25 |
+----+------------------+---------------------+
3 rows in set (0.00 sec)

        MySQL 主从复制相关配置参见“配置异步复制”。

四、在 ClickHouse 中创建 MySQL 引擎数据库

set allow_experimental_database_materialized_mysql=1;
create database test_mysql on cluster cluster_2S_2R
engine = MaterializeMySQL('172.18.16.156:3307', 'test', 'dba', '123456');

        如果不设置 allow_experimental_database_materialized_mysql=1 会报如下错误:

Received exception from server (version 24.1.8):
Code: 336. DB::Exception: Received from localhost:9000. DB::Exception: There was an error on [node3:9000]: Code: 336. DB::Exception: MaterializedMySQL is an experimental database engine. Enable allow_experimental_database_materialized_mysql to use it. (UNKNOWN_DATABASE_ENGINE) (version 24.1.8.22 (official build)). (UNKNOWN_DATABASE_ENGINE)

        输出:

vvml-yz-hbase-test.172.18.4.188 :) set allow_experimental_database_materialized_mysql=1;

SET allow_experimental_database_materialized_mysql = 1

Query id: 7ce08dff-8d1e-496f-a1af-39c5bec41643

Ok.

0 rows in set. Elapsed: 0.001 sec. 

vvml-yz-hbase-test.172.18.4.188 :) create database test_mysql on cluster cluster_2S_2R
engine = MaterializeMySQL('172.18.16.156:3307', 'test', 'dba', '123456');

CREATE DATABASE test_mysql ON CLUSTER cluster_2S_2R
ENGINE = MaterializeMySQL('172.18.16.156:3307', 'test', 'dba', '123456')

Query id: 610e3c86-c5b6-477c-b4a3-33624809d05c

┌─host──┬─port─┬─status─┬─error─┬─num_hosts_remaining─┬─num_hosts_active─┐
│ node4 │ 9000 │      0 │       │                   3 │                0 │
│ node3 │ 9000 │      0 │       │                   2 │                0 │
│ node2 │ 9000 │      0 │       │                   1 │                0 │
│ node1 │ 9000 │      0 │       │                   0 │                0 │
└───────┴──────┴────────┴───────┴─────────────────────┴──────────────────┘

4 rows in set. Elapsed: 0.086 sec. 

vvml-yz-hbase-test.172.18.4.188 :)

        现在可以查询 MySQL 的库表数据:

vvml-yz-hbase-test.172.18.4.188 :) select * from test_mysql.t1;

SELECT *
FROM test_mysql.t1

Query id: bae55b87-6e80-4e7f-a2c7-b9312ffef999

┌─id─┬─remark───────┬──────────createtime─┐
│  1 │ 第一行:row1 │ 2024-04-19 08:46:25 │
│  2 │ 第二行:row2 │ 2024-04-19 08:46:25 │
│  3 │ 第三行:row3 │ 2024-04-19 08:46:25 │
└────┴──────────────┴─────────────────────┘

3 rows in set. Elapsed: 0.002 sec. 

vvml-yz-hbase-test.172.18.4.188 :) 

五、在 ClickHouse 中创建物化视图

-- 创建数据库
create database db1 on cluster cluster_2S_2R;

-- 创建本地表
create table db1.t1 on cluster cluster_2S_2R
( 
  id Int64,
  remark Nullable(String),
  createtime timestamp,
  primary key id)
engine = ReplicatedMergeTree(
    '/clickhouse/tables/{shard}/t1',
    '{replica}')
order by id;

-- 创建分布式表
create table db1.t1_replica_all ON CLUSTER 'cluster_2S_2R'
as db1.t1
engine = Distributed(cluster_2S_2R, db1, t1, rand());

-- 创建物化视图
create materialized view db1.t1_mv on cluster cluster_2S_2R
to db1.t1_replica_all
as
select * from test_mysql.t1;

        注意创建本地表时的数据类型及其是否允许为空的属性,都要与 MySQL 表的数据类型匹配,否则会报类似下面的错误:

Received exception from server (version 24.1.8):
Code: 53. DB::Exception: Received from localhost:9000. DB::Exception: Type mismatch for column id. Column has type Int64, got type UInt64. (TYPE_MISMATCH)

Received exception from server (version 24.1.8):
Code: 53. DB::Exception: Received from localhost:9000. DB::Exception: Type mismatch for column remark. Column has type Nullable(String), got type String. (TYPE_MISMATCH)

        数据类型的对应如下图所示:

六、物化视图数据刷新

1. 初始数据装载

        ClickHouse 物化视图创建时缺省不会进行初始数据装载。初始装载的方法有两个,一是在创建物化视图时使用 POPULATE。POPULATE 关键字决定了物化视图的更新策略:

  • 若有 POPULATE 则在创建视图的过程会将源表已经存在的数据一并导入,类似于 create table ... as
  • 若无 POPULATE 则物化视图在创建之后没有数据,只会同步物化视图创建之后写入源表的数据

        ClickHouse 官方并不推荐使用 POPULATE,因为在创建物化视图的过程中同时写入的数据不能被插入物化视图。本例使用 TO [db].[table] 语法,物化视图创建后手工执行初始数据装载。

-- MySQL 从库停止复制
stop slave;

-- ClickHouse 初始装载物化视图
insert into db1.t1_mv(id,remark,createtime) select * from test_mysql.t1;

-- MySQL 从库开启复制
start slave;

        这么简单的一句却是实现初始数据装载的关键所在。从库停止复制,不影响主库的正常使用,也就不会影响业务。此时从库的数据处于静止状态,不会产生变化,这使得获取存量数据变得轻而易举。然后执行普通的 insert ... select 语句向物化视图插入数据,数据实际是被写入 db1.t1_replica_all 表。之后在 ClickHouse 集群中的任一实例上,都能从物化视图中查询到一致的 MySQL 存量数据。

vvml-yz-hbase-test.172.18.4.86 :) select * from db1.t1_replica_all;

SELECT *
FROM db1.t1_replica_all

Query id: 5063cd12-6eba-4a81-9c75-d892cb152b17

┌─id─┬─remark───────┬──────────createtime─┐
│  1 │ 第一行:row1 │ 2024-04-19 08:46:25 │
│  2 │ 第二行:row2 │ 2024-04-19 08:46:25 │
│  3 │ 第三行:row3 │ 2024-04-19 08:46:25 │
└────┴──────────────┴─────────────────────┘

3 rows in set. Elapsed: 0.004 sec. 

vvml-yz-hbase-test.172.18.4.86 :) select * from db1.t1_mv;

SELECT *
FROM db1.t1_mv

Query id: e319f96b-f299-41a1-b730-737a42ceedb0

┌─id─┬─remark───────┬──────────createtime─┐
│  1 │ 第一行:row1 │ 2024-04-19 08:46:25 │
│  2 │ 第二行:row2 │ 2024-04-19 08:46:25 │
│  3 │ 第三行:row3 │ 2024-04-19 08:46:25 │
└────┴──────────────┴─────────────────────┘

3 rows in set. Elapsed: 0.006 sec. 

vvml-yz-hbase-test.172.18.4.86 :) 

2. 增量数据刷新

        ClickHouse 的物化视图能够在底层数据更新后,自动更新自己的数据。数据更新包括两个方面的变化:基础表的数据修改和基础表的数据新增。

  • 基础表的数据修改

        如果基础表的数据修改,物化视图会自动更新。这是通过 ClickHouse 的引擎和存储方式来实现的。当基础表的一行记录被修改,ClickHouse 会将这个修改转化为一个新的 INSERT 语句,并且将其发送到物化视图中。这样,物化视图就能够自动更新自己的数据。

  • 基础表的数据新增

        如果基础表的数据新增,物化视图同样会自动更新。这是通过设置物化视图的刷新机制来实现的。刷新机制有两种类型:定时刷新和自动刷新。

        需要注意的是,ClickHouse 的物化视图虽然能够自动更新数据,但是会带来一些性能上的损失,尤其是在基础表数据量较大的情况下。因此,在设计物化视图时,需要考虑这个因素,同时选择合适的刷新机制来平衡性能和数据实时性的需求。

        当然既然选择了 ClickHouse,使用场景就应该是数据新增比较多,而极少去修改或删除。对于一般对实时要求不高的业务场景,定时刷新完全够用了。

-- MySQL 主库修改数据
insert into test.t1 (remark) values ('第四行:row4');
update test.t1 set remark = '第五行:row5' where id = 4;
delete from test.t1 where id =1;
insert into test.t1 (remark) values ('第六行:row6');

此时 MySQL 的数据如下:
mysql> select * from test.t1;
+----+------------------+---------------------+
| id | remark           | createtime          |
+----+------------------+---------------------+
|  2 | 第二行:row2     | 2024-04-19 08:46:25 |
|  3 | 第三行:row3     | 2024-04-19 08:46:25 |
|  4 | 第五行:row5     | 2024-04-19 11:24:33 |
|  5 | 第六行:row6     | 2024-04-19 11:56:20 |
+----+------------------+---------------------+
4 rows in set (0.00 sec)

        ClickHouse 查询数据,所有实例上查询物化视图返回相同的数据:

vvml-yz-hbase-test.172.18.4.86 :) select * from db1.t1_mv order by id;

SELECT *
FROM db1.t1_mv
ORDER BY id ASC

Query id: 1e3f8bb3-6fdd-4f3e-a494-af8c72e3dab2

┌─id─┬─remark───────┬──────────createtime─┐
│  1 │ 第一行:row1 │ 2024-04-19 08:46:25 │
└────┴──────────────┴─────────────────────┘
┌─id─┬─remark───────┬──────────createtime─┐
│  1 │ 第一行:row1 │ 2024-04-19 08:46:25 │
│  1 │ 第一行:row1 │ 2024-04-19 08:46:25 │
│  1 │ 第一行:row1 │ 2024-04-19 08:46:25 │
│  1 │ 第一行:row1 │ 2024-04-19 08:46:25 │
│  2 │ 第二行:row2 │ 2024-04-19 08:46:25 │
│  3 │ 第三行:row3 │ 2024-04-19 08:46:25 │
└────┴──────────────┴─────────────────────┘
┌─id─┬─remark───────┬──────────createtime─┐
│  4 │ 第四行:row4 │ 2024-04-19 11:24:33 │
└────┴──────────────┴─────────────────────┘
┌─id─┬─remark───────┬──────────createtime─┐
│  4 │ 第五行:row5 │ 2024-04-19 11:24:33 │
└────┴──────────────┴─────────────────────┘
┌─id─┬─remark───────┬──────────createtime─┐
│  4 │ 第四行:row4 │ 2024-04-19 11:24:33 │
│  4 │ 第五行:row5 │ 2024-04-19 11:24:33 │
│  4 │ 第四行:row4 │ 2024-04-19 11:24:33 │
│  4 │ 第四行:row4 │ 2024-04-19 11:24:33 │
│  4 │ 第五行:row5 │ 2024-04-19 11:24:33 │
│  4 │ 第五行:row5 │ 2024-04-19 11:24:33 │
└────┴──────────────┴─────────────────────┘
┌─id─┬─remark───────┬──────────createtime─┐
│  5 │ 第六行:row6 │ 2024-04-19 11:56:20 │
└────┴──────────────┴─────────────────────┘
┌─id─┬─remark───────┬──────────createtime─┐
│  5 │ 第六行:row6 │ 2024-04-19 11:56:20 │
└────┴──────────────┴─────────────────────┘

17 rows in set. Elapsed: 0.005 sec. 

        查询本地表,同一分片的副本返回相同的结果,不同分片的数据不同:

-- node1
vvml-yz-hbase-test.172.18.4.126 :) select * from db1.t1 order by id;

SELECT *
FROM db1.t1
ORDER BY id ASC

Query id: c4d50038-f73f-4989-948e-031d6ff5d5ee

┌─id─┬─remark───────┬──────────createtime─┐
│  1 │ 第一行:row1 │ 2024-04-19 08:46:25 │
│  1 │ 第一行:row1 │ 2024-04-19 08:46:25 │
│  1 │ 第一行:row1 │ 2024-04-19 08:46:25 │
│  1 │ 第一行:row1 │ 2024-04-19 08:46:25 │
│  2 │ 第二行:row2 │ 2024-04-19 08:46:25 │
│  3 │ 第三行:row3 │ 2024-04-19 08:46:25 │
│  4 │ 第四行:row4 │ 2024-04-19 11:24:33 │
│  4 │ 第五行:row5 │ 2024-04-19 11:24:33 │
│  4 │ 第四行:row4 │ 2024-04-19 11:24:33 │
│  4 │ 第四行:row4 │ 2024-04-19 11:24:33 │
│  4 │ 第五行:row5 │ 2024-04-19 11:24:33 │
│  4 │ 第五行:row5 │ 2024-04-19 11:24:33 │
└────┴──────────────┴─────────────────────┘
┌─id─┬─remark───────┬──────────createtime─┐
│  5 │ 第六行:row6 │ 2024-04-19 11:56:20 │
└────┴──────────────┴─────────────────────┘

13 rows in set. Elapsed: 0.002 sec. 

vvml-yz-hbase-test.172.18.4.126 :) 

-- node3
vvml-yz-hbase-test.172.18.4.71 :) select * from db1.t1 order by id;

SELECT *
FROM db1.t1
ORDER BY id ASC

Query id: e91e72b7-508a-48a2-b08f-779e49b7cd01

┌─id─┬─remark───────┬──────────createtime─┐
│  1 │ 第一行:row1 │ 2024-04-19 08:46:25 │
└────┴──────────────┴─────────────────────┘
┌─id─┬─remark───────┬──────────createtime─┐
│  4 │ 第四行:row4 │ 2024-04-19 11:24:33 │
└────┴──────────────┴─────────────────────┘
┌─id─┬─remark───────┬──────────createtime─┐
│  4 │ 第五行:row5 │ 2024-04-19 11:24:33 │
└────┴──────────────┴─────────────────────┘
┌─id─┬─remark───────┬──────────createtime─┐
│  5 │ 第六行:row6 │ 2024-04-19 11:56:20 │
└────┴──────────────┴─────────────────────┘

4 rows in set. Elapsed: 0.002 sec. 

vvml-yz-hbase-test.172.18.4.71 :) 

        MySQL 中只有三行数据,ClickHouse 却有17行。ID=1 的行在 MySQL 中被删除,而在 ClickHouse 中并没有删除,而是变为了5行。虽然定义本地表时指定了 id 字段为主键,但 ClickHouse 中自定义的主键并不保证唯一性,即便本地表也是如此。ID=2 和 ID=3 的行在 MySQL 中没有变化,在 ClickHouse 中也分别是唯一的一行。ID=4 的行在 MySQL 中先新增后修改,在 ClickHouse 中都是新增数据。ID=6 的行在 MySQL 中新增一行,在 ClickHouse 中却增加了两行。分布式表的分片规则用的是随机,为什么 MySQL 端新增一条数据,到 ClickHouse 中两个分片都写了呢?

        实验到此实现了数据实时同步,但 ClickHouse 中的数据明显多了很多行,这与选择的表引擎、使用的分片规则都有关系,比较复杂,对数据的解释也变得很重要。所以这里得出的结论是,要用 ClickHouse,最好还是定期从源端导入数据比较靠谱,而且源端最好是只新增数据。

参考:

<think>好的,用户现在想找在ClickHouse中实现MySQL数据实时同步的最佳方案和推荐方法。之前我已经提供过几种方法,比如使用MySQL引擎、MaterializedMySQLmysql表函数和Canal同步,但用户现在可能希望得到更优化的建议,尤其是结合他们提供的引用内容。 首先,我需要回顾用户提供的引用内容。引用[1]提到了Synch这个开源项目,可以作为替代方案。引用[2]指出ClickHouse不适合直接作为主数据库,推荐冷热数据分离。引用[3]提到NineData数据管理平台,可能是一个商业解决方案。引用[4]提到将海量数据迁移到ClickHouse,因为MySQL无法承载。用户的需求是实时同步的最佳实践,所以需要结合这些信息。 接下来,我需要分析之前的方法是否仍然适用。例如,MaterializedMySQL虽然官方不推荐,但用户提到Synch可能更好。Canal的方案可能比较复杂,而NineData可能更简便。同时,用户可能需要考虑冷热数据分离的架构,如引用[2]所述。 用户可能希望了解各种方案的优缺点,并得到最佳推荐的方案。需要比较开源工具如Synch、Canal,以及商业方案如NineData,还有MaterializedMySQL的最新情况。同时,可能需要强调架构设计,比如冷热分离,确保用户理解如何合理使用ClickHouse。 还要注意用户提到的实时性,所以需要评估各方案的实时性能力。例如,Canal和Debezium基于Binlog解析,实时性较好,而NineData和Synch可能有不同的实现方式。此外,是否需要考虑数据一致性、容错机制、扩展性等因素。 最后,可能需要给出具体步骤或配置建议,帮助用户实施最佳方案。同时,回答相关问题,帮助用户进一步了解如何优化和选择。</think>#### ClickHouse 实现 MySQL 数据实时同步的推荐方案与最佳实践 根据实际场景需求和技术特点,以下是 **实时同步的优先级推荐方案** 及其核心实现逻辑: --- ### 推荐方案 1:**使用 Binlog 解析工具(Canal/Debezium + Kafka)** **适用场景**: 高吞吐、低延迟的实时同步,支持复杂业务逻辑和容错处理。 **技术原理**: 通过解析 MySQL 的 Binlog 日志(Row 格式),将数据变更事件推送至消息队列(如 Kafka),再由消费者程序写入 ClickHouse。此方案结合了异步处理的可靠性和实时性[^2][^4]。 #### **实施步骤**: 1. **MySQL 配置**: ```ini [mysqld] server-id=1 log-bin=mysql-bin binlog-format=ROW # 必须为行模式 binlog_row_image=FULL ``` 2. **部署 Canal/Debezium**: - **Canal**(轻量级,适合国内环境): 通过 `canal-server` 监听 MySQL Binlog,定义过滤规则后推送至 Kafka。 - **Debezium**(云原生友好,支持 CDC 标准化): 使用 Kafka Connect 集成 MySQL Connector,自动注册 Schema 并生成 Avro/JSON 消息。 3. **ClickHouse 消费者逻辑**: - 使用 `Kafka` 引擎表接收消息流: ```sql CREATE TABLE kafka_queue ( id Int64, data String, _topic String, _offset UInt64, _timestamp DateTime ) ENGINE = Kafka( 'kafka_host:9092', 'mysql_ch_topic', 'consumer_group', 'JSONEachRow' ); ``` - 通过物化视图实时写入目标表: ```sql CREATE MATERIALIZED VIEW mv_mysql_to_ch TO target_table AS SELECT id, data FROM kafka_queue; ``` **优点**: - 支持断点续传和 Exactly-Once 语义(需 Kafka 配置事务) - 灵活处理数据清洗、分片、去重等逻辑 - 与 ClickHouse 的 `ReplacingMergeTree` 引擎结合可解决数据更新问题[^1] **缺点**: - 架构复杂度较高,需维护多个组件(Kafka、Canal/Debezium) - 需自行处理 Schema 变更兼容性 --- ### 推荐方案 2:**使用 Synch 开源工具** **适用场景**: 简化运维、快速部署的轻量级同步需求。 **技术原理**: Synch 是基于 Go 开发的轻量级数据同步工具,支持 MySQLClickHouse实时增量同步,通过监听 Binlog 并直接写入 ClickHouse,无需中间消息队列[^1]。 #### **实施步骤**: 1. **配置 MySQL(同方案 1 的 Binlog 设置)** 2. **部署 Synch**: ```yaml # synch.yml sources: mysql: host: "mysql_host" user: "synch_user" password: "password" targets: clickhouse: dsn: "tcp://ch_host:9000?database=default" pipelines: - source: mysql target: clickhouse rules: - database: "source_db" table: "source_table" ch_table: "target_table" ``` 3. **启动服务**: ```bash ./synch --config synch.yml ``` **优点**: - 开箱即用,无需编码 - 支持自动建表(根据 MySQL Schema 生成 ClickHouse DDL) - 内置重试机制和监控指标 **缺点**: - 对高并发场景的稳定性需验证 - 缺少消息队列缓冲,可能因 ClickHouse 写入压力导致数据堆积 --- ### 推荐方案 3:**NineData 数据管理平台** **适用场景**: 企业级需求,需可视化操作、权限管控和审计功能。 **技术原理**: NineData 提供全托管的 ClickHouse 数据同步服务,基于日志解析和无锁算法实现秒级延迟,支持跨云、混合云环境[^3]。 #### **操作流程**: 1. 在 NineData 控制台创建 MySQLClickHouse 同步任务 2. 配置映射规则(表结构转换、字段过滤等) 3. 启动全量迁移 + 增量同步,自动监控任务状态 **优点**: - 图形化界面降低运维成本 - 支持数据校验和自动修复 - 提供 SLA 保障和企业级安全特性 **缺点**: - 商业产品需支付服务费用 - 自定义扩展能力有限 --- ### **架构设计最佳实践** 1. **冷热数据分层**: - 将实时更新的热数据暂存于 MySQL,历史冷数据同步ClickHouse,通过视图或联邦查询实现透明访问[^2]。 - 使用 `MySQL` 引擎表关联查询热数据: ```sql CREATE TABLE hot_data ( id UInt64, update_time DateTime ) ENGINE = MySQL(...); ``` 2. **数据一致性保障**: - 为 ClickHouse 表启用 `ReplacingMergeTree` 引擎,按时间版本去重: ```sql CREATE TABLE ch_table ( id UInt64, data String, _version UInt64 ) ENGINE = ReplacingMergeTree(_version) ORDER BY id; ``` 3. **性能调优**: - 批量写入: 控制 Kafka 消费者批次大小(如每 1000 条提交一次) - 异步提交: 使用 `async_insert=1` 和 `wait_for_async_insert=0` 参数提升吞吐量 --- ### 方案对比表 | 方案 | 实时性 | 复杂度 | 成本 | 适用场景 | |---------------------|--------|--------|--------|------------------------------| | Binlog+Kafka | 毫秒级 | 高 | 中 | 大规模、高可靠生产环境 | | Synch | 秒级 | 低 | 低 | 中小规模、快速部署 | | NineData | 秒级 | 中 | 高 | 企业级需求、免运维 | | MaterializedMySQL | 秒级 | 中 | 低 | 实验性场景(官方暂不推荐) | --- ### 总结建议 - **优先选择 Binlog+Kafka 方案**,适合对可靠性和扩展性要求高的场景[^4]。 - **中小团队可尝试 Synch**,快速实现轻量级同步[^1]。 - **企业用户推荐 NineData**,尤其需合规审计和自动化运维时[^3]。 - **避免直接替代 MySQL**,坚持冷热分离架构以发挥 ClickHouse 优势[^2]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值