Canal

缓存与数据库一致性解决方案——Canal

排错:消除老年代参数

image-20240921105117026

canal [kə’næl] ,译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费。其诞生的背景是早期阿里巴巴因为杭州和美国双机房部署,存在跨机房同步的业务需求,实现方式主要是基于业务 trigger 获取增量变更。从 2010 年开始,业务逐步尝试数据库日志解析获取增量变更进行同步,由此衍生出了大量的数据库增量订阅和消费业务。所以其核心功能如下:

数据实时备份
异构数据源(elasticsearch、Hbase)与数据库数据增量同步
业务缓存cache 刷新,保证缓存一致性
带业务逻辑的增量数据处理,如监听某个数据的变化做一定的逻辑处理

img

canal是借助于MySQL主从复制原理实现,所以我们接下来先来了解一下主从复制原理

img

大概流程可以理解为如下:

master将改变记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary log events,可以通过show binlog events进行查看);
slave将master的binary log events拷贝到它的中继日志(relay log);
slave重做中继日志中的事件,将改变反映它自己的数据。

canal工作原理

img

原理相对比较简单:

canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议
mysql master收到dump请求,开始推送binary log给slave(也就是canal)
canal解析binary log对象(原始为byte流)

canal组件架构实现

img

我们可以大概看看canal是怎么实现,其内部的组件抽取、封装及其对应的功能实现,以便我们后续在使用上更加得心应手。

说明:

server代表一个canal运行实例,对应于一个jvm
instance对应于一个数据队列 (1个server对应1…n个instance)
instance模块:

eventParser (数据源接入,模拟slave协议和master进行交互,协议解析)
eventSink (Parser和Store链接器,进行数据过滤,加工,分发的工作)
eventStore (数据存储)
metaManager (增量订阅&消费信息管理器)


import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.Message;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;

import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CanalDemo {

    public static void main(String[] args) throws InvalidProtocolBufferException {
        //1.获取 canal 连接对象
        CanalConnector canalConnector = CanalConnectors.newSingleConnector(new InetSocketAddress("localhost", 11111), "example", "", "");
        while (true) {
            //2.获取连接
            canalConnector.connect();
            //3.指定要监控的数据库
            canalConnector.subscribe("canal-demo.*");
            //4.获取 Message
            Message message = canalConnector.get(100);
            List<CanalEntry.Entry> entries = message.getEntries();
            if (entries.size() <= 0) {
                System.out.println("没有数据,休息一会");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                for (CanalEntry.Entry entry : entries) {
                    // 获取表名
                    String tableName = entry.getHeader().getTableName();
                    //  Entry 类型
                    CanalEntry.EntryType entryType = entry.getEntryType();
                    //  判断 entryType 是否为 ROWDATA
                    if (CanalEntry.EntryType.ROWDATA.equals(entryType)) {
                        //  序列化数据
                        ByteString storeValue = entry.getStoreValue();
                        //  反序列化
                        CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(storeValue);
                        // 获取事件类型
                        CanalEntry.EventType eventType = rowChange.getEventType();
                        // 获取具体的数据
                        List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
                        // 遍历并打印数据
                        for (CanalEntry.RowData rowData : rowDatasList) {
                            List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
                            Map<String, Object> bMap = new HashMap<>();
                            for (CanalEntry.Column column : beforeColumnsList) {
                                bMap.put(column.getName(), column.getValue());
                            }
                            Map<String, Object> afMap = new HashMap<>();
                            List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();
                            for (CanalEntry.Column column : afterColumnsList) {
                                afMap.put(column.getName(), column.getValue());
                            }
                            System.out.println("表名:" + tableName + ",操作类型:" + eventType);
                            System.out.println("改前:" + bMap );
                            System.out.println("改后:" + afMap );
                        }
                    }
                }
            }
        }
    }
}

改后:" + afMap );
}
}
}
}
}
}
}


03-08
### 阿里巴巴 Canal MySQL Binlog 同步工具使用指南 #### 一、环境准备 为了确保 Canal 能够正常工作,需先确认 MySQL 已经开启了二进制日志记录功能并设置了唯一的 `server-id` 。这通常是在 MySQL 的配置文件 my.cnf 中完成设置[^1]。 ```ini [mysqld] server-id = 1 log-bin = mysql-bin binlog-format = ROW ``` #### 二、安装部署Canal Server 下载对应版本的 Canal 安装包后解压至指定目录下,在 conf/example/instance.properties 文件内修改目标数据库连接信息等参数。启动服务前还需调整 canal-server.conf 下的一些全局属性来适应具体应用场景需求。 #### 三、运行模式介绍 Canal 支持多种不同的运行方式,包括单机版、集群版以及 Docker 版本。对于初次使用者来说推荐采用最简单的单机模式进行测试验证;而对于生产环境中则建议考虑更为稳定可靠的集群架构方案以保障高可用性。 #### 四、数据解析流程说明 当 Canal 成功连上 MySQL 实例之后会模拟成为其下游节点之一,并通过发送特定命令请求获取最新的变更事件流。这些原始字节序列会被逐步转换成易于理解的对象结构供后续处理模块调用分析[^3]。 #### 五、应用案例分享 借助于强大的插件机制,Canal 不仅可以满足基本的数据复制迁移任务之外还能配合其他第三方软件共同打造更加复杂的企业级解决方案。比如利用 canal2sql 自动生成 SQL 语句辅助开发人员快速定位问题所在位置;亦或是结合 Otter 构建跨平台间无缝衔接的信息交换桥梁等等[^4]。 ```python import com.alibaba.otter.canal.client.CanalConnector; import com.alibaba.otter.canal.protocol.position.EntryPosition; public class SimpleCanalClient { public static void main(String[] args) throws Exception { // 创建链接对象 CanalConnector connector = ... ; try{ EntryPosition position = null; while(true){ // 获取最新位点 position = connector.getLatestEntryPosition(); // 处理接收到的消息... } }finally{ connector.disconnect(); } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值