写在前面的话:
ES在7之后不推荐transport的方式进行连接,统一使用rest的风格连接,如果使用前者要代码层面的要求特别高,把es项目和服务项目都要调整,这里用rest风格目前是流行的只需要在hosts后面加上http://,核心配置的东西不多,使用transport报各种各样的错就直接用rest模式,配置文件的书写使用idea减少没必要的错误,其次存在kafka和rabbit的配置,这些配置可能不能去掉否则空指针异常,这种情况避免他们使用到同一个端口号。
z1 开启mysql的binlog功能
2 下载canal.adapte和canal.deployer工具包
3 配置canal.deployer文件
在下载工具包的时候一定注意es版本与当前包的版本关系,不是越新越好,否则会出现各种报错
配置canal.deployer对mysql的binlog监听,在deployer-1.1.7\conf\example下的instance.properties文件配置要监听的mysq服务器,这个文件配置并不复杂,只需要配置账号密码,数据库名,以及要监听的数据库表,可以去全部可以是指定数据库文件,这里为了方面,监听所有,省的来回修改,里面最重要的点是mysql的binlog日志。
启动canal.deployer-1.1.7\bin下的startup.bat查看log文件夹下的日志,避免报错,影响接下来的配置
配置canal.adapte内容在conf下的application.yml中配置数据采集服务规则,
srcDataSources:
defaultDS:
url: jdbc:mysql://127.0.0.1:3308/canal?useUnicode=true
username: root
password: 1234
canalAdapters:
- instance: example # canal instance Name or mq topic name
groups:
- groupId: g1
outerAdapters:
- name: es7
keys: db1Key
hosts: 127.0.0.1:9300 # 127.0.0.1:9200 for rest mode
properties:
mode: transport # or rest
# security.auth: test:123456 # only used for rest mode
cluster.name: elasticsearch
这是服务的提供数据抓取的核心配置,在es7中配置一个自己的规则canal.yml
dataSourceKey: defaultDS #指定数据源,这个值和adapter的application.yml文件中配置的srcDataSources值对应。
destination: example #指定canal-server中配置的某个实例的名字,注意:我们可能配置多个实例,你要清楚的知道每个实例收集的是那些数据,不要瞎搞。
groupId: g1 #组ID,对应application.yml中的canalAdapters/groups/groupId中的值
outerAdapterKey: db1Key #对应application.yml中的canalAdapters/groups/outerAdapters/name/key中的值
esMapping: #ES的mapping(映射)
_index: canal #要同步到的ES的索引名称(自定义),需要自己在ES上创建哦!
#_type: _doc #ES索引的类型名称(自定义)
_id: _id #ES标示文档的唯一标示,通常对应数据表中的主键ID字段,注意我这里写成的是"_id",有个下划线哦!
#pk: id #如果不需要_id, 则需要指定一个属性为主键属性
sql: "select t.id as _id, t.account,t.password,t.age, t.email from user t" #这里就是数据表中的每个字段到ES索引中叫什么名字的sql映射,注意映射到es中的每个字段都要是唯一的,不能重复。
#etlCondition: "where t.occur_time>='{0}'"
commitBatch: 3000
版本不一致会报错
exception caught on transport layer [NettyTcpChannel{localAddress=/127.0.0.1:9300, remoteAddress=/127.0.0.1:65412}], closing connection
然后创建ES索引,做好字段关系映射
PUT /canal
{
"mappings": {
"properties":{
"id":{
"type":"long"
},
"account":
{
"type":"text",
"analyzer": "ik_smart"
},
"password":{
"type":"text",
"index": false
},
"age":{
"type":"long"
},
"email":{
"type":"text"
}
}
}
}
这些弄完后发现会一直报错
java.lang.ClassCastException: com.alibaba.druid.pool.DruidDataSource cannot be cast to com.alibaba.druid.pool.DruidDataSource
解决方式,编译源代码并替换,下载地址
https://github.com/alibaba/canal/archive/refs/tags/canal-1.1.5.tar.gz编译方式,单独在局部编译容易报错,手动定位到定位到 client-adapter.escore 模块的 pom.xml 的 druid 更新为
com.alibaba
druid
provided
选择在根目录下运行
mvn clean package成功后把canal-canal-1.1.5/client-adapter/es7x/target 下 将打包好的 client-adapter.es7x-1.1.5-jar-with-dependencies.jar 替换掉 canal-adapter/plugin 下原来的依赖,最终会解决。
还有一种解决方案下载v1.1.5-alpha-2的adapter,然后复制这个版本的client-adapter.es7x-1.1.5-SNAPSHOT-jar-with-dependencies.jar 放入1.1.5的plugins目录中,这是一种比较快捷的方式,如果你了解源码思路可以采取局部编译,担是容易形成依赖下不下来问题。
Canal-deployer是对接mysql的服务,伪装成从节点发送dump请求,让master发送binlog给服务端,canal-deployer负责采集数据增量同步到其他设备如ES,MQ,Redis等,除此之外应用层可以对对接这里个服务,获取增量信息,springBoot中测试如下,导入依赖,创建连接客户端
com.alibaba.otter
canal.protocol
1.1.5
com.alibaba.otter
canal.client
1.1.5
客户端代码
package canal.es;
import java.net.InetSocketAddress;
import java.util.List;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.CanalEntry.Column;
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
import com.alibaba.otter.canal.protocol.CanalEntry.RowData;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component
public class CanalClient implements InitializingBean {
private final static int BATCH_SIZE = 1000;
@Override
public void afterPropertiesSet() throws Exception {
// 创建链接
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("127.0.0.1", 11111), "example", "", "");
try {
//打开连接
connector.connect();
//订阅数据库表,全部表
connector.subscribe(".*\\..*");
//回滚到未进行ack的地方,下次fetch的时候,可以从最后一个没有ack的地方开始拿
connector.rollback();
while (true) {
// 获取指定数量的数据
Message message = connector.getWithoutAck(BATCH_SIZE);
//获取批量ID
long batchId = message.getId();
//获取批量的数量
int size = message.getEntries().size();
//如果没有数据
if (batchId == -1 || size == 0) {
try {
//线程休眠2秒
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//如果有数据,处理数据
printEntry(message.getEntries());
}
//进行 batch id 的确认。确认之后,小于等于此 batchId 的 Message 都会被确认。
connector.ack(batchId);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
connector.disconnect();
}
}
/**
* 打印canal server解析binlog获得的实体类信息
*/
private static void printEntry(List entrys) {
for (Entry entry : entrys) {
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
//开启/关闭事务的实体类型,跳过
continue;
}
//RowChange对象,包含了一行数据变化的所有特征
//比如isDdl 是否是ddl变更操作 sql 具体的ddl sql beforeColumns afterColumns 变更前后的数据字段等等
RowChange rowChage;
try {
rowChage = RowChange.parseFrom(entry.getStoreValue());
} catch (Exception e) {
throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(), e);
}
//获取操作类型:insert/update/delete类型
EventType eventType = rowChage.getEventType();
//打印Header信息
System.out.println(String.format("================》; binlog[%s:%s] , name[%s,%s] , eventType : %s",
entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
entry.getHeader().getSchemaName(), entry.getHeader().getTableName(),
eventType));
//判断是否是DDL语句
if (rowChage.getIsDdl()) {
System.out.println("================》;isDdl: true,sql:" + rowChage.getSql());
}
//获取RowChange对象里的每一行数据,打印出来
for (RowData rowData : rowChage.getRowDatasList()) {
//如果是删除语句
if (eventType == EventType.DELETE) {
printColumn(rowData.getBeforeColumnsList());
//如果是新增语句
} else if (eventType == EventType.INSERT) {
printColumn(rowData.getAfterColumnsList());
//如果是更新的语句
} else {
//变更前的数据
System.out.println("------->; before");
printColumn(rowData.getBeforeColumnsList());
//变更后的数据
System.out.println("------->; after");
printColumn(rowData.getAfterColumnsList());
}
}
}
}
private static void printColumn(List columns) {
for (Column column : columns) {
System.out.println(column.getName() + " : " + column.getValue() + " update=" + column.getUpdated());
}
}
}
启动SpringBoot项目运行结果
分别对应了删除和修改操作,binlog通过日志事务版本号来标记记录数据,当物理上的数据丢失就能通过这个保存的快照恢复到之前的数据,可以有以下操作,开启binlog日志,创建数据库,创建一张表,插如几条数据,查看binlog,生成事务事件id,把表删除,使用mysql自带的mysqlbinlog mysql-bin.000001或者
show binlog events [in 'log_name'] [FROM pos] [limit [offset,] row_count]
show binlog events in 'mysql-bin.000001' from 124 limit 0 5;
使用 mysqlbinlog 命令,查找从备份时间到误操作时间之间的 binlog 日志文件。命令格式如下:
mysqlbinlog --start-datetime="2023-06-01 10:00:00" --stop-datetime="2023-06-02 10:00:00" /var/lib/mysql/binlog.000001 > mysqlbinlog.txt
其中,--start-datetime 表示起始时间,--stop-datetime 表示结束时间,/var/lib/mysql/binlog.000001 是 binlog 日志文件的路径,> mysqlbinlog.txt 表示将输出结果保存到 mysqlbinlog.txt 文件中。
mysqlbinlog.txt
文件,查找误删除数据的操作。mysqlbinlog --no-defaults mysqlbinlog.txt | mysql -u username -p dbname
其中,--no-defaults 表示不使用默认配置文件,mysqlbinlog.txt 是保存误删除操作的 binlog 日志文件,username 是 MySQL 用户名,dbname 是要还原的数据库名称。