Canal框架
概念:
canal是用java开发的基于数据库增量日志解析,提供增量数据订阅&消费的中间件。目前,canal主要支持了MySQL的binlog解析,解析完成后才利用canal client 用来处理获得的相关数据。
工作结构模式图:

MySQL主从复制过程

#重点 -- $ Canal主要支持MySQL的Binlog解析,解析完成后才利用Canal Client 来处理获得的相关数据。 (数据库同步需要阿里的Otter中间件,基于Canal)
MySQL的Binlog(二进制日志)
1、概念

2、Binlog的分类
#分类 1、statement -----> 语句级 2、row ---------->行级 3、mixed --------->statement的升级版 binlog_format=statement|mixed|row
#区别
1、statement: 语句级
--binlog会记录每次执行‘写(增删改)’的操作,相对于row模式节省空间,但是可能会产生不一致性
如:'update xx set create_date=now()'
-- 此时用binlog进行恢复,由于执行时间不同可能产生的数据就不同
优点: 节省空间。
缺点: 有可能造成数据不一致
---------------------------------------------------------------------------------------
2、row : 行级
--binlog会记录每次操作后每行记录的变化。
优点: 保持数据的绝对一致性,无论sql是什么,引用了什么函数,只记录执行后的效果。
缺点: 占用较大空间。
---------------------------------------------------------------------------------------
3、mixed : statement的升级版
--一定程度上解决了由于一些特殊情况使得statement模式导致的数据不一致问题。默认还是'statement'。
在某些情况下: 当函数中包含UUID()时: 包含 AUTO_INCREMENT(自动增长)字段的表被更新时;执行'INSERT DELAYED' 语句时,用UDF时,会按照 ROW 的方式进行处理。
优点: 节省空间,同时兼顾了一定的一致性。
缺点: 些许极个别情况依然会造成不一致,另外 statement 和 mixed 对于需要对 binlog做监控的情况下,都比较不方便。
$ 综上: ROW 对于 Canal 做监控分析比较合适。
Canal的工作原理
1、MySQL主从复制过程
1) Master 主库将改变记录,写入二进制日志(Binary Log)中;
2) Slave 从库向 MySQL 发送dump协议,将 Master主库的 binary log events 进行拷贝到
它的中继日志(relay log)
3) Slave 从库读取并重做中继日志中的事件,将改变的数据同步到自己的数据库。
2、Canal的工作原理 (ROW级别)
-- $ 将自身伪装成 Slave , 假装从 Master 复制数据。
-- $ Otter是阿里用于进行异地数据库之间的同步框架,Canal是其中一部分。
使用场景
1) 原始场景: 阿里Otter中间件的一部分。
Otter是阿里用于进行异地数据库之间的同步框架,Canal是其中一部分。

2)常见场景1: 更新缓存

3)常见场景2: 抓取业务表的新增变化数据,用于制作实时统计。
EX
linux下开启binlog (my.cnf)
windows下开启mysql的binlog是my.ini文件
1、创建数据表
#测试用表 CREATE TABLE user_info( id VARCHAR(255), NAME VARCHAR(255), sex VARCHAR(255) );
2、修改配置文件开启binlog日志
$ cd /etc $ vi my.cnf ----------------------------------------------------------- #基本配置 max_allowed_packet=20M server-id=1 log-bin=mysql-bin binlog_format=row binlog-do-db=数据库名称 ------------------------------------------------------------ [mysqld] ## 设置server_id,一般设置为IP,注意要唯一,确保主从不相同 server_id=1 ## 复制过滤:也就是指定哪个数据库不用同步 binlog-ignore-db=mysql binlog-ignore-db=information_schema binlog-ignore-db=performance_schema binlog-ignore-db=test ##设置同步数据库(如:esqsui) binlog-do-db=(同步数据库名称) ## 开启二进制日志功能,可以随便取,最好有含义(关键就是这里了) log-bin=mysql-bin-1 ##MySQL 磁盘写入策略以及数据安全性 ##每次事务提交时MySQL都会把log buffer的数据写入log file,并且flush(刷到磁盘)中去 innodb_flush_log_at_trx_commit=1 ## 为每个session 分配的内存,在事务过程中用来存储二进制日志的缓存 binlog_cache_size=1M ## 主从复制的格式(mixed,statement,row,默认格式是statement) ## SBR(基于sql语句复制),RBR(基于行的复制),MBR(混合模式复制) binlog_format=mixed ## 二进制日志自动删除/过期的天数。默认值为0,表示不自动删除。单位:天 expire_logs_days=7 ## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。 ## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致 slave_skip_errors=1062 ## 设置binlog每个日志文件大小 max_binlog_size=20M ##当sync_binlog =N (N>0) ,MySQL 在每写 N次 二进制日志binary log时,会使用fdatasync()函数将它的写二进制日志binary log同步到磁盘中去, ##sync_binlog 的默认值是0,像操作系统刷其他文件的机制一样,MySQL不会同步到磁盘中去而是依赖操作系统来刷新binary log。 sync_binlog=1

3、重启mysql服务使配置生效
root用户权限下 $ systemctl restart mysqld ------ 查看配置文件是否生效 $ cd /var/lib/mysql

#插入一条数据后

4、赋权限
在mysql中执行 ( 给到canal读的权限 )
mysql>set global validate_password_length=4; mysql>set global validate_password_policy=0; mysql> GRANT SELECT,REPLICATION SLAVE,REPLICATION CLIENT ON *.* TO 'canal'@'%' IDENTIFIED BY 'canal'; #命令 $ mysql> use mysql $ mysql>select * from user;
5、Canal的下载和安装
1、下载并解压jar包
地址:Releases · alibaba/canal · GitHub
#linux下解压canal包 $ tar -zxvf canal.deployer-1.1.5.tar.gz -C /opt/module/canal $ cd ../module/canal $ vi canal.properties

去掉注释java才可以运行

#下图 可以创建多个Example

B、instance.properties(canal实例属性文件配置)
#下图 -- 命令: $ cd /opt/module/canal/conf/example $ ls $ vi instance.properties 第一个打开,它的Id不能与mysql-serverId一样(从节点id) 第二个配置mysql的安装地址 本机就填127.0.0.1

#下图 用户名和密码在上面myql中给权限的时候可以指定,要一致。

Canal数据结构
A、Message:(sql集合)
一次canal从日志中抓取的信息,一个message可以包含多个sql执行的结果。 Entry:一个entry对应一个sql命令,一个sql可能会对多行记录造成影响。 一个message中包含的是entry集合(多个entry)

B、
。。。。
EX:
1、创建工程
#导入依赖
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client</artifactId>
<version>1.1.4</version> //不要导入最新版本
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.4.1</version>
</dependency>
2、创建测试案例
package com.xqh.DSJ;
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.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import java.net.InetSocketAddress;
import java.util.List;
public class canalClient {
public static void main(String[] args) {
//获得连接
CanalConnector canalConnector = CanalConnectors.newSingleConnector(
new InetSocketAddress("192.168.80.130",11111),"example","",
"");
while (true){
//连接
canalConnector.connect();
//订阅数据库
canalConnector.subscribe("canal11.*");
//获取数据
Message message = canalConnector.get(100);
//获取entry集合
List<CanalEntry.Entry> entries = message.getEntries();
//判断集合是否为空,若为空则等待一会
if(entries.size()<=0){
System.out.println("当次抓取无数据,等待一会。。。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//遍历entries,单条解析
for (CanalEntry.Entry entry : entries) {
//1、获取表名
String tableName = entry.getHeader().getTableName();
//2、获取类型
CanalEntry.EntryType entryType = entry.getEntryType();
//3、获取序列化后的数据
ByteString storeValue = entry.getStoreValue();
//4、判断当前entry类型是否为ROWDATA(行数据类型)
if(CanalEntry.EntryType.ROWDATA.equals(entryType)){
//5、反序列化数据本身
try {
CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(storeValue);
//6、获取当前事件的操作类型
CanalEntry.EventType eventType = rowChange.getEventType();
//7、获取数据集
List<CanalEntry.RowData> rowDataList = rowChange.getRowDatasList();
//8、遍历ROWdatalist,打印数据集
for (CanalEntry.RowData rowData : rowDataList) {
JSONObject beforeData = new JSONObject(); //之前数据
List<CanalEntry.Column> beforeColumnsList = rowData.getBeforeColumnsList();
for (CanalEntry.Column column : beforeColumnsList) {
beforeData.put(column.getName(),column.getValue());
}
System.out.println("------------------------------------------------------------------");
JSONObject afterData = new JSONObject();//之后数据
List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();
for (CanalEntry.Column column : afterColumnsList) {
afterData.put(column.getName(),column.getValue());
}
//打印内容 此时数据可以往kafka / redis / MQ 里面去写入
System.out.println("Table"+tableName+",EventType"+eventType+",之前数据:"+beforeData
+",之后数据:"+afterData
);
}
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}else {
System.out.println("当前数据类型为:"+entryType);
}
}
}
}
}
}
3、创库 创表
create table KL( id int name varchar score int );
#要点总结: 1、首先mysql的binlog需要开启 2、linux下配置相应的文件信息如: canal.properties / instance.properties等 3、开启canal服务 bin目录下./startup.sh 4、数据库连接 5、依赖导入 ,工程中导入对应依赖 。。。
#开启canal bin目录下 $ ./startup.sh
navicat连接linux下的mysql数据库:
#命令 $ mysql>GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'linux下mysql的密码' WITH GRANT OPTION; $ mysql>select * from mysql.user where user='root'\G; #看到下图Host: % 即可远程连接 $ mysql>exit; $ firewall-cmd --zone=public --add-port=3306/tcp --permanent; 若出现:FirewallD is not running此错误; $ systemctl start firewalld; 开启防火墙 $ systemctl status firewalld; 再执行一遍: 注册防火墙端口号: $ firewall-cmd --zone=public --add-port=(端口号)/tcp --permanent; $ firewall-cmd --list-ports;---->查看开发端口;

navicat中输入linux的 ip地址及密码。即可连接。
2、Kafka案例内容
kafka下载地址: Index of /dist/kafka
#kafka模式开始 1、vim canal.properties(canal/conf目录下) 2、把canal.serverMode = tcp 改为 --> canal.serverMode = kafka 3、 ./stop.sh --> canal的 bin 目录下。停止canal 4、由于kafka里面是有主题 topic的,所以需要指定消息发送到哪一个主题 5、cd example目录下 6、vim instance.properties 7、bin/startup.sh 开启canal服务
修改cananl.serverMode = kafka

修改kafka集群配置


vim example下的instance.properties

若有多个分区

指定数据库的表的主键或者是指定某一列进行hash

linux启动脚本
zookeeper启动脚本

kafka启动脚本

#命令 进入kafka的bin目录 执行 $ kafka-console-consumer.sh --bootstrap-server 主机:端口号 --topic 指定的主题名称(上图instance.properties文件)
做数据同步可以学一下 DataX (如有需要)
#插入'INSERT DELAYED' 研究 --关注: $ MySQL的这个特性,是MySQL对标准SQL的一个扩展,从MySQL 3.22.15 引入,5.6已经不推荐使用,5.7已经不支持了(虽然能识别,但是已经被忽略掉,而且会生成ER_WARN_LEGACY_SYNTAX_CONVERTED警告),在后续的版本中会废弃掉。
$ insert delayed使用限制: ------------------------------------------------------ INSERT DELAYED works only with MyISAM, MEMORY, ARCHIVE, and BLACKHOLE tables. For engines that do not supportDELAYED, an error occurs. insert delayed 只适用于MyISAM、MEMORY、ARCHIVE、BLACKHOLE引擎的表,对于不支持的引擎会报错。 An error occurs for INSERT DELAYED if used with a table that has been locked with LOCK TABLES because the insert must be handled by a separate thread, not by the session that holds the lock. ----------------------------------------------------------------------------------------- insert delayed必须由单独的线程完成,如果线程已经持有lock tables锁,则insert delayed会报错。 For MyISAM tables, if there are no free blocks in the middle of the data file, concurrent SELECT and INSERTstatements are supported. Under these circumstances, you very seldom need to use INSERT DELAYED with MyISAM. 对于MyISAM表,如果在数据文件中没有空闲块,支持并发的select和insert。在这种情况下,你会很少需要使用insert delayed。 INSERT DELAYED should be used only for INSERT statements that specify value lists. The server ignores DELAYED forINSERT ... SELECT or INSERT ... ON DUPLICATE KEY UPDATE statements. ----------------------------------------------------------------------------------------- insert delayed 只适用于指定具体值的insert,对于insert...select...或insert...on duplicate update不支持。 Because the INSERT DELAYED statement returns immediately, before the rows are inserted, you cannot useLAST_INSERT_ID() to get the AUTO_INCREMENT value that the statement might generate. insert delayed执行完会立马返回结果。但是insert delayed是缓存在内存中,并没有真正的执行,不能使用LAST_INSERT_ID()来获取表的自增键。 DELAYED rows are not visible to SELECT statements until they actually have been inserted. delayed的行在未真正插入数据库前,不能使用select查出结果。 INSERT DELAYED is handled as a simple INSERT (that is, without the DELAYED option) whenever the value of binlog_format is STATEMENT or MIXED. (In the latter case, the statement does not trigger a switch to row-based logging, and so is logged using the statement-based format.) This does not apply when using row-based binary logging mode (binlog_format set to ROW), in which INSERT DELAYED statements are always executed using the DELAYED option as specified, and logged as row-update events. DELAYED is ignored on slave replication servers, so that INSERT DELAYED is treated as a normal INSERT on slaves. This is because DELAYED could cause the slave to have different data than the master. 在从库上,delayed会被忽略。这是因为delayed可能会导致主从数据不一致。 Pending INSERT DELAYED statements are lost if a table is write locked and ALTER TABLE is used to modify the table structure. 如果表被写入索引,并且使用alter table来修改表结构,则挂起的insert delayed将会丢失。 INSERT DELAYED is not supported for views. insert delayed不支持视图。 INSERT DELAYED is not supported for partitioned tables. insert delayed不支持分区表。 The following describes in detail what happens when you use the DELAYED option to INSERT or REPLACE. In this description, the “thread” is the thread that received an INSERT DELAYED statement and “handler” is the thread that handles all INSERT DELAYED statements for a particular table. 下面详细描述当您使用INSERT或REPLACE的DELAYED选项时会发生什么情况。 在此描述中,“线程”是接收到INSERT DELAYED语句的线程,“处理程序”是处理特定表的所有INSERT DELAYED语句的线程。 When a thread executes a DELAYED statement for a table, a handler thread is created to process all DELAYEDstatements for the table, if no such handler already exists. 当一个线程为一个表执行一个DELAYED语句时,如果没有这个处理程序,就会创建一个处理程序线程来处理表的所有DELAYED语句。 The thread checks whether the handler has previously acquired a DELAYED lock; if not, it tells the handler thread to do so. The DELAYED lock can be obtained even if other threads have a READ or WRITE lock on the table. However, the handler waits for all ALTER TABLE locks or FLUSH TABLES statements to finish, to ensure that the table structure is up to date. 线程检查处理程序以前是否获取了DELAYED锁; 如果没有,它会通知处理程序线程这样做。 即使其他线程在表上具有READ或WRITE锁,也可以获得DELAYED锁。 但是,处理程序将等待所有ALTER TABLE锁定或FLUSH TABLES语句完成,以确保表结构是最新的。 The thread executes the INSERT statement, but instead of writing the row to the table, it puts a copy of the final row into a queue that is managed by the handler thread. Any syntax errors are noticed by the thread and reported to the client program. 线程执行INSERT语句,但不是将行写入表中,而是将最后一行的副本放入由处理程序线程管理的队列中。 任何语法错误都被线程注意到并被报告给客户端程序。 The client cannot obtain from the server the number of duplicate rows or the AUTO_INCREMENT value for the resulting row, because the INSERT returns before the insert operation has been completed. (If you use the C API, the mysql_info() function does not return anything meaningful, for the same reason.) 客户端无法从服务器获取重复行数或结果行的AUTO_INCREMENT值,因为INSERT在真正的插入操作完成之前返回。 (如果使用C API,出于同样的原因,mysql_info()函数不会返回任何有意义的内容。 The binary log is updated by the handler thread when the row is inserted into the table. In case of multiple-row inserts, the binary log is updated when the first row is inserted. 当行插入到表中时,二进制日志由处理程序线程更新。 在多行插入的情况下,插入第一行时更新二进制日志。 Each time that delayed_insert_limit rows are written, the handler checks whether any SELECT statements are still pending. If so, it permits these to execute before continuing. 每次写入delayed_insert_limit行时,处理程序都将检查是否有任何SELECT语句仍处于待处理状态。 如果是这样,它允许这些在继续之前执行。 When the handler has no more rows in its queue, the table is unlocked. If no new INSERT DELAYED statements are received within delayed_insert_timeout seconds, the handler terminates. 当处理程序的队列中没有更多的行时,表将被解锁。 如果在delayed_insert_timeout秒内没有收到新的INSERT DELAYED语句,则处理程序终止。 If more than delayed_queue_size rows are pending in a specific handler queue, the thread requesting INSERT DELAYED waits until there is room in the queue. This is done to ensure that mysqld does not use all memory for the delayed memory queue. 如果多于delayed_queue_size行在特定的处理程序队列中挂起,则请求INSERT DELAYED的线程将等待,直到队列中有空间。 这样做是为了确保mysqld不会将全部内存用于延迟内存队列。 The handler thread shows up in the MySQL process list with delayed_insert in the Command column. It is killed if you execute a FLUSH TABLES statement or kill it with KILL thread_id. However, before exiting, it first stores all queued rows into the table. During this time it does not accept any new INSERT statements from other threads. If you execute an INSERT DELAYED statement after this, a new handler thread is created. 处理程序线程显示在Command列中的delayed_insert的MySQL进程列表中。 如果你执行一个FLUSH TABLES语句或者用KILL thread_id杀死它,它就会被杀死。 但是,在退出之前,它首先将所有排队的行存储到表中。 在此期间,它不接受来自其他线程的任何新的INSERT语句。 如果在此之后执行INSERT DELAYED语句,则会创建一个新的处理程序线程。 This means that INSERT DELAYED statements have higher priority than normal INSERT statements if there is an INSERT DELAYED handler running. Other update statements have to wait until the INSERT DELAYED queue is empty, someone terminates the handler thread (with KILL thread_id), or someone executes a FLUSH TABLES. The following status variables provide information about INSERT DELAYED statements. Status Variable Meaning Delayed_insert_threads Number of handler threads Delayed_writes Number of rows written with INSERT DELAYED Not_flushed_delayed_rows Number of rows waiting to be written
21万+

被折叠的 条评论
为什么被折叠?



