canal针对分库分表场景的高可用架构设计与应用

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: canal针对分库分表场景的高可用架构设计与应用

一 架构设计



网络异常,图片无法展示
|


简单架构设计


说明:

  1. 两个mysql库中均创建有canal/canal的账户;
  2. 这里A、B两个mysql库是用来模拟t_dept进行分库分表;
  3. 另外,在A、B两种表中都创建有表t_canal.
  4. canal原理: 可查看文章【了解canal,看这个就够了】
  5. 安装与搭建流程:可参考文章【canal应用-1个server+2个instance+2个client+2个mysql】


处理分表分库的场景,主要是要使用配置group-instance.xml。group-instance主要针对需要进行多库合并时,可以将多个物理instance合并为一个逻辑instance,提供客户端访问。


比如产品数据拆分了4个库,每个库会有一个instance,如果不用group,业务上要消费数据时,需要启动4个客户端,分别链接4个instance实例。使用group后,可以在canal server上合并为一个逻辑instance,只需要启动1个客户端,链接这个逻辑instance即可。


二 关键实现流程


2.1 canal.properties配置文件


canal.properties是对应一个canal server的全局配置,保存位置:/usr/local/hadoop/app/canal_group/conf/canal.properties。配置修改内容如下:


canal.id = 1 #唯一标识
canal.ip =192.168.175.20 # client访问canal server的ip地址
canal.port = 11111  # client访问canal server的端口
#canal.instance.global.spring.xml = classpath:spring/file-instance.xml   #原来是这个
canal.instance.global.spring.xml = classpath:spring/group-instance.xml  #启动这个
#其他配置保持默认即可.


2.2 instance.properties配置文件


使用如下命令复制出两个代表canal instance的文件夹:


cp -R example t_dept;
cp -R example t_canal;
rm -rf example;


调整配置文件/usr/local/hadoop/app/canal/conf/t_dept/instance.properties如下:


#canal.instance.master.address=192.168.175.21:3306 #原来的
canal.instance.master1.address=192.168.175.21:3306 #新增,与group-instance.xml的对应
canal.instance.master2.address=192.168.175.22:3306 #新增,与group-instance.xml的对应
# username/password
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal
# mq config
canal.mq.topic=t_dept


调整配置文件/usr/local/hadoop/app/canal/conf/t_canal/instance.properties如下:


#canal.instance.master.address=192.168.175.21:3306 #原来的
canal.instance.master1.address=192.168.175.21:3306 #新增,与group-instance.xml的对应
# username/password
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal
# mq config
canal.mq.topic=t_canal


2.3 group-instance.xml配置文件

配置文件/usr/local/hadoop/app/canal/conf/spring/group-instance.xml不需要做调整。


2.4 启动canal server

进入文件夹/usr/local/hadoop/app/canal/bin执行如下启动命令:


./startup.sh


查看日志/usr/local/hadoop/app/canal/logs/canal/canal.log,出现如下内容,即表示启动成功:


2019-06-07 21:15:03.372 [main] INFO  com.alibaba.otter.canal.deployer.CanalLauncher - ## load canal configurations
2019-06-07 21:15:03.427 [main] INFO  c.a.o.c.d.monitor.remote.RemoteConfigLoaderFactory - ## load local canal configurations
2019-06-07 21:15:03.529 [main] INFO  com.alibaba.otter.canal.deployer.CanalStater - ## start the canal server.
2019-06-07 21:15:06.251 [main] INFO  com.alibaba.otter.canal.deployer.CanalController - ## start the canal server[192.168.175.20:11111]
2019-06-07 21:15:22.245 [main] INFO  com.alibaba.otter.canal.deployer.CanalStater - ## the canal server is running now ......


2.5 使用canal client连接canal server


注意运行canal客户端代码时,一定要先启动canal server!!!

(1) 添加pom依赖


<!--canal-->
    <dependency>
      <groupId>com.alibaba.otter</groupId>
      <artifactId>canal.client</artifactId>
      <version>1.1.3</version>
    </dependency>


(2) canal client代码:


package com.xgh.canal;
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 java.net.InetSocketAddress;
import java.util.List;
public class CanalClientGroupTest1 {
    public static void main(String args[]) {
        //String zkHost="192.168.175.20:2181,192.168.175.21:2181,192.168.175.22:2181";
        // 创建链接
        //CanalConnector connector = CanalConnectors.newClusterConnector(zkHost,"example","","");
        CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("192.168.175.21", 11111),
                "t_dept", "", "");
        int batchSize = 1000;
        int emptyCount = 0;
        long batchId = 0;
        //外层死循环:在canal节点宕机后,抛出异常,等待zk对canal处理切换,切换完后,继续创建连接处理数据
        while(true) {
            try {
                connector.connect();
                connector.subscribe(".*\\..*");//订阅所有库下面的所有表
                //connector.subscribe("canal.t_canal");//订阅库canal库下的表t_canal
                connector.rollback();
                //内层死循环:按频率实时监听数据变化,一旦收到变化数据,立即做消费处理,并ack,考虑消费速度,可以做异步处理并ack.
                while (true) {
                    Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
                    batchId = message.getId();
                    int size = message.getEntries().size();
                    //// 偏移量不等于-1 或者 获取的数据条数不为0 时,认为拿到消息,并处理
                    if (batchId == -1 || size == 0) {
                        emptyCount++;
                        System.out.println("empty count : " + emptyCount);//此時代表當前數據庫無遍更數據
                        Thread.sleep(1000); //1000ms拉一次变动数据
                    } else {
                        emptyCount = 0;
                        System.out.printf("message[batchId=%s,size=%s] \n", batchId, size);
                        printEntry(message.getEntries());
                    }
                    connector.ack(batchId); // 提交确认
                    //
                }
            }catch(Exception e){
                e.printStackTrace();
                connector.rollback(batchId); // 处理失败, 回滚数据
            } finally {
                connector.disconnect();
            }
        }
    }
    private static void printEntry(List<Entry> entrys) {
        for (Entry entry : entrys) {
            if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN
                    || entry.getEntryType() == EntryType.TRANSACTIONEND) {
                continue;
            }
            RowChange rowChage = null;
            try {
                rowChage = RowChange.parseFrom(entry.getStoreValue());
            } catch (Exception e) {
                throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
                        e);
            }
            System.out.println("rowChare ======>"+rowChage.toString());
            EventType eventType = rowChage.getEventType(); //事件類型,比如insert,update,delete
            System.out.println(String.format("================> binlog[%s:%s] , name[%s,%s] , eventType : %s",
                    entry.getHeader().getLogfileName(),//mysql的my.cnf配置中的log-bin名稱
                    entry.getHeader().getLogfileOffset(), //偏移量
                    entry.getHeader().getSchemaName(),//庫名
                    entry.getHeader().getTableName(), //表名
                    eventType));//事件名
            for (RowData rowData : rowChage.getRowDatasList()) {
                if (eventType == EventType.DELETE) {
                    printColumn(rowData.getBeforeColumnsList());
                } else if (eventType == EventType.INSERT) {
                    printColumn(rowData.getAfterColumnsList());
                } else {
                    System.out.println("-------> before");
                    printColumn(rowData.getBeforeColumnsList());
                    System.out.println("-------> after");
                    printColumn(rowData.getAfterColumnsList());
                }
            }
        }
    }
    private static void printColumn(List<Column> columns) {
        for (Column column : columns) {
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
        }
    }
}


2.6 其他


将canal client代码CanalConnectors.newSingleConnector(new InetSocketAddress("192.168.175.21", 11111), "t_dept", "", "");中的队列名t_dept换成t_canal再执行,就可以监听t_canal对应数据变化了.


三 运行测试及总结



1. 监听t_dept的canal client可以接收到数据库A和B的数据变化
2. 监听t_canal的canal client只能接收到数据库B的数据变化
3. 数据过滤的设置问题


当在instance.properties和canal client中对设置filter时,canal client的设置会覆盖instance.properties中的配置。所以不如干脆保持instance.properties为默认状态,也即是不过滤,然后过滤全部设置在canal client中,如下:


connector.connect();
connector.subscribe(".*\\..*");//订阅所有库下面的所有表
//connector.subscribe("canal.t_canal");//订阅库canal库下的表t_canal


四 高可用架构设计



网络异常,图片无法展示
|


高可用架构设计


参考文章:
  1. https://www.cnblogs.com/yulu080808/p/8819260.html
  2. https://github.com/alibaba/canal
  3. https://blog.csdn.net/my201110lc/article/details/78836270
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
16天前
|
运维 Cloud Native 持续交付
深入理解云原生架构及其在现代企业中的应用
随着数字化转型的浪潮席卷全球,企业正面临着前所未有的挑战与机遇。云计算技术的迅猛发展,特别是云原生架构的兴起,正在重塑企业的IT基础设施和软件开发模式。本文将深入探讨云原生的核心概念、关键技术以及如何在企业中实施云原生策略,以实现更高效的资源利用和更快的市场响应速度。通过分析云原生架构的优势和面临的挑战,我们将揭示它如何助力企业在激烈的市场竞争中保持领先地位。
|
20天前
|
NoSQL Java 数据处理
基于Redis海量数据场景分布式ID架构实践
【11月更文挑战第30天】在现代分布式系统中,生成全局唯一的ID是一个常见且重要的需求。在微服务架构中,各个服务可能需要生成唯一标识符,如用户ID、订单ID等。传统的自增ID已经无法满足在集群环境下保持唯一性的要求,而分布式ID解决方案能够确保即使在多个实例间也能生成全局唯一的标识符。本文将深入探讨如何利用Redis实现分布式ID生成,并通过Java语言展示多个示例,同时分析每个实践方案的优缺点。
38 8
|
21天前
|
Cloud Native 安全 持续交付
深入理解微服务架构及其在现代软件开发中的应用
深入理解微服务架构及其在现代软件开发中的应用
40 3
|
21天前
|
运维 Kubernetes Docker
深入理解容器化技术及其在微服务架构中的应用
深入理解容器化技术及其在微服务架构中的应用
42 1
|
22天前
|
监控 持续交付 API
深入理解微服务架构及其在现代应用开发中的应用
深入理解微服务架构及其在现代应用开发中的应用
24 0
|
23天前
|
边缘计算 监控 自动驾驶
揭秘云计算中的边缘计算:架构、优势及应用场景
揭秘云计算中的边缘计算:架构、优势及应用场景
|
23天前
|
存储 监控 API
深入解析微服务架构及其在现代应用中的实践
深入解析微服务架构及其在现代应用中的实践
35 0
|
15天前
|
弹性计算 API 持续交付
后端服务架构的微服务化转型
本文旨在探讨后端服务从单体架构向微服务架构转型的过程,分析微服务架构的优势和面临的挑战。文章首先介绍单体架构的局限性,然后详细阐述微服务架构的核心概念及其在现代软件开发中的应用。通过对比两种架构,指出微服务化转型的必要性和实施策略。最后,讨论了微服务架构实施过程中可能遇到的问题及解决方案。
|
24天前
|
Cloud Native Devops 云计算
云计算的未来:云原生架构与微服务的革命####
【10月更文挑战第21天】 随着企业数字化转型的加速,云原生技术正迅速成为IT行业的新宠。本文深入探讨了云原生架构的核心理念、关键技术如容器化和微服务的优势,以及如何通过这些技术实现高效、灵活且可扩展的现代应用开发。我们将揭示云原生如何重塑软件开发流程,提升业务敏捷性,并探索其对企业IT架构的深远影响。 ####
39 3
|
1月前
|
Cloud Native 安全 数据安全/隐私保护
云原生架构下的微服务治理与挑战####
随着云计算技术的飞速发展,云原生架构以其高效、灵活、可扩展的特性成为现代企业IT架构的首选。本文聚焦于云原生环境下的微服务治理问题,探讨其在促进业务敏捷性的同时所面临的挑战及应对策略。通过分析微服务拆分、服务间通信、故障隔离与恢复等关键环节,本文旨在为读者提供一个关于如何在云原生环境中有效实施微服务治理的全面视角,助力企业在数字化转型的道路上稳健前行。 ####