最近在学习如何使用canal来同步mysql数据库,因此记录一下学习使用的流程
canal将自己伪装成mysql的slave读取mysql的Binary log实现对mysql数据变动的同步。
在mysql主库日志发生变化后,直接通过canal对redis操作同步mysql的数据,与业务sql数据解耦。
版本8.0.16
my.ini设置
[mysqld]
# 设置3306端口
port=3306
# 设置mysql的安装目录
basedir=D:\Environment\mysql-8.0.16-winx64
# 设置mysql数据库的数据的存放目录
datadir=D:\Environment\mysql-8.0.16-winx64\data
# 允许最大连接数
max_connections=200
# 允许连接失败的次数。
max_connect_errors=10
# 服务端使用的字符集默认为UTF8
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
# 默认使用“mysql_native_password”插件认证
#mysql_native_password
default_authentication_plugin=mysql_native_password
[mysql]
# 设置mysql客户端默认字符集
default-character-set=utf8
[client]
# 设置mysql客户端连接服务端时默认使用的端口
port=3306
default-character-set=utf8
# 开启mysql的binlog模块
log-bin=mysql-bin
binlog-format=ROW
# server_id需保证唯一,不能和canal的slaveId重复
server_id=1
# 需要同步的数据库名称
binlog-do-db=redis_test
# 忽略的数据库,建议填写
binlog-ignore-db=mysql
# 启动mysql时不启动grant-tables授权表
skip-grant-tables
用户授权
CREATE USER canal IDENTIFIED BY 'canal';
grant ALL PRIVILEGES on *.* to 'canal'@'%';
FLUSH PRIVILEGES;
版本6.0
#bind 127.0.0.1
protected-mode no
port 6379
版本1.1.5
/conf/example/instance.properties
#################################################
## mysql serverId , v1.0.26+ will autoGen
#不要和mysql主库的server_id相同
canal.instance.mysql.slaveId=2
# enable gtid use true/false
canal.instance.gtidon=false
# position info
#使用了本地的mysql服务器
canal.instance.master.address=127.0.0.1:3306
canal.instance.master.journal.name=
canal.instance.master.position=
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.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb
#canal.instance.tsdb.dbUsername=canal
#canal.instance.tsdb.dbPassword=canal
#canal.instance.standby.address =
#canal.instance.standby.journal.name =
#canal.instance.standby.position =
#canal.instance.standby.timestamp =
#canal.instance.standby.gtid=
# username/password
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,.*\\..*
#canal.mq.dynamicTopicPartitionNum=test.*:4,mycanal:6
#################################################
启动位置
/bin/startup.sh
关闭位置
/bin/stop.sh
canal启动后
canal监视mysql日志文件
获得sql语句具体内容并拆解得到rowChage
EventType eventType = rowChage.getEventType();
//数据库名 entry.getHeader().getSchemaName()
//表名 entry.getHeader().getTableName()
//操作名 eventType
//删除操作的数据存放在 rowData.getBeforeColumnsList() 这个列表中
//插入操作的数据存放在 rowData.getAfterColumnsList() 这个列表中
//更新操作则先删除后插入
//每个操作以键值对存储在列表中
//如果有3个字段:id,name,password
//第一个key是id,第二个key是name,第三个key是password
//此列表存储了具体的数据
List<RowData> rowData = rowChage.getRowDatasList();
//通过每次sql操作的eventType判断增删改的类型对rowData中的数据进行个性化处理
//这里使用自定义的redistemplate模板操作数据来讲数据库更改更新至缓存中
//也可以将此处操作替换为自己的,如连接至MQ处理
for (RowData rowData : rowChage.getRowDatasList()) {
if (eventType == EventType.DELETE) {
redisDelete(rowData.getBeforeColumnsList());
} else if (eventType == EventType.INSERT) {
redisInsert(rowData.getAfterColumnsList());
} else {
redisUpdate(rowData.getAfterColumnsList());
}
}
获得主键字段,删除key为对应id的缓存
private void redisDelete(List<Column> columns){
for(Column column:columns){
if (column.getName().equals("id")){
redisTemplate.delete("Id::"+column.getValue());
System.out.println("执行了redisDelete,id="+column.getValue());
}
}
}
获得主键字段,set key为主键,value为对象缓存
private void redisInsert(List<Column> columns){
User user = new User();
int insertId=0;
for (Column column : columns) {
switch (column.getName()){
case "id":
try {
insertId=Integer.valueOf(column.getValue()).intValue();
user.setId(insertId);
} catch (NumberFormatException e) {
e.printStackTrace();
}
break;
case "name":
user.setName(column.getValue());
break;
case "password":
user.setPassword(column.getValue());
break;
default:
System.out.println("redisInsert,switch结束");
}
}
redisTemplate.opsForValue().set("Id::"+insertId,user);
System.out.println("执行了redisInsert,id="+insertId);
}
获得主键字段,删除key对应的缓存,set key为主键,value为对象缓存/
覆盖该key的value
private void redisUpdate(List<Column> columns){
User user = new User();
int updateId=0;
for (Column column : columns) {
switch (column.getName()){
case "id":
try {
updateId=Integer.valueOf(column.getValue()).intValue();
user.setId(updateId);
} catch (NumberFormatException e) {
e.printStackTrace();
}
break;
case "name":
user.setName(column.getValue());
break;
case "password":
user.setPassword(column.getValue());
break;
default:
System.out.println("redisUpdatet,switch结束");
}
}
redisTemplate.opsForValue().set("Id::"+updateId,user);
System.out.println("执行了redisUpdate,id="+updateId);
}
user表,自增主键id(int),name(varchar),password(varchar)
lombok注解的int id,String name,String password
增删查改
增删查改
增删查改
RedisConfig
自定义的RedisTemplate
使用jackson和StringRedisSerializer序列化Object和String
SpringCacheRedisConfig
解决cache缓存到redis后的乱码问题
注入simpleCanalClientExample
bok注解的int id,String name,String password
增删查改
增删查改
增删查改
RedisConfig
自定义的RedisTemplate
使用jackson和StringRedisSerializer序列化Object和String
SpringCacheRedisConfig
解决cache缓存到redis后的乱码问题
注入simpleCanalClientExample
作为监视器线程启动