阿里开源中间件Canal入门学习笔记(二)

前言

在业务系统中,数据不光要保存到关系型数据库中,部分业务数据还要同步保存到Redis、ES、HBASE中。

像秒杀的业务,我们会将数据的库存缓存到Redis中。这个时候,我们对数据库中订单的增加或者减少,都需要同步响应的库存数据到Redis中。这种数据同步的代码跟业务的代码耦合度太高会不太优雅,此时我们可以把这些同步数据的操作,单独出来形成一个独立的模块。

大致的流程图如下:
在这里插入图片描述

一、canal 控制台的安装及配置

上一篇笔记中,介绍到canal.deployer + springboot 项目启动日志监听的操作。这次我们用过使用canal控制台去做一些数据库的配置以及rocketmq的配置。

1.1下载安装canal.admin

canal官网站下载canal.admin

我还是之前下载好的版本canal.admin-1.1.5.tar.gz

1.2 修改配置

下载好后,完成解压缩。我们最终是要通过一个web页面去配置canal server ,会很方便,还可以配置选择集群的搭建。

打开安装路径{path}\canal-admin\conf\application.yml文件并修改。

server:
  port: 8089
spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

spring.datasource:
  address: 127.0.0.1:3306
  database: canal_manager
  username: root
  password: 12345678
  driver-class-name: com.mysql.jdbc.Driver
  url: jdbc:mysql://${spring.datasource.address}/${spring.datasource.database}?useUnicode=true&characterEncoding=UTF-8&useSSL=false
  hikari:
    maximum-pool-size: 30
    minimum-idle: 1

canal:
  adminUser: admin
  adminPasswd: admin

其中 database 为什么要配置为 canal_manager ,下面要说。

1.3 初始化canal元数据

因为我们要通过canal的web页面去配置,所以,需要初始化管理页面的元数据。

元数据存放的位置:
{path}\canal-admin\conf\canal_manager.sql

有2种方式初始化元数据。

  1. 直接MySQL数据库中,执行元数据的脚本,会默认创建database:canal_manager的数据库(建议使用拥有root权限的用户去操作,本地搭建的肯定是拥有root权限的用户)
  2. win10通过命令行,连接MySQL(>source conf/canal_manager.sql
1.4 启动canal控制台

初始化元数据完成后,我们通过{path}\canal-admin\bin\startup.bat启动。
启动成功后,浏览器访问:

http://ip:8090/

会跳转到登录页面 通过admin/123456进行登录,成功的页面如下所示:
在这里插入图片描述

1.5 启动canal.deployer

进入到{path}\canal\conf\
这次我们先不修改canal.properties,而是通过修改canal_local.properties的配置去进行覆盖,修改如下:

# register ip
canal.register.ip =

# canal admin config
canal.admin.manager = 127.0.0.1:8089
canal.admin.port = 11110
canal.admin.user = admin
canal.admin.passwd = 4ACFE3202A5FF5CF467898FC58AAB1D615029441
# admin auto register
canal.admin.register.auto = true
canal.admin.register.cluster =
canal.admin.register.name =

配置好后,我们需要启动canal server,这个时候我们需要注意下,因为现在是使用的canal_local.properties的配置。所以,我们需要修改下启动文件bin\startup.bat
需要替换的位置如下:
在这里插入图片描述
这个步骤操作完,就可以启动了。

启动成功后,我们在canal.admin web 控制台刷新server管理,就可以看到canal server已经启动成功。
到这步,我们的canal.server搭建就已经成功了。

二、canal.admin控制台相关配置

2.1 配置instance管理

在这里插入图片描述

  1. 填写instance 名称所属集群/主机
  2. 选择载入模板
  3. 修改默认信息

在这里插入图片描述

然后我们将之前配置好的{安装路径}\canal\conf\example\instance.properties的文件,对照修改下。
然后保存即可。

2.2配置RocketMq

我们编辑修改刚才创建好的Instance实例。修改配置下面这个部分,这个地方的配置,可以配置监听数据库全部表作为mq topic的名称,或者直接指定一个固定的表,作为MQtopic。

具体详细介绍,请移步官方传送门
在这里插入图片描述

我这里是配置了指定了其中的一个表。

到这里,canal指定mq的配置完成,保存退出。

2.3 canal监听MySQL操作发送RocketMq消息

我们来测试下,是否有效。我对已经存在的表做了个insert的操作,如下:
在这里插入图片描述

此时,我们看下RocketMq的控制台,查看Topic是否自动创建了我们自定义配置的topic。
在这里插入图片描述
如图所示,topic已经创建完成。此时,我们在看下,canal给rocketMq发送的消息,是不是我们上面操作的Insert的操作。
在这里插入图片描述
没有问题,已经发送了消息,看看是不是我们操作的那个。
在这里插入图片描述
具体内容:

{
	"data": [{
		"RECORD_ID": "1000000004",
		"TYPE_CODE": "T_DICT",
		"PARAM_CODE": "STATUS",
		"TYPE_CODE_NAME": "系统基础字典值类型",
		"PARAM_CODE_NAME": "是否有效",
		"STATUS": "VALID",
		"CREATE_BY": "1",
		"CREATE_DATE": "2021-07-08 20:11:44",
		"UPDATE_BY": null,
		"UPDATE_DATE": null
	}],
	"database": "mrsoftrock",
	"es": 1625746304000,
	"id": 3,
	"isDdl": false,
	"mysqlType": {
		"RECORD_ID": "bigint",
		"TYPE_CODE": "varchar(50)",
		"PARAM_CODE": "varchar(50)",
		"TYPE_CODE_NAME": "varchar(50)",
		"PARAM_CODE_NAME": "varchar(1000)",
		"STATUS": "varchar(10)",
		"CREATE_BY": "bigint",
		"CREATE_DATE": "timestamp",
		"UPDATE_BY": "bigint",
		"UPDATE_DATE": "timestamp"
	},
	"old": null,
	"pkNames": ["RECORD_ID"],
	"sql": "",
	"sqlType": {
		"RECORD_ID": -5,
		"TYPE_CODE": 12,
		"PARAM_CODE": 12,
		"TYPE_CODE_NAME": 12,
		"PARAM_CODE_NAME": 12,
		"STATUS": 12,
		"CREATE_BY": -5,
		"CREATE_DATE": 93,
		"UPDATE_BY": -5,
		"UPDATE_DATE": 93
	},
	"table": "dict_desc",
	"ts": 1625746304528,
	"type": "INSERT"
}

也没问题,到这里,说明我们canal已经成功的监听到了MySQL的binlog的变化,并将操作直接投递发送到RocketMq中。
那么,剩下的操作,就是我们程序中监听MQ的消息,拿到message body数据,并操作。

三、代码操作

mq接收消息对象:

package com.nacos.test.controller.mq.bean;

import lombok.*;
import lombok.experimental.FieldDefaults;

import java.util.List;

/**
 * @author Mr.SoftRock
 * @Date 2021/7/8 20:40
 **/
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
@AllArgsConstructor
@NoArgsConstructor
public class CanalBean {
    /**
     * 数据
     */
    List<DictDesc> data;
    /**
     * 数据库名称
     */
    String database;
    long es;
    /**
     * 递增,从1开始
     */
    int id;
    /**
     * 是否是DDL语句
     */
    boolean isDdl;
    /**
     * 表结构的字段类型
     */
    MysqlType mysqlType;
    /**
     * UPDATE语句,旧数据
     */
    List<DictDesc> old;
    /**
     * 主键名称
     */
    List<String> pkNames;
    /**
     * sql语句
     */
    String sql;

    SqlType sqlType;
    /**
     * 表名
     */
    String table;

    long ts;
    /**
     * (新增)INSERT、(更新)UPDATE、(删除)DELETE、(删除表)ERASE等等
     */
    String type;

}

package com.nacos.test.controller.mq.bean;

import lombok.*;
import lombok.experimental.FieldDefaults;

import java.util.Date;

/**
 * @author Mr.SoftRock
 * @Date 2021/7/8 20:45
 **/
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
@AllArgsConstructor
@NoArgsConstructor
public class DictDesc {
    Long recordId;
    String typeCode;
    String paramCode;
    String tpeCodeName;
    String paramCodeName;
    String status;
    Long createBy;
    Date createDate;
    Long updateBy;
    Date updateDate;

}
package com.nacos.test.controller.mq.bean;

import lombok.*;
import lombok.experimental.FieldDefaults;

/**
 * @author Mr.SoftRock
 * @Date 2021/7/8 20:44
 **/
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
@AllArgsConstructor
@NoArgsConstructor
public class MysqlType {

    int id;
    int commodity_name;
    int commodity_price;
    int number;
    int description;
}
package com.nacos.test.controller.mq.bean;

import lombok.*;
import lombok.experimental.FieldDefaults;

/**
 * @author Mr.SoftRock
 * @Date 2021/7/8 20:43
 **/
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
@AllArgsConstructor
@NoArgsConstructor
public class SqlType {
     int id;
     int commodity_name;
     int commodity_price;
     int number;
     int description;
}


监听接收消息类:

package com.nacos.test.controller.mq;

import com.alibaba.fastjson.JSONObject;
import com.nacos.test.controller.mq.bean.CanalBean;
import com.nacos.test.controller.mq.bean.DictDesc;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.apache.rocketmq.spring.core.RocketMQPushConsumerLifecycleListener;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @author Mr.SoftRock
 * @Date 2021/07/08 20:33
 **/
@Service
@Slf4j
@RocketMQMessageListener(consumerGroup = "canal-group",
        topic = "mrsoftrock_dict_desc",
        selectorExpression = "*",
        consumeMode = ConsumeMode.ORDERLY,
        consumeThreadMax = 1

)
public class CanalListener implements RocketMQListener<MessageExt>, RocketMQPushConsumerLifecycleListener {
    @Override
    public void onMessage(MessageExt message) {
        log.info("---consume canal message begin");
        try {
            log.info(new String(message.getBody(), StandardCharsets.UTF_8));
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("---consume canal message end");
    }

    @Override
    public void prepareStart(DefaultMQPushConsumer consumer) {
        try {
            consumer.subscribe("mrsoftrock_dict_desc", "*");
        } catch (Exception e) {
            e.printStackTrace();
        }
        consumer.registerMessageListener((MessageListenerOrderly) (msgs, context) -> {
            log.info("消费消息-mrsoftrock_dict_desc--consume order message begin");
            try {
                String message = new String(msgs.get(0).getBody(), StandardCharsets.UTF_8);
                log.info("消费消息==>msgId:{},message:{}", msgs.get(0).getMsgId(), message);
                CanalBean canalBean = JSONObject.parseObject(message, CanalBean.class);
                String table = canalBean.getTable();
                System.out.println(table.toString());
                String type = canalBean.getType();
                System.out.println(type);
                List<DictDesc> data = canalBean.getData();
                data.stream().forEach(tbTest -> {
                    log.info("获取到的数据库操作数据:---》" + JSONObject.toJSONString(tbTest));
                    if ("UPDATE".equals(type) && "dict_desc".equals(table)) {
                        //删除缓存
                        log.info("执行redis的操作---UPDATE");

                    } else if ("INSERT".equals(type) && "dict_desc".equals(table)) {

                        //添加缓存
                        log.info("执行redis的操作---INSERT");
                    }
                });

            } catch (Exception e) {
                e.printStackTrace();
            }
            log.info("消费消息-mrsoftrock_dict_desc--consume order message end");

            return ConsumeOrderlyStatus.SUCCESS;
        });
    }
}

我们启动下我们的springboot项目,然后看控制台的输出:
在这里插入图片描述

Redis的操作,我就不展开说了,随便写个set,get操作,验证下即可。

canal对MySQL的监听,然后发送到mq的过程中,会不会有顺序性的问题出现。

那么官方也分了几种不同的情况去处理,官方对rocketMq接受canal顺序消息的处理。也可直接看下图:

在这里插入图片描述

第一句很明显,binlog本身是有序的,那么就剩下控制,canal发送消息到mq之后,如何保证顺序了。

四、结尾

至此,对阿里开源中间件canal的初步学习就完成了。不得不说,阿里的开源产品,做的确实可圈可点。

但是没有任何一项技术是完美无缺的,适合自己的才是最好的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值