1、redis (默认端口6379)
2、zookeeper (默认端口2181)
3、Kafka (默认端口9092)
4、Canal (默认端口 11111)
5、MySQL (默认端口 3306
本文Github代码地址:https://github.com/cheriduk/spring-boot-integration-template
Canal介绍:引用官方介绍
canal 是阿里巴巴 MySQL 数据库 Binlog 的增量订阅&消费组件。
名称:canal [kə'næl]
译意: 水道/管道/沟渠
语言: 纯java开发
定位: 基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了MySQL早期,阿里巴巴 B2B 公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于 trigger 的方式获取增量 变更,不过从 2010 年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&消费的业务,从此开 启了一段新纪元。ps. 目前内部使用的同步,已经支持 MySQL 8.x 和 Oracle 部分版本的日志解析
基于日志增量订阅&消费支持的业务:
数据库镜像
数据库实时备份
多级索引 (卖家和买家各自分库索引)
search build (elastic search)
业务cache刷新(redis)
价格变化等重要业务消息
Canal 工作原理:
原理相对比较简单:
canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议
mysql master收到dump请求,开始推送binary log给slave(也就是canal)
canal解析binary log对象(原始为byte流)
以上为官方介绍
canal 链接: https://pan.baidu.com/s/1HIT4b30BtXrkHym-w4peww 提取码: ar6c
实际项目我们是配置MQ模式,配合RocketMQ或者Kafka,canal会把数据发送到MQ的topic中,然后通过消息队列的消费者进行消费处理。
这篇文章演示部署Canal,配合使用Kafka,同步数据到Redis
通过架构图,我们很清晰就知道要用到的组件:MySQL、Canal、Kafka、ZooKeeper、Redis
MySQL搭建大家应该都会,ZooKeeper、Redis这些网上也有很多资料参考
主要说下Kafka搭建
首先在官网下载安装包:
解压,打开/config/server.properties配置文件,修改日志目录
首先启动ZooKeeper,我用的是3.4.13版本:
接着再启动Kafka,在Kafka的bin目录下打开cmd,输入命令:
kafka-server-start.bat ../../config/server.properties
我们可以通过ZooInspector看到ZooKeeper上注册了Kafka相关的配置信息:
然后需要创建一个队列,用于接收canal传送过来的数据,使用命令:
kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic canaltopic
创建的队列名是canaltopic
。
配置Cannal Server
canal官网下载相关安装包:
找到canal.deployer-1.1.4/conf目录下的canal.properties配置文件:
# tcp, kafka, RocketMQ 这里选择kafka模式
canal.serverMode = kafka
# 解析器的线程数,打开此配置,不打开则会出现阻塞或者不进行解析的情况
canal.instance.parser.parallelThreadSize = 16
# 配置MQ的服务地址,这里配置的是kafka对应的地址和端口
canal.mq.servers = 127.0.0.1:9092
# 配置instance,在conf目录下要有example同名的目录,可以配置多个
canal.destinations = example
然后配置instance,找到/conf/example/instance.properties配置文件:
## mysql serverId , v1.0.26+ will autoGen(自动生成,不需配置)
# canal.instance.mysql.slaveId=0
# position info
canal.instance.master.address=127.0.0.1:3306
# 在Mysql执行 SHOW MASTER STATUS;查看当前数据库的binlog
canal.instance.master.journal.name=mysql-bin.000006
canal.instance.master.position=4596
# 账号密码
canal.instance.dbUsername=canal
canal.instance.dbPassword=Canal@****
canal.instance.connectionCharset = UTF-8
#MQ队列名称
canal.mq.topic=canaltopic
#单队列模式的分区下标
canal.mq.partition=0
数据库配置,创建授权账户
canal的原理是模拟自己为mysql slave,所以这里一定需要做为mysql slave的相关权限
CREATE USER canal IDENTIFIED BY 'canal'; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%'; -- GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ; FLUSH PRIVILEGES;
针对已有的账户可通过grants查询权限:
show grants for 'canal'
配置完成后,就可以启动canal了。
测试验证
这时可以打开kafka的消费者窗口,测试一下kafka是否收到消息。
kafka-console-consumer.bat --bootstrap-server 127.0.0.1:9092 --from-beginning --topic canaltopic
控制台会出现乱码的话,需要临时设置一下编码
在cmd命令行执行前切换到UTF-8编码即可,使用命令行:chcp 65001
在MySQL数据库操作数据,然后观察Kafka这边变化情况;
返回的这一串字符对应的数据结构
官方有说明:
https://github.com/alibaba/canal/wiki/ClientAPI#%E6%95%B0%E6%8D%AE%E5%AF%B9%E8%B1%A1%E6%A0%BC%E5%BC%8F%E7%AE%80%E5%8D%95%E4%BB%8B%E7%BB%8Dentryprotocolproto
我使用的是最新的版本,官网文档给出的数据格式可能没及时更新,有些不同
环境都搭建好了以后,下面编写Redis客户端代码
首先引入Kafka和Redis的maven依赖:
org.springframework.kafka
spring-kafka
org.springframework.boot
spring-boot-starter-data-redis
com.alibaba
fastjson
1.2.28
compile
在application.yml文件配置:
spring:
redis:
host: 127.0.0.1
port: 6379
database: 0
password: 123456
编写操作Redis的工具类:
@Component
public class RedisClient {
/**
* 获取redis模版
*/
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 设置redis的key-value
*/
public void setString(String key, String value) {
setString(key, value, null);
}
/**
* 设置redis的key-value,带过期时间
*/
public void setString(String key, String value, Long timeOut) {
stringRedisTemplate.opsForValue().set(key, value);
if (timeOut != null) {
stringRedisTemplate.expire(key, timeOut, TimeUnit.SECONDS);
}
}
/**
* 获取redis中key对应的值
*/
public String getString(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
/**
* 删除redis中key对应的值
*/
public Boolean deleteKey(String key) {
return stringRedisTemplate.delete(key);
}
}
在application.yml配置文件加上kafka的配置信息:
spring:
kafka:
# Kafka服务地址
bootstrap-servers: 127.0.0.1:9092
consumer:
# 指定一个默认的组名
group-id: consumer-group1
#序列化反序列化
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
producer:
key-serializer: org.apache.kafka.common.serialization.StringDeserializer
value-serializer: org.apache.kafka.common.serialization.StringDeserializer
# 批量抓取
batch-size: 65536
# 缓存容量
buffer-memory: 524288
可以创建一个CanalBean对象进行接收
public class CanalBean {
//数据
private List data;
//数据库名称
private String database;
private long es;
//递增,从1开始
private int id;
//是否是DDL语句
private boolean isDdl;
//表结构的字段类型
private MysqlType mysqlType;
//UPDATE语句,旧数据
private String old;
//主键名称
private List pkNames;
//sql语句
private String sql;
private SqlType sqlType;
//表名
private String table;
private long ts;
//(新增)INSERT、(更新)UPDATE、(删除)DELETE、(删除表)ERASE等等
private String type;
//getter、setter方法
}
public class MysqlType {
private String id;
private String commodity_name;
private String commodity_price;
private String number;
private String description;
//getter、setter方法
}
public class SqlType {
private int id;
private int commodity_name;
private int commodity_price;
private int number;
private int description;
}
创建业务测试表对应的Bean用于测试使用
@Data // lombok插件依赖
public class Student implements Serializable {
private Long id;
private String name;
private Integer age;
private static final long serialVersionUID = 1L;
}
最后就可以创建一个消费者CanalConsumer进行消费
package com.gary.sync.consumer;
import com.alibaba.fastjson.JSONObject;
import com.gary.sync.model.CanalBean;
import com.gary.sync.model.Student;
import com.gary.sync.model.TbCommodityInfo;
import com.gary.sync.redis.RedisClient;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
@Component
public class CanalConsumer {
//日志记录
private static Logger log = LoggerFactory.getLogger(CanalConsumer.class);
//redis操作工具类
@Resource
private RedisClient redisClient;
//监听的队列名称为:canaltopic
@KafkaListener(topics = "canaltopic")
public void receive(ConsumerRecord, ?> consumer) {
String value = (String) consumer.value();
log.info("topic名称:{},key:{},分区位置:{},下标:{},value:{}", consumer.topic(), consumer.key(),consumer.partition(), consumer.offset(), value);
//转换为javaBean
CanalBean canalBean = JSONObject.parseObject(value, CanalBean.class);
//获取是否是DDL语句
boolean isDdl = canalBean.getIsDdl();
//获取类型
String type = canalBean.getType();
//不是DDL语句
if (!isDdl) {
List students = canalBean.getData();
//过期时间
long TIME_OUT = 600L;
if ("INSERT".equals(type)) {
//新增语句
for (Student student : students) {
Long id = student.getId();
//新增到redis中,过期时间是10分钟
redisClient.setString(String.valueOf(id), JSONObject.toJSONString(student), TIME_OUT);
}
} else if ("UPDATE".equals(type)) {
//更新语句
for (Student student : students) {
Long id = student.getId();
//更新到redis中,过期时间是10分钟
redisClient.setString(String.valueOf(id), JSONObject.toJSONString(student), TIME_OUT);
}
} else {
//删除语句
for (Student student : students) {
Long id = student.getId();
//从redis中删除
redisClient.deleteKey(String.valueOf(id));
}
}
}
}
}
依次启动-zookeeper-》kafka-》canal-》redis
zookeeper
kafka
canal
redis
先在MySQL创建表
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int NOT NULL,
`name` varchar(25) DEFAULT NULL,
`age` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
然后启动项目
接着新增一条数据:
INSERT INTO `test`.`student` (`id`, `name`, `age`)
VALUES
('777', '测试', '123') ;
student表查到新增的数据:
使用场景:
canal只能同步增量数据。
不是实时同步,是准实时同步。
增量同步,实现性不太强的场景