canal将数据同步到es、mq等实践

Canal数据同步到ES、MQ等实践
Canal是基于MySQL数据库增量日志解析,提供增量数据订阅和消费的工具。本文介绍了其工作原理,包括MySQL主备复制和Canal自身原理,还阐述了同步缓存、下发任务、数据异构等应用场景,以及下载地址、配置步骤、整合方式和客户端模式等内容。

简介

canal [kə'næl],译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费

早期阿里巴巴因为杭州和美国双机房部署,存在跨机房同步的业务需求,实现方式主要是基于业务 trigger 获取增量变更。从 2010 年开始,业务逐步尝试数据库日志解析获取增量变更进行同步,由此衍生出了大量的数据库增量订阅和消费业务。

基于日志增量订阅和消费的业务包括

  • 数据库镜像

  • 数据库实时备份

  • 索引构建和实时维护(拆分异构索引、倒排索引等)

  • 业务 cache 刷新

  • 带业务逻辑的增量数据处理

当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x

工作原理

MySQL主备复制原理

687474703a2f2f646c2e69746579652e636f6d2f75706c6f61642f6174746163686d656e742f303038302f333038362f34363863316131342d653761642d333239302d396433642d3434616335303161373232372e6a7067.jpg

  • MySQL master 将数据变更写入二进制日志( binary log, 其中记录叫做二进制日志事件binary log events,可以通过 show binlog events 进行查看)

  • MySQL slave 将 master 的 binary log events 拷贝到它的中继日志(relay log)

  • MySQL slave 重放 relay log 中事件,将数据变更反映它自己的数据

canal 工作原理
  • canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议

  • MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )

  • canal 解析 binary log 对象(原始为 byte 流)

应用场景

1、同步缓存redis/全文搜索ES

canal一个常见应用场景是同步缓存/全文搜索,当数据库变更后通过binlog进行缓存/ES的增量更新。当缓存/ES更新出现问题时,应该回退binlog到过去某个位置进行重新同步,并提供全量刷新缓存/ES的方法,如下图所示:

13587608-871628bb7897f40b.png

2、下发任务

另一种常见应用场景是下发任务,当数据变更时需要通知其他依赖系统。其原理是任务系统监听数据库变更,然后将变更的数据写入MQ/kafka进行任务下发,比如商品数据变更后需要通知商品详情页、列表页、搜索页等相关系统。这种方式可以保证数据下发的精确性,通过MQ发送消息通知变更缓存是无法做到这一点的,而且业务系统中不会散落着各种下发MQ的代码,从而实现了下发归集,如下图所示。

13587608-f102d71aacf0d302.png

3、数据异构

在大型网站架构中,DB都会采用分库分表来解决容量和性能问题,但分库分表之后带来的新问题。比如不同维度的查询或者聚合查询,此时就会非常棘手。一般我们会通过数据异构机制来解决此问题。

所谓的数据异构,那就是将需要join查询的多表按照某一个维度又聚合在一个DB中。让你去查询。canal就是实现数据异构的手段之一。

13587608-871628bb7897f40b.png

下载地址:

Releases · alibaba/canal · GitHub

配置步骤:

  • mysql主同步开启

## 设置server_id,同一局域网中需要唯一
server-id=101
log-bin=/var/lib/mysql/mysql-bin
binlog-do-db=canal
## 开启二进制日志功能
log-bin=mall-mysql-bin
## 设置二进制日志使用内存大小(事务)
binlog_cache_size=1M
## 设置使用的二进制日志格式(mixed,statement,row)
binlog_format=row
## 二进制日志过期清理时间。默认值为0,表示不自动清理。
expire_logs_days=7
## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。
## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致,1046表结构不存在
slave_skip_errors=1062
##同步的库名(多个库可以写多行,不指定就同步所有)
binlog_do_db = custvideo
binlog_do_db = test_canal
##忽略的库
binlog_ignore_db = mysql
##同步的库的表名
replicate-wild-do-table = custvideo.mac_cust_video
  • 重启mysql服务

图片.png

图片.png

  • 创建数据库用户及权限

CREATE USER canal IDENTIFIED BY 'canal';  
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
-- GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;
FLUSH PRIVILEGES;
  •     canal-deployer配置

最新下载地址:https://github.com/alibaba/canal/releases/download/canal-1.1.6/canal.deployer-1.1.6.tar.gz

mkdir canal-deployer && tar -xvzf canal.deployer-1.1.6.tar.gz -C  canal-deployer

<think>我们被要求使用RocketMQ实现MySQL到Elasticsearch数据同步。根据引用内容,我们可以采用以下方案:使用Canal监控MySQL的binlog,然后将变更事件发送到RocketMQ,最后通过消费者将消息写入Elasticsearch。具体步骤:1.配置MySQL开启binlog(ROW模式)。2.部署CanalServer(canal-deployer)来模拟MySQL的从库,解析binlog。3.配置CanalServer将解析后的数据发送到RocketMQCanal支持RocketMQ作为消息队列)。4.部署并编写一个RocketMQ消费者,该消费者从RocketMQ中获取数据变更消息,并将数据同步Elasticsearch。详细步骤:###一、MySQL配置确保MySQL开启binlog,并设置为ROW模式(引用[3]):```ini[mysqld]log-bin=mysql-binbinlog-format=ROWserver_id=1```###二、Canal部署与配置1.下载并解压Canalcanal-deployer)。2.配置CanalServer(引用[4]和[5]):-修改`conf/example/instance.properties`文件,配置MySQL的连接信息以及需要监听的数据库和表。-设置Canal输出到RocketMQ(配置MQ模式和RocketMQ的地址、Topic等)。示例配置(`instance.properties`):```properties#配置MySQL主库地址canal.instance.master.address=127.0.0.1:3306#配置MySQL用户名密码canal.instance.dbUsername=canalcanal.instance.dbPassword=canal#配置监听的数据库(可以写正则表达式)canal.instance.filter.regex=.*\\..*#设置MQ为RocketMQcanal.serverMode=rocketMQ#RocketMQnameserver地址canal.mq.servers=localhost:9876#设置Topiccanal.mq.topic=example#其他配置根据需要设置```3.启动CanalServer。###三、RocketMQ部署1.安装RocketMQ(包括NameServer和Broker)。2.启动RocketMQ(NameServer和Broker)。###四、编写RocketMQ消费者同步数据到Elasticsearch1.创建一个RocketMQ消费者,订阅Canal发送的Topic(例如:example)。2.解析消息(Canal发送的消息是二进制的,需要按照Canal的Entry协议解析)。3.将解析后的数据转换为Elasticsearch的文档格式,然后使用Elasticsearch客户端写入到Elasticsearch中。示例代码框架(Java):```javapublicclassSyncToESConsumer{publicstaticvoidmain(String[]args)throwsException{//初始化Elasticsearch客户端RestHighLevelClientesClient=newRestHighLevelClient(RestClient.builder(newHttpHost("localhost",9200,"http")));//初始化RocketMQ消费者DefaultMQPushConsumerconsumer=newDefaultMQPushConsumer("sync_es_group");consumer.setNamesrvAddr("localhost:9876");consumer.subscribe("example","*");consumer.registerMessageListener(newMessageListenerConcurrently(){@OverridepublicConsumeConcurrentlyStatusconsumeMessage(List<MessageExt>msgs,ConsumeConcurrentlyContextcontext){for(MessageExtmsg:msgs){try{//解析Canal消息CanalEntry.Entryentry=CanalEntry.Entry.parseFrom(msg.getBody());if(entry.getEntryType()==CanalEntry.EntryType.ROWDATA){CanalEntry.RowChangerowChange=CanalEntry.RowChange.parseFrom(entry.getStoreValue());for(CanalEntry.RowDatarowData:rowChange.getRowDatasList()){//根据事件类型(insert/update/delete)处理switch(rowChange.getEventType()){caseINSERT:caseUPDATE://将rowData转换为JSON,并写入ElasticsearchMap<String,Object>dataMap=convertRowDataToMap(rowData);IndexRequestrequest=newIndexRequest("your_index").id("your_id")//设置文档ID,一般使用数据库主键.source(dataMap);esClient.index(request,RequestOptions.DEFAULT);break;caseDELETE://从Elasticsearch中删除DeleteRequestdeleteRequest=newDeleteRequest("your_index").id("your_id");esClient.delete(deleteRequest,RequestOptions.DEFAULT);break;default:break;}}}}catch(Exceptione){e.printStackTrace();returnConsumeConcurrentlyStatus.RECONSUME_LATER;}}returnConsumeConcurrentlyStatus.CONSUME_SUCCESS;}});consumer.start();}//将RowData转换为Map的方法(根据表结构编写)privateMap<String,Object>convertRowDataToMap(CanalEntry.RowDatarowData){//实现转换逻辑}}```注意:在转换RowData时,需要知道表结构,将每一列映射到Elasticsearch文档的字段。###五、部署Elasticsearch(引用[2])可以使用Docker快速部署Elasticsearch和Kibana(用于查看数据):```bashdockerrun-d--nameelasticsearch-p9200:9200-p9300:9300-e"discovery.type=single-node"elasticsearch:7.7.0dockerrun--namekibana--linkelasticsearch:elasticsearch-p5601:5601-dkibana:7.7.0```###六、测试1.在MySQL中执行增删改操作。2.观察Elasticsearch中对应的索引数据是否同步。###注意事项1.确保Canal、RocketMQElasticsearch之间的网络连通。2.注意消息的顺序性:Canal可以保证单分区(partition)内消息的顺序性,所以同一个数据库表的变更会在同一个分区,因此消费者按顺序处理即可保证顺序。但如果涉及多个表且需要事务一致性,则可能需要额外处理。3.错误处理:在消费者中,如果处理消息失败,可以选择重试或者记录日志人工干预。###相关问题1.如何保证数据同步的幂等性?2.如果同步过程中Elasticsearch不可用,如何保证数据不丢失?3.如何监控数据同步的延迟?4.如何处理表结构变更(DDL)?引用标识:[^1]:与ElasticsearchRepository构建QueryBuilder和SearchQuery类似,ElasticsearchRestTemplate同样是通过构建各种SearchQuery条件查询。[^2]:mysql+canal+rocketMq+elasticsearch实现mysql/elasticsearch数据同步。搭建elkELK官网镜像...[^3]:对于自建MySQL,需要先开启Binlog写入功能,配置binlog-format为ROW模式...[^4]:选择Instance管理->新建Instance填写Instance名称...[^5]:使用canal和rocketmq同步mysql数据到elasticsearch中【canal,rocketmqelasticsearch】...</think>### RocketMQ 实现 MySQL 数据同步Elasticsearch 方案 #### 一、整体架构 ```mermaid graph LR A[MySQL] -->|Binlog| B(Canal Server) B -->|RocketMQ| C[RocketMQ Broker] C -->|消费消息| D[同步程序] D -->|写入| E[Elasticsearch] ``` #### 二、核心组件作用 1. **MySQL** - 需开启 Binlog 并设为 ROW 模式(关键配置)[^3]: ```ini [mysqld] log-bin=mysql-bin binlog-format=ROW server_id=1 ``` 2. **Canal Server** - 伪装成 MySQL 从库,实时解析 Binlog - 将数据变更封装为消息投递到 RocketMQ - 配置要点[^4]: ```properties canal.instance.master.address=127.0.0.1:3306 canal.instance.dbUsername=canal canal.instance.dbPassword=canal canal.mq.topic=db_sync_topic # RocketMQ 目标 Topic ``` 3. **RocketMQ** - 作为消息中间件缓冲数据流 - 提供高可靠异步传输能力 - 关键优势: - 保证消息顺序性(同一主键操作顺序一致) - 支持消息重试和死信队列 4. **同步程序** - 消费 RocketMQ 消息 - 将数据转换为 ES 文档格式 - 使用 Bulk API 批量写入 Elasticsearch #### 三、实施步骤 ##### 1. 环境部署 **Canal & RocketMQ 安装** [^2][^5]: ```bash # Canal Deployer wget https://github.com/alibaba/canal/releases/download/canal-1.1.7/canal.deployer-1.1.7.tar.gz # RocketMQ wget https://archive.apache.org/dist/rocketmq/4.9.4/rocketmq-all-4.9.4-bin-release.zip ``` **Elasticsearch 集群** [^2]: ```bash docker run -d --name elasticsearch -p 9200:9200 \ -e "discovery.type=single-node" elasticsearch:7.17.0 ``` ##### 2. Canal-RocketMQ 配置 编辑 `conf/rocketmq/rocketmq.yml`: ```yaml rocketmq: namesrv: 127.0.0.1:9876 # RocketMQ NameServer producerGroup: canal-producer topic: db_sync_topic partition: 0 ``` ##### 3. 同步程序实现(Java 示例) ```java public class EsSyncConsumer { public static void main(String[] args) { // 初始化 RocketMQ 消费者 DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("es_sync_group"); consumer.setNamesrvAddr("127.0.0.1:9876"); consumer.subscribe("db_sync_topic", "*"); // 初始化 ES 客户端 RestHighLevelClient esClient = new RestHighLevelClient( RestClient.builder(new HttpHost("localhost", 9200, "http"))); consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { for (MessageExt msg : msgs) { CanalEntry.Entry entry = CanalEntry.Entry.parseFrom(msg.getBody()); if (entry.getEntryType() == EntryType.ROWDATA) { processRowChange(entry, esClient); // 处理数据变更 } } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); consumer.start(); } private static void processRowChange(CanalEntry.Entry entry, RestHighLevelClient esClient) { CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { IndexRequest request = new IndexRequest("products"); // 构建 ES 文档 Map<String, Object> jsonMap = new HashMap<>(); for (CanalEntry.Column column : rowData.getAfterColumnsList()) { jsonMap.put(column.getName(), column.getValue()); } request.id(jsonMap.get("id").toString()); request.source(jsonMap); esClient.index(request, RequestOptions.DEFAULT); } } } ``` #### 四、数据一致性保障 1. **幂等性设计**: ```java // 使用数据库主键作为 ES 文档 ID request.id(jsonMap.get("id").toString()); ``` 2. **错误处理机制**: - RocketMQ 重试策略(最多 16 次) - 失败消息进入死信队列人工处理 3. **顺序消费**: ```java consumer.setConsumeMessageBatchMaxSize(1); // 单批次消费1条 ``` #### 五、性能优化建议 1. **批量写入 ES**: ```java BulkRequest bulkRequest = new BulkRequest(); bulkRequest.add(new IndexRequest("products").id("1").source(...)); bulkRequest.add(new IndexRequest("products").id("2").source(...)); esClient.bulk(bulkRequest, RequestOptions.DEFAULT); ``` 2. **异步提交**: ```java esClient.bulkAsync(bulkRequest, RequestOptions.DEFAULT, listener); ``` 3. **资源隔离**: - 为同步程序单独部署 RocketMQ 消费组 - 限制 ES 的 Bulk 线程池大小 #### 六、监控方案 1. **RocketMQ 控制台**: - 查看消息堆积量 - 监控消费延迟 2. **Canal 监控**: ```bash curl http://localhost:8089/metrics ``` 3. **ES 监控**: - Kibana Stack Monitoring - 监控 Bulk 拒绝率 > 实测数据:单节点处理能力可达 **5000-10000 docs/s**(取决于文档大小和硬件配置) --- ### 常见问题解决方案 1. **数据延迟高** - 检查 RocketMQ 消费位点 - 增加同步程序并行度 2. **ES 写入拒绝** ```json PUT _cluster/settings { "persistent": { "thread_pool.write.queue_size": 10000 } } ``` 3. **DDL 变更处理** - 通过 Canal 的 `DDLHandler` 捕获表结构变更 - 调用 ES 的 `_mapping` API 更新索引映射 #### 七、方案优势 1. **解耦**:MQ 层隔离数据生产与消费 2. **弹性**:可水平扩展同步程序实例 3. **可靠**:RocketMQ 保证消息不丢失 4. **实时**:秒级数据延迟(正常情况) [^1]: 与 ElasticsearchRepository 构建 QueryBuilder 和 SearchQuery 类似 [^2]: mysql + canal + rocketMq + elasticsearch实现mysql/elasticsearch数据同步 [^3]: 对于自建 MySQL , 需要先开启 Binlog写入功能 [^4]: 选择Instance 管理-> 新建Instance [^5]: 使用canal和rocketmq同步mysql数据到elasticsearch中 --- ### 相关问题 1. 如何监控 RocketMQ 的数据积压情况? 2. Canal 如何过滤不需要同步的表? 3. Elasticsearch 写入拒绝时如何自动降级? 4. 如何实现 MySQL 分库分表到 ES 的映射? 5. 同步过程中如何保证事务一致性?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值