Canal监听Mysql回写Redis

目录

一、canal服务端

1.1 下载

1.2 解压

1.3 配置

1.4 启动

1.5 查看

二、canal客户端(Java编写业务程序)

2.1 SQL脚本

2.2 写POM

2.3 写Yaml

2.4 写业务类

2.4.1.项目结构

2.4.2 Utils.RedisUtil

2.4.3 biz.RedisCanalClientExample


一、canal服务端

1.1 下载

1.2 解压

tar -zxvf canal.deployer-1.1.6.tar.gz 到mycanal文件夹

1.3 配置

修改/mycan/conf/example/instance.properties文件

  • 换成自己的mysql主机master的IP地址

  • 换成自己的在mysql新建的canal账户

1.4 启动

注意这个地方需要JDK环境支持才能正常启动,那就补充一下安装JDK

  • Centos7安装JDK8

    • 在下载linux64版本的jdk

    • 解压后放到自己指定的文件夹

    • 配置环境变量:vim /ect/profile新增内容后在source /etc/profile 最后java -version 看是否安装成功

export JAVA_HOME=/myjava/jdk1.8.0_371
export JRE_HOME=$JAVA_HOME/jre
export CLASSPATH=$JRE_HOME/lib/ext:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin
  • 启动canal--->在canal的bin目录下执行 ./startup.sh

1.5 查看

  • 查看server日志 在目录mycanal/logs/canal/下执行cat canal.log
  • 查看样例example的日志 在目录mycanal/logs/example/下执行cat example.log

二、canal客户端(Java编写业务程序)

2.1 SQL脚本

CREATE TABLE `t_user`(
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `userName` VARCHAR(100) NOT NULL,
  PRIMARY KEY(`id`)
)ENGINE=INNODB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4

2.2 写POM

<dependencies>
  <dependency>
    <groupId>com.alibaba.otter</groupId>
    <artifactId>canal.client</artifactId>
    <version>1.1.0</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
  </dependency>
  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
  </dependency>
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.14</version>
  </dependency>
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
  </dependency>
  <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
	</dependency>
</dependencies>

2.3 写Yaml

server:
  port: 5555

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/bigdata?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 980918
    driver-class-name: com.mysql.jdbc.Driver
    druid:
      test-while-idle: false

2.4 写业务类

2.4.1.项目结构

2.4.2 Utils.RedisUtil

package com.atguigu.canal.utils;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @author zhumq
 * @date 2024/7/27 9:24
 */
public class RedisUtils
{
    public static final String  REDIS_IP_ADDR = "192.168.111.185";
    public static final String  REDIS_pwd = "111111";
    public static JedisPool jedisPool;

    static {
        JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(20);
        jedisPoolConfig.setMaxIdle(10);
        jedisPool=new JedisPool(jedisPoolConfig,REDIS_IP_ADDR,6379,10000,REDIS_pwd);
    }

    public static Jedis getJedis() throws Exception {
        if(null!=jedisPool){
            return jedisPool.getResource();
        }
        throw new Exception("Jedispool is not ok");
    }

}

2.4.3 biz.RedisCanalClientExample

package com.atguigu.canal.biz;

import com.alibaba.fastjson.JSONObject;
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.atguigu.canal.utils.RedisUtils;
import redis.clients.jedis.Jedis;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author zhumq
 * @date 2024/7/27 9:26
 */
public class RedisCanalClientExample
{
    /**
     * 表示60秒的常量。
     * 用于定义某些操作的时间间隔。
     */
    public static final Integer _60SECONDS = 60;
    
    /**
     * Redis服务器的IP地址。
     * 用于数据存储和检索操作中定位Redis服务器。
     */
    public static final String REDIS_IP_ADDR = "192.168.111.185";
    
    /**
     * 将数据插入Redis。
     *
     * @param columns 列数据列表,每项包含列名、值和更新标志。
     *                本方法首先将列数据列表转换为JSON对象,
     *                然后使用第一列的值作为键,在Redis中存储JSON字符串。
     *
     *                设计目的可能是以结构化方式在Redis中存储实体的相关属性信息,
     *                以便于快速检索和使用。
     */
    private static void redisInsert(List<Column> columns)
    {
        // 创建一个JSON对象来存储列数据
        JSONObject jsonObject = new JSONObject();
        
        // 遍历列数据列表,填充JSON对象
        for (Column column : columns)
        {
            // 打印列信息,用于调试或日志记录
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
            
            // 将列名和值添加到JSON对象中
            jsonObject.put(column.getName(), column.getValue());
        }
        
        // 如果列数据列表不为空,则执行Redis插入操作
        if(columns.size() > 0)
        {
            try(Jedis jedis = RedisUtils.getJedis())
            {
                // 使用第一列的值作为键,序列化的JSON对象作为值,存储到Redis中
                jedis.set(columns.get(0).getValue(), jsonObject.toJSONString());
            } catch (Exception e) {
                // 打印异常堆栈跟踪,用于错误处理或日志记录
                e.printStackTrace();
            }
        }
    }
    
    
    /**
     * 删除Redis中的键值对。
     *
     * 此方法通过接收一列列名和对应的值,构建一个JSON对象。然后,它从这个JSON对象中提取第一个列的值,
     * 并使用这个值作为Redis键来删除对应的条目。这个方法假设Redis已经连接,并且通过RedisUtils.getJedis()
     * 提供了Jedis实例。
     *
     * @param columns 列表,包含要删除的Redis键对应的列名和值。
     */
    private static void redisDelete(List<Column> columns)
    {
        // 构建一个JSON对象,用于存储列名和对应的值
        JSONObject jsonObject = new JSONObject();
        for (Column column : columns)
        {
            // 将每一列的名称和值添加到JSON对象中
            jsonObject.put(column.getName(),column.getValue());
        }
        // 当列表不为空时,尝试删除Redis中的条目
        if(columns.size() > 0)
        {
            try(Jedis jedis = RedisUtils.getJedis())
            {
                // 使用列表中第一个列的值作为键,删除Redis中的对应条目
                jedis.del(columns.get(0).getValue());
            }catch (Exception e){
                // 打印堆栈跟踪,以记录任何异常
                e.printStackTrace();
            }
        }
    }
    
    
    /**
     * 更新Redis中的数据。
     * 该方法接收一个列的列表,将这些列的名称和值存储到JSON对象中,然后将这个JSON对象存储到Redis中。
     * 此方法主要用于在数据更新后,将更新的列及其值同步到Redis缓存中,以保持数据的一致性。
     *
     * @param columns 列的列表,每个列包含一个名称、一个值和一个标志位表示该列是否被更新。
     */
    private static void redisUpdate(List<Column> columns)
    {
        // 创建一个JSON对象,用于存储列的名称和值。
        JSONObject jsonObject = new JSONObject();
        for (Column column : columns)
        {
            // 打印列的名称、值和更新状态,用于调试和日志记录。
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
            // 将列的名称和值添加到JSON对象中。
            jsonObject.put(column.getName(),column.getValue());
        }
        // 检查列表是否为空,如果不为空,则更新Redis。
        if(columns.size() > 0)
        {
            try(Jedis jedis = RedisUtils.getJedis())
            {
                // 使用列列表中第一个列的值作为键,将JSON对象序列化为字符串后存储到Redis中。
                jedis.set(columns.get(0).getValue(),jsonObject.toJSONString());
                // 打印更新后的Redis数据,用于调试和确认更新是否成功。
                System.out.println("---------update after: "+jedis.get(columns.get(0).getValue()));
            }catch (Exception e){
                // 捕获并打印任何异常,确保方法在异常情况下不会中断执行。
                e.printStackTrace();
            }
        }
    }

    
    /**
     * 打印日志条目中的变更信息。
     * 此方法忽略事务开始和结束的日志条目,因为它只对实际的数据变更感兴趣。
     * 它解析每条日志条目中的RowChange数据,并根据变更类型(插入、删除、更新)执行相应的操作。
     *
     * @param entrys 日志条目的列表,这些条目包含数据库变更的信息。
     */
    public static void printEntry(List<Entry> entrys) {
        // 遍历每个日志条目
        for (Entry entry : entrys) {
            // 跳过事务开始和结束的日志条目
            if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
                continue;
            }
            
            RowChange rowChage = null;
            try {
                // 从日志条目的存储值中解析RowChange对象
                // 获取变更的row数据
                rowChage = RowChange.parseFrom(entry.getStoreValue());
            } catch (Exception e) {
                // 如果解析失败,抛出运行时异常,并包含错误详情
                throw new RuntimeException("ERROR ## parser of eromanga-event has an error,data:" + entry.toString(), e);
            }
            // 获取事件类型 获取变动类型
            EventType eventType = rowChage.getEventType();
            // 打印日志条目的基本信息,包括日志文件名、偏移量、模式名、表名和事件类型
            System.out.println(String.format("================&gt; binlog[%s:%s] , name[%s,%s] , eventType : %s",
                    entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
                    entry.getHeader().getSchemaName(), entry.getHeader().getTableName(), eventType));
            
            // 遍历RowData列表,根据事件类型执行相应的操作(插入、删除、更新)
            for (RowData rowData : rowChage.getRowDatasList()) {
                if (eventType == EventType.INSERT) {
                    // 对于插入事件,调用redisInsert方法处理后的列数据
                    redisInsert(rowData.getAfterColumnsList());
                } else if (eventType == EventType.DELETE) {
                    // 对于删除事件,调用redisDelete方法处理前的列数据
                    redisDelete(rowData.getBeforeColumnsList());
                } else {// EventType.UPDATE
                    // 对于更新事件,调用redisUpdate方法处理后的列数据
                    redisUpdate(rowData.getAfterColumnsList());
                }
            }
        }
    }
    
    
    /**
     * 程序入口主方法,用于初始化并连接Canal服务器,以监听MySQL数据库的变化。
     * @param args 命令行参数
     */
    public static void main(String[] args)
    {
        // 初始化时的提示信息
        System.out.println("---------O(∩_∩)O哈哈~ initCanal() main方法-----------");
        
        // 创建Canal客户端连接器,用于连接和通信
        //=================================
        // 创建链接canal服务端
        CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(REDIS_IP_ADDR, 11111),
                "example",
                "",
                "");
        
        // 定义每次获取的记录数量
        int batchSize = 1000;
        // 定义空闲循环计数器,用于判断是否需要重新连接
        // 空闲空转计数器
        int emptyCount = 0;
        
        // 连接初始化完成的提示信息
        System.out.println("---------------------canal init OK,开始监听mysql变化------");
        try {
            // 连接Canal服务器
            connector.connect();
            // 订阅指定的数据库表变更事件
            //connector.subscribe(".*\\..*");
            connector.subscribe("bigdata.t_user");
            // 回滚事务,确保数据一致性
            connector.rollback();
            // 定义空闲循环的总次数
            int totalEmptyCount = 10 * _60SECONDS;
            while (emptyCount < totalEmptyCount) {
                // 每秒打印一次监控信息
                System.out.println("我是canal,每秒一次正在监听:"+ UUID.randomUUID().toString());
                // 获取一批变更记录
                Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
                // 获取批次ID
                long batchId = message.getId();
                // 获取记录数量
                int size = message.getEntries().size();
                // 如果批次ID为-1或记录数量为0,表示没有数据变更
                if (batchId == -1 || size == 0) {
                    // 空闲计数器加1
                    emptyCount++;
                    // 每秒检查一次
                    try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
                } else {
                    // 有数据变更,重置空闲计数器
                    //计数器重新置零
                    emptyCount = 0;
                    // 打印变更记录
                    printEntry(message.getEntries());
                }
                // 确认处理完成,提交批次
                connector.ack(batchId); // 提交确认
                // connector.rollback(batchId); // 处理失败, 回滚数据
            }
            // 监听结束的提示信息
            System.out.println("已经监听了"+totalEmptyCount+"秒,无任何消息,请重启重试......");
        } finally {
            // 断开与Canal服务器的连接
            connector.disconnect();
        }
    }
}

题外话:

  • Java程序下connector.subscribe配置的过滤正则

关闭资源简写

  • try-with-resources释放资源

        

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 要将MySQL数据同步到Redis,您可以使用一个名为“Canal”的工具。Canal是阿里巴巴开源的一款数据变更捕获和同步工具,可以监控MySQL数据库中的数据变化,并将其同步到Redis或其他数据存储中。 以下是将MySQL数据同步到Redis的一般步骤: 1. 安装和配置Canal,可以参考Canal官方文档。 2. 配置Canal实例,包括MySQLRedis的连接信息、数据过滤规则等。 3. 启动Canal实例,开始监控MySQL数据库的数据变化。 4. 编写处理Canal数据的程序,将数据从Canal获取并同步到Redis。 5. 运行处理程序,开始将MySQL数据同步到Redis。 需要注意的是,在将MySQL数据同步到Redis时,需要考虑数据格式的转换、数据冲突处理等问题,以确保数据的正确性和一致性。 ### 回答2: 在现代的web应用中,通常会使用关系型数据库MySQL来存储数据,而使用Redis来作为缓存数据库,加快应用的响应速度。同时,为了保证数据一致性,需要将MySQL中的数据同步到Redis中。这里就介绍下如何使用Canal来实现MySQLRedis的数据同步。 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
发出的红包

打赏作者

伏颜.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值