SpringBoot整合Canal1.1.6并同步数据到Redis(超详细和很多踩坑点)

一、使用背景

最近公司新接了一个医院项目,主要是对接医院内营养处方数据,院内his部署在内网环境,因此仅提供一个外网数据库给我们(他们自己应该做了类似主从的方式将我们需要的数据同步一份到外网数据库),又因为落地的数据我们需要立马进行处理并在公众号推送给病人进行付款等一系列其他操作,所以我们需要实时监听数据库的变化。以下是当时能想到的方案:

  • 方案一:轮询数据库
    第一个想到的也第一个被pass。原因也不必多说

  • 方案二:数据库触发器
    数据库触发器公司内部的小伙伴使用的都极其的少,确实是我们自己学艺不精,而且维护起来不方便,对后面来的开发小伙伴也不友好,最终也被pass

  • 方案三:消息队列
    这应该是在没有提供外网数据库的时候想出的方案,也是最符合业务场景的方案之一,但由于甲方不想增加开发成本、业务代码被入侵以及使用第三方插件增加系统风险和不稳定性等一系列原因,这个方案也被pass

  • 方案四:Canal
    讨论很久以后才决定使用canal,主要原因第一个是我们需要实时监听院内处方的数据变化,第二个是推过来的处方数据我们需要展示给用户,每个人只能看到自己的处方数据,并且处方有时效性,利用redis的过期时间也能完美代替数据库条件查询。

二、什么是 Canal?

这里简单介绍,具体就不从官网贴图了,啥都没有官网说的清楚,点击进入Canal官网
1、了解什么是Canal之前我们应该首先了解以下MySql的主从复制原理

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

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

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

    上述中涉及到两个重要的MySql事务日志 binlog和relaylog,简单介绍一下两种日志

BinLog

即binary log,二进制日志文件,也叫作变更日志(update log)。它记录了数据库所有执行的
DDL 和 DML 等数据库更新事件的语句,但是不包含没有修改任何数据的语句(如数据查询语句select、 show等)。
说白了就是MySql记录了除了查询语句之外的几乎任何增删改操作。MySql8以后默认开启。开启会损失约百分之一的性能。

RelayLog

中继日志只在主从服务器架构的从服务器上存在。从服务器为了与主服务器保持一致,要从主服务器读
取二进制日志(binary log)的内容,并且把读取到的信息写入 本地的日志文件 中,这个从服务器本地的日志文件就叫
中继日志 。然后,从服务器读取中继日志,并根据中继日志的内容对从服务器的数据进行更新,完成主
从服务器的 数据同步 。

2、了解了主从复制的原理以后,就把Canal当成一个Slave,原理也就清晰了

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

三、准备工作

1、准备MySql 8.x

1)查看数据库版本

SELECT VERSION(); -- 查看数据库版本

2)查看BinLog日志是否开启

SHOW VARIABLES LIKE 'log_bin%';
-- 如果log_bin的value为on则已开启,显示off则未开启。

3)如果未开启binlog

  • 找到服务器上my.cnf,增加如下配置
[mysqld]  
log-bin=mysql-bin #添加这一行就ok  
binlog-format=ROW #选择row模式  
server_id=1 #配置mysql replaction需要定义,不能和canal的slaveId重复  

4)为canal新建账号

CREATE USER canal IDENTIFIED BY 'canal';  
-- 授权部分操作
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
-- 也可以使用下面的命令授权所有操作
GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;
-- 需要刷新权限生效
FLUSH PRIVILEGES;

5)踩坑点

------------注意踩坑点------------

注意点一: log_bin 为只读变量,不能通过命令set global log_bin = on;临时生效。
在这里插入图片描述
注意点二: 这是我刚开始学习Canal踩的坑,也是自己对binlog不够了解犯的错误。也是很多博主没有提到过的问题,所以自己照葫芦画瓢就容易出错。执行命令如下:

SHOW BINARY LOGS;

在这里插入图片描述
针对于已经运行的或者重启过很多次的MySql可以看到很多binlog日志文件(也有可能是几十几百个),这和binlog的配置有很大关系,同学们自己感兴趣的了解一下。因此canal在配置的时候要配置监听的文件名称,很多博主照搬照抄都设置为binlog.000001。导致后面监听不到数据便变化。所以后面设置的时候需要注意文件名称为当前MySql正在写入的binlog,如何确定具体是哪个呢?执行如下命令:

SHOW MASTER STATUS;

在这里插入图片描述

正对于一些不重要的数据库可以直接使用下面命令删除所有binlog日志文件(慎用!!!)

RESET MASTER;

在这里插入图片描述
这样后面canal的配置文件名称就可以用binlog.000001,否则只能用上述的binlog.000054

2、准备Canal

下载地址:

1)Canal1.1.4下载

在这里插入图片描述

2)解压并配置Canal

解压:tar -zxvf canal.deployer-1.1.4.tar.gz
解压后如图四个文件夹
在这里插入图片描述
进入conf/example下
在这里插入图片描述
修改instance.properties,主要配置参考如下

#################################################
#需要配置的地方都进行了中文描述,其他都为默认信息
canal.instance.gtidon=false

# 这里是数据库地址信息,数据库账号密码在下面
canal.instance.master.address=124.111.11.111:3306
#这里就是上面强调的binlog日志文件名称即mysql主库链接时起始的binlog文件
canal.instance.master.journal.name=binlog.000001
#mysql主库链接时起始的binlog偏移量
canal.instance.master.position=157
#mysql主库链接时起始的binlog的时间戳
canal.instance.master.timestamp=
canal.instance.master.gtid=

# rds oss binlog
canal.instance.rds.accesskey=
canal.instance.rds.secretkey=
canal.instance.rds.instanceId=

# table meta tsdb info
canal.instance.tsdb.enable=false
#这里为数据库账号
canal.instance.dbUsername=canal
#这里为数据库密码
canal.instance.dbPassword=canal
canal.instance.connectionCharset = UTF-8
# enable druid Decrypt database password
canal.instance.enableDruid=false
#canal.instance.pwdPublicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALK4BUxdDltRRE5/zXpVEVPUgunvscYFtEip3pmLlhrWpacX7y7GCMo2/JM6LeHmiiNdH1FWgGCpUfircSwlWKUCAwEAAQ==

# table regex
canal.instance.filter.regex=.*\\..*
# table black regex
canal.instance.filter.black.regex=mysql\\.slave_.*
# table field filter(format: schema1.tableName1:field1/field2,schema2.tableName2:field1/field2)
#canal.instance.filter.field=test1.t_product:id/subject/keywords,test2.t_company:id/name/contact/ch
# table field black filter(format: schema1.tableName1:field1/field2,schema2.tableName2:field1/field2)
#canal.instance.filter.black.field=test1.t_product:subject/product_image,test2.t_company:id/name/contact/ch

# mq config
canal.mq.topic=example
# dynamic topic route by schema or table regex
#canal.mq.dynamicTopic=mytest1.user,mytest2\\..*,.*\\..*
canal.mq.partition=0
# hash partition config
#canal.mq.partitionsNum=3
#canal.mq.partitionHash=test.table:id^name,.*\\..*
#################################################

3)启动Canal

进入canal1.1.4/bin可以看以下脚本。
在这里插入图片描述
直接命令启动:sh startup.sh;
因为canal-server本质上也是jar包在运行,直接通过jps命令查看运行状态
在这里插入图片描述
发现已经初步启动
接下来进入/canal1.1.4/logs/example/查看日志观察是否报错tail -f example.log查看日志信息

在这里插入图片描述
如此便算是完全启动成功。

4)踩坑点

------------注意踩坑点------------
注意点一:linux启动完成后,会在bin目录下生成canal.pid,stop.sh会读取canal.pid进行进程关闭。如果直接通过kill命令可能会导致canal不能完全关闭,这样下次执行./startup.sh命令可能会启动失败。

3、准备项目测试

1、构建SpringBoot项目,引入坐标

	<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.</groupId>
    <artifactId>springcloud-alibaba</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>



    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.otter</groupId>
            <artifactId>canal.client</artifactId>
            <version>1.1.4</version>
        </dependency>

    </dependencies>

</project>

2、新建监听类


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 org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.net.InetSocketAddress;
import java.util.List;

/**
 * @author zqf
 * @desc
 */

@Component
public class CanalListener {


    /**
     * 解析数据
     *
     * @param beforeColumns 修改、删除后的数据
     * @param afterColumns  新增、修改、删除前的数据
     * @param dbName        数据库名字
     * @param tableName     表大的名字
     * @param eventType     操作类型(INSERT,UPDATE,DELETE)
     * @param timestamp     消耗时间
     */
    private static void dataDetails(List<CanalEntry.Column> beforeColumns, List<CanalEntry.Column> afterColumns, String dbName, String tableName, CanalEntry.EventType eventType, long timestamp) {

        System.out.println("数据库:" + dbName);
        System.out.println("表名:" + tableName);
        System.out.println("操作类型:" + eventType);
        if (CanalEntry.EventType.INSERT.equals(eventType)) {
            System.out.println("这是一条新增的数据");
        } else if (CanalEntry.EventType.DELETE.equals(eventType)) {
            System.out.println("删除数据:" + afterColumns);
        } else {
            System.out.println("更新数据:更新前数据--" + afterColumns);
            System.out.println("更新数据:更新后数据--" + beforeColumns);

        }
        System.out.println("操作时间:" + timestamp);
    }

    @PostConstruct
    public void run() throws Exception {
        CanalConnector conn = CanalConnectors.newSingleConnector(new InetSocketAddress("124.111.11.111", 11111), "example", null, null);
        while (true) {
            conn.connect();
            conn.subscribe(".*\\..*");
            // 回滚到未进行ack的地方
            conn.rollback();
            // 获取数据 每次获取一百条改变数据
            Message message = conn.getWithoutAck(100);
            //获取这条消息的id
            long id = message.getId();
            int size = message.getEntries().size();
            if (id != -1 && size > 0) {
                // 数据解析
                analysis(message.getEntries());
            } else {
                //暂停1秒防止重复链接数据库
                Thread.sleep(1000);
            }
            // 确认消费完成这条消息
            conn.ack(message.getId());
            // 关闭连接
            conn.disconnect();
        }
    }

    /**
     * 数据解析
     */
    private void analysis(List<CanalEntry.Entry> entries) {
        for (CanalEntry.Entry entry : entries) {
            // 解析binlog
            CanalEntry.RowChange rowChange = null;
            try {
                rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
            } catch (Exception e) {
                throw new RuntimeException("解析出现异常 data:" + entry.toString(), e);
            }
            if (rowChange != null) {
                // 获取操作类型
                CanalEntry.EventType eventType = rowChange.getEventType();
                // 获取当前操作所属的数据库
                String dbName = entry.getHeader().getSchemaName();
                // 获取当前操作所属的表
                String tableName = entry.getHeader().getTableName();
                // 事务提交时间
                long timestamp = entry.getHeader().getExecuteTime();
                for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
                    dataDetails(rowData.getBeforeColumnsList(), rowData.getAfterColumnsList(), dbName, tableName, eventType, timestamp);

                }
            }
        }
    }

}
3、新建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author zqf
 * @desc
 */
@SpringBootApplication
public class CanalApplication {

    public static void main(String[] args) {
        SpringApplication.run(CanalApplication.class, args);
    }
}

如此即可,启动项目,并随意在监听的数据库增删改,查看控制台信息输出

数据库:linyi
表名:tb_answer
操作类型:UPDATE
更新数据:更新前数据--[index: 0
sqlType: -5
name: "answer_no"
isKey: true
updated: false
isNull: false
value: "1638010372783263744"
mysqlType: "bigint"
, index: 1
sqlType: 12
name: "questionnaire_name"
isKey: false
updated: true
isNull: false
value: "\351\227\256\345\215\267\350\260\203\346\237\245\350\241\2501"
mysqlType: "varchar(255)"
, index: 2
sqlType: -5
name: "questionnaire_no"
isKey: false
updated: false
isNull: false
value: "1637737551628750848"
mysqlType: "bigint"
, index: 3
sqlType: -5
name: "goods_no"
isKey: false
updated: false
isNull: false
value: "1"
mysqlType: "bigint"
, index: 4
sqlType: -5
name: "patient_no"
isKey: false
updated: false
isNull: false
value: "1635614014201823232"
mysqlType: "bigint"
, index: 5
sqlType: 12
name: "answer_url"
isKey: false
updated: false
isNull: false
value: "questionnaire_answer/2023-03-21/1638010372783263745.jpg"
mysqlType: "varchar(255)"
, index: 6
sqlType: 4
name: "assess_score"
isKey: false
updated: false
isNull: false
value: "2"
mysqlType: "int"
, index: 7
sqlType: 12
name: "assess_result"
isKey: false
updated: false
isNull: true
mysqlType: "varchar(500)"
, index: 8
sqlType: 4
name: "status"
isKey: false
updated: false
isNull: false
value: "0"
mysqlType: "int"
, index: 9
sqlType: 93
name: "create_time"
isKey: false
updated: false
isNull: false
value: "2023-03-21 10:51:20"
mysqlType: "datetime"
, index: 10
sqlType: 93
name: "update_time"
isKey: false
updated: false
isNull: false
value: "2023-03-21 10:51:20"
mysqlType: "datetime"
, index: 11
sqlType: 4
name: "is_del"
isKey: false
updated: false
isNull: false
value: "0"
mysqlType: "int"
]
更新数据:更新后数据--[index: 0
sqlType: -5
name: "answer_no"
isKey: true
updated: false
isNull: false
value: "1638010372783263744"
mysqlType: "bigint"
, index: 1
sqlType: 12
name: "questionnaire_name"
isKey: false
updated: false
isNull: false
value: "\351\227\256\345\215\267\350\260\203\346\237\245\350\241\250"
mysqlType: "varchar(255)"
, index: 2
sqlType: -5
name: "questionnaire_no"
isKey: false
updated: false
isNull: false
value: "1637737551628750848"
mysqlType: "bigint"
, index: 3
sqlType: -5
name: "goods_no"
isKey: false
updated: false
isNull: false
value: "1"
mysqlType: "bigint"
, index: 4
sqlType: -5
name: "patient_no"
isKey: false
updated: false
isNull: false
value: "1635614014201823232"
mysqlType: "bigint"
, index: 5
sqlType: 12
name: "answer_url"
isKey: false
updated: false
isNull: false
value: "questionnaire_answer/2023-03-21/1638010372783263745.jpg"
mysqlType: "varchar(255)"
, index: 6
sqlType: 4
name: "assess_score"
isKey: false
updated: false
isNull: false
value: "2"
mysqlType: "int"
, index: 7
sqlType: 12
name: "assess_result"
isKey: false
updated: false
isNull: true
mysqlType: "varchar(500)"
, index: 8
sqlType: 4
name: "status"
isKey: false
updated: false
isNull: false
value: "0"
mysqlType: "int"
, index: 9
sqlType: 93
name: "create_time"
isKey: false
updated: false
isNull: false
value: "2023-03-21 10:51:20"
mysqlType: "datetime"
, index: 10
sqlType: 93
name: "update_time"
isKey: false
updated: false
isNull: false
value: "2023-03-21 10:51:20"
mysqlType: "datetime"
, index: 11
sqlType: 4
name: "is_del"
isKey: false
updated: false
isNull: false
value: "0"
mysqlType: "int"
]
操作时间:1681747515000

至此,Canal整合完成

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要将MySQL数据同步Redis,您可以使用一个名为“Canal”的工具。Canal是阿里巴巴开源的一款数据变更捕获和同步工具,可以监控MySQL数据库中的数据变化,并将其同步Redis或其他数据存储中。 以下是将MySQL数据同步Redis的一般步骤: 1. 安装和配置Canal,可以参考Canal官方文档。 2. 配置Canal实例,包括MySQL和Redis的连接信息、数据过滤规则等。 3. 启动Canal实例,开始监控MySQL数据库数据变化。 4. 编写处理Canal数据的程序,将数据从Canal获取并同步Redis。 5. 运行处理程序,开始将MySQL数据同步Redis。 需要注意的是,在将MySQL数据同步Redis时,需要考虑数据格式的转换、数据冲突处理等问题,以确保数据的正确性和一致性。 ### 回答2: 在现代的web应用中,通常会使用关系型数据库MySQL来存储数据,而使用Redis来作为缓存数据库,加快应用的响应速度。同时,为了保证数据一致性,需要将MySQL中的数据同步Redis中。这里就介绍下如何使用Canal来实现MySQL到Redis数据同步。 Canal是阿里巴巴开源的一款基于Java开发的数据库同步工具,可以实时监控MySQL数据库的变化,并将变化同步到指定的目的地,如Redis等。以下是步骤: 1、下载Canal 可以从Canal的github仓库中找到最新的release版本,下载解压后,即可使用。同时,需要准备一个MySQL服务器和一个Redis服务器。 2、配置Canal 在Canal的安装目录中,可以找到一个conf文件夹,其中存放着各种配置文件。在这里,我们需要修改instance.properties文件,其中涉及到的参数包括: - canal.instance.master.address:MySQL服务器的地址。 - canal.instance.master.journal.name:MySQL服务器的binlog名称。 - canal.instance.master.position:上次同步到MySQL的位置。 - canal.instance.rdb.url:Redis服务器的地址。 - canal.instance.rdb.password:Redis服务器的密码。 3、启动Canal 在安装目录中的bin文件夹中,可以找到canal.sh/canal.bat等启动脚本文件,启动Canal服务。 4、创建同步任务 在Canal的管理界面中,可以创建同步任务,并指定目标同步位置、过滤规则等参数。 5、同步MySQL数据Redis 启动Canal服务后,即可实时监控MySQL的变化,并将数据同步Redis中。可以在Redis中通过命令行或者客户端工具查看已同步数据。 以上是使用Canal同步MySQL数据Redis的基本步骤,需要注意的是,Canal的数据同步可以控制灵活,支持多种过滤规则,可以根据具体需求进行设置。同时,受限于Redis的并发处理能力,当数据量较大时,需要注意Redis设置参数的调整,以充分利用其性能优势。 ### 回答3: Canal是一款用于MySQL数据库日志增量订阅&消费的工具,可以将MySQL数据库的变更事件以Kafka消息队列的形式发布出来,并提供了多种客户端的消费途径。Redis则是一款高性能的非关系型数据库,通常被用作缓存和存储数据结构。 将MySQL数据同步Redis,是一项非常实用的任务。通过使用Canal,我们可以订阅MySQL数据库的变化,并将数据以Kafka消息的形式呈现出来。接着,可以通过编写定制化的程序,按需消费Kafka消息,并将消息中的数据存储到Redis中。 以下是同步MySQL数据Redis的基本步骤: 1. 启动Canal和Kafka。首先,需要安装Canal和Kafka,并启动两者。启动Canal后,需要创建一个Canal实例来订阅MySQL数据库的变更事件。在这里,我们可以指定订阅特定的数据库、表和事件类型,以便更好地控制数据流。 2. 编写消费者程序。在Canal和Kafka都已启动的情况下,我们需要编写一个Kafka消费者程序,用于消费Canal发送的消息。在消费者程序中,我们可以通过解析Kafka消息体来获取变化的数据,然后将其转换为Redis中的数据格式,并存储到Redis中。 3. 数据格式转换。根据不同的业务需求,我们需要将MySQL中的数据转换成Redis支持的数据格式。例如,在存储关系型数据时,我们可以使用Hash来存储key-value键值对;在存储非关系型数据时,我们可以使用Sorted Set等数据结构。 4. 容错与性能优化。在实际应用场景中,我们还需要考虑各种异常情况的处理。例如,当Redis出现宕机或网络中断等故障时,需要自动进行重试或者将数据存储到其他存储介质中。此外,还需要对程序进行性能优化,以便提高系统的吞吐量和性能表现。 总的来说,通过Canal同步MySQL数据Redis具有很大的实用价值,可以极大地提高系统的实时性和响应性。但是,该过程涉及比较复杂的流程和技术,需要综合考虑多种因素,才能实现高效可靠的数据同步

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值