使用 Binlog 和 Canal 从 MySQL 抽取数据

数据抽取是 ETL 流程的第一步。我们会将数据从 RDBMS 或日志服务器等外部系统抽取至数据仓库,进行清洗、转换、聚合等操作。在现代网站技术栈中,MySQL 是最常见的数据库管理系统,我们会从多个不同的 MySQL 实例中抽取数据,存入一个中心节点,或直接进入 Hive。市面上已有多种成熟的、基于 SQL 查询的抽取软件,如著名的开源项目 Apache Sqoop,然而这些工具并不支持实时的数据抽取。MySQL Binlog 则是一种实时的数据流,用于主从节点之间的数据复制,我们可以利用它来进行数据抽取。借助阿里巴巴开源的 Canal 项目,我们能够非常便捷地将 MySQL 中的数据抽取到任意目标存储中。

Canal

Canal 的组成部分

简单来说,Canal 会将自己伪装成 MySQL 从节点(Slave),并从主节点(Master)获取 Binlog,解析和贮存后供下游消费端使用。Canal 包含两个组成部分:服务端和客户端。服务端负责连接至不同的 MySQL 实例,并为每个实例维护一个事件消息队列;客户端则可以订阅这些队列中的数据变更事件,处理并存储到数据仓库中。下面我们来看如何快速搭建起一个 Canal 服务。

配置 MySQL 主节点

MySQL 默认没有开启 Binlog,因此我们需要对 my.cnf 文件做以下修改:

server-id = 1
log_bin = /path/to/mysql-bin.log
binlog_format = ROW

注意 binlog_format 必须设置为 ROW, 因为在 STATEMENTMIXED 模式下, Binlog 只会记录和传输 SQL 语句(以减少日志大小),而不包含具体数据,我们也就无法保存了。

从节点通过一个专门的账号连接主节点,这个账号需要拥有全局的 REPLICATION 权限。我们可以使用 GRANT 命令创建这样的账号:

GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT
ON *.* TO 'canal'@'%' IDENTIFIED BY 'canal';

启动 Canal 服务端

从 GitHub 项目发布页中下载 Canal 服务端代码(链接),配置文件在 conf 文件夹下,有以下目录结构:

canal.deployer/conf/canal.properties
canal.deployer/conf/instanceA/instance.properties
canal.deployer/conf/instanceB/instance.properties

conf/canal.properties 是主配置文件,如其中的 canal.port 用以指定服务端监听的端口。instanceA/instance.properties 则是各个实例的配置文件,主要的配置项有:

# slaveId 不能与 my.cnf 中的 server-id 项重复
canal.instance.mysql.slaveId = 1234
canal.instance.master.address = 127.0.0.1:3306
canal.instance.dbUsername = canal
canal.instance.dbPassword = canal
canal.instance.connectionCharset = UTF-8
# 订阅实例中所有的数据库和表
canal.instance.filter.regex = .*\\..*

执行 sh bin/startup.sh 命令开启服务端,在日志文件 logs/example/example.log 中可以看到以下输出:

Loading properties file from class path resource [canal.properties]
Loading properties file from class path resource [example/instance.properties]
start CannalInstance for 1-example
[destination = example , address = /127.0.0.1:3306 , EventParser] prepare to find start position just show master status

编写 Canal 客户端

从服务端消费变更消息时,我们需要创建一个 Canal 客户端,指定需要订阅的数据库和表,并开启轮询。

首先,在项目中添加 com.alibaba.otter:canal.client 依赖项,构建 CanalConnector 实例:

CanalConnector connector = CanalConnectors.newSingleConnector(
        new InetSocketAddress("127.0.0.1", 11111), "example", "", "");

connector.connect();
connector.subscribe(".*\\..*");

while (true) {
    Message message = connector.getWithoutAck(100);
    long batchId = message.getId();
    if (batchId == -1 || message.getEntries().isEmpty()) {
        Thread.sleep(3000);
    } else {
        printEntries(message.getEntries());
        connector.ack(batchId);
    }
}

这段代码和连接消息系统很相似。变更事件会批量发送过来,待处理完毕后我们可以 ACK 这一批次,从而避免消息丢失。

// printEntries
RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
for (RowData rowData : rowChange.getRowDatasList()) {
    if (rowChange.getEventType() == EventType.INSERT) {
      printColumns(rowData.getAfterCollumnList());
    }
}

每一个 Entry 代表一组具有相同变更类型的数据列表,如 INSERT 类型、UPDATE、DELETE 等。每一行数据我们都可以获取到各个字段的信息:

// printColumns
String line = columns.stream()
        .map(column -> column.getName() + "=" + column.getValue())
        .collect(Collectors.joining(","));
System.out.println(line);

完整代码可以在 GitHub 中找到(链接)。

加载至数据仓库

关系型数据库与批量更新

若数据仓库是基于关系型数据库的,我们可以直接使用 REPLACE 语句将数据变更写入目标表。其中需要注意的是写入性能,在更新较频繁的场景下,我们通常会缓存一段时间的数据,并批量更新至数据库,如:

REPLACE INTO `user` (`id`, `name`, `age`, `updated`) VALUES
(1, 'Jerry', 30, '2017-08-12 16:00:00'),
(2, 'Mary', 28, '2017-08-12 17:00:00'),
(3, 'Tom', 36, '2017-08-12 18:00:00');

另一种方式是将数据变更写入按分隔符分割的文本文件,并用 LOAD DATA 语句载入数据库。这些文件也可以用在需要写入 Hive 的场景中。不管使用哪一种方法,请一定注意要对字符串类型的字段进行转义,避免导入时出错。

基于 Hive 的数据仓库

Hive 表保存在 HDFS 上,该文件系统不支持修改,因此我们需要一些额外工作来写入数据变更。常用的方式包括:JOIN、Hive 事务、或改用 HBase。

数据可以归类成基础数据和增量数据。如昨日的 user 表是基础数据,今日变更的行是增量数据。通过 FULL OUTER JOIN,我们可以将基础和增量数据合并成一张最新的数据表,并作为明天的基础数据:

SELECT
  COALESCE(b.`id`, a.`id`) AS `id`
  ,COALESCE(b.`name`, a.`name`) AS `name`
  ,COALESCE(b.`age`, a.`age`) AS `age`
  ,COALESCE(b.`updated`, a.`updated`) AS `updated`
FROM dw_stage.`user` a
FULL OUTER JOIN (
  -- 增量数据会包含重复数据,因此需要选择最新的那一条
  SELECT `id`, `name`, `age`, `updated`
  FROM (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY `id` ORDER BY `updated` DESC) AS `n`
    FROM dw_stage.`user_delta`
  ) b
  WHERE `n` = 1
) b
ON a.`id` = b.`id`;

Hive 0.13 引入了事务和 ACID 表,0.14 开始支持 INSERTUPDATEDELETE 语句,Hive 2.0.0 则又新增了 Streaming Mutation API,用以通过编程的方式批量更新 Hive 表中的记录。目前,ACID 表必须使用 ORC 文件格式进行存储,且须按主键进行分桶(Bucket)。Hive 会将变更记录保存在增量文件中,当 OrcInputFormat 读取数据时会自动定位到最新的那条记录。官方案例可以在这个链接中查看。

最后,我们可以使用 HBase 来实现表数据的更新,它是一种 KV 存储系统,同样基于 HDFS。HBase 的数据可以直接为 MapReduce 脚本使用,且 Hive 中可以创建外部映射表指向 HBase。更多信息请查看官方网站

初始化数据

数据抽取通常是按需进行的,在新增一张表时,数据源中可能已经有大量原始记录了。常见的做法是手工将这批数据全量导入至目标表中,但我们也可以复用 Canal 这套机制来实现历史数据的抽取。

首先,我们在数据源库中创建一张辅助表:

CREATE TABLE `retl_buffer` (
  id BIGINT AUTO_INCREMENT PRIMARY KEY
  ,table_name VARCHAR(255)
  ,pk_value VARCHAR(255)
);

当需要全量抽取 user 表时,我们执行以下语句,将所有 user.id 写入辅助表中:

INSERT INTO `retl_buffer` (`table_name`, `pk_value`)
SELECT 'user', `id` FROM `user`;

Canal 客户端在处理到 retl_buffer 表的数据变更时,可以从中解析出表名和主键的值,直接反查数据源,将数据写入目标表:

if ("retl_buffer".equals(entry.getHeader().getTableName())) {
    String tableName = rowData.getAfterColumns(1).getValue();
    String pkValue = rowData.getAfterColumns(2).getValue();
    System.out.println("SELECT * FROM " + tableName + " WHERE id = " + pkValue);
}

这一方法在阿里巴巴的另一个开源软件 Otter 中使用。

Canal 高可用

  • Canal 服务端中的实例可以配置一个备用 MySQL,从而能够在双 Master 场景下自动选择正在工作的数据源。注意两台主库都需要打开 log_slave_updates 选项。Canal 会使用自己的心跳机制(定期更新辅助表的记录)来检测主库的存活。
  • Canal 自身也有 HA 配置,配合 Zookeeper,我们可以开启多个 Canal 服务端,当某台服务器宕机时,客户端可以从 ZK 中获取新的服务端地址,继续进行消费。更多信息可以参考 Canal AdminGuide

参考资料

  • https://github.com/alibaba/canal/wiki
  • https://github.com/alibaba/otter/wiki
  • https://www.phdata.io/4-strategies-for-updating-hive-tables/
  • https://hortonworks.com/blog/four-step-strategy-incremental-updates-hive/
  • https://cwiki.apache.org/confluence/display/Hive/Hive+Transactions
  • 24
    点赞
  • 122
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: 可以回答这个问题。canal中间件可以实现redis和mysql数据同步,它可以监控mysql数据库的binlog日志,将数据变更同步到redis中。同时,canal也支持将mysql数据同步到其他数据库,如elasticsearch等。 ### 回答2: Canal中间件是一种用于实现数据数据同步的工具。它可以实现Redis和MySQL之间的数据同步。 首先,我们需要设置Canal中间件与Redis和MySQL的连接。通过配置Canal中间件的连接参数,使其能够连接到Redis和MySQL数据库。可以设置监控的表和字段,以确定需要同步的数据范围。 当MySQL数据库中的数据发生变化时,Canal中间件会自动捕获这些变化并生成相应的Binlog日志。通过监听MySQL数据库的Binlog日志,Canal中间件能够实时获取更新的数据。 接下来,Canal中间件将获取到的数据进行处理,将变化的数据转换成Redis可以接受的数据格式,并将其写入到Redis数据库中。这样就实现了MySQL数据变化的同步到Redis数据库的功能。 通过Canal中间件,我们可以实现MySQL和Redis之间的双向数据同步。当MySQL中的数据发生改变时,Canal中间件会将变化的数据同步到Redis数据库中。同样地,当Redis中的数据发生改变时,Canal中间件也能够捕获这些变化并同步到MySQL数据库中。 这种数据同步的方式可以提高应用程序的性能和可靠性。通过将热点数据存储到Redis中,可以提高读取性能。同时,由于Canal中间件的实时同步机制,可以保证数据的一致性。 总结起来,Canal中间件可以实现Redis和MySQL之间的数据同步。它通过捕获MySQL数据库的Binlog日志,并将变化的数据转换成Redis可以接受的格式,实现了数据的同步。这种方式可以提高应用程序的性能和可靠性。 ### 回答3: Canal中间件是一个开源的数据同步工具,用于将数据库中的数据同步到其他数据源。在实现Redis和MySQL数据同步的场景下,可以通过以下步骤实现数据的同步: 第一步,安装配置Canal中间件。首先需要在服务器上安装Canal中间件,并配置Canal的相关参数,如数据源的地址、端口号、用户名和密码等。 第二步,配置Canal中间件连接MySQL数据库。需要在Canal配置文件中添加MySQL数据源的相关信息,包括MySQL服务器的地址、端口号、用户名和密码等。 第三步,配置Canal中间件连接Redis数据库。同样需要在Canal配置文件中添加Redis数据源的相关信息,包括Redis服务器的地址、端口号、密码等。 第四步,创建数据库表数据的监听和同步。通过创建Canal的实例,并指定需要监听的数据库和表,可以实现对特定表数据的监听和同步。 第五步,通过Canal中间件将MySQL数据同步到Redis。当MySQL数据库中的数据发生变化时,Canal中间件会将变更的数据解析并转发给Redis数据库,实现数据的同步。 通过以上步骤,即可实现Redis和MySQL数据的同步。Canal中间件作为数据同步的桥梁,能够实时监听MySQL数据库的变化,并将变更的数据同步到Redis,确保数据的一致性和及时性。 需要注意的是,在配置Canal中间件时,需要确保CanalMySQL、Redis之间的网络连接正常,并且对应的用户有足够的权限来进行数据操作。同时,还需要进行相关的性能测试和优化,以确保数据同步的效率和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值