前面提到資料庫快取不一致的幾種解決方案,但是在不同的場景下各有利弊,而今天我們使用的canal進行快取與資料同步的方案是最好的,但是也有一個缺點,就是相對前面幾種解決方案會引入阿里巴巴的canal元件,訂閱消費binlog日誌,增加的系統複雜度。
canal官網地址:https://github.com/alibaba/canal/wiki
簡介:
canal [kə'næl],譯意為水道/管道/溝渠,主要用途是基於 MySQL 資料庫增量日誌解析,提供增量資料訂閱和消費
工作原理
-
canal 模擬 MySQL slave 的互動協議,偽裝自己為 MySQL slave ,向 MySQL master 傳送 dump 協議
-
MySQL master 收到 dump 請求,開始推送 binary log 給 slave (即 canal )
-
canal 解析 binary log 物件(原始為 byte 流)
第一步:建立MySQL配置檔案
建立MySQL檔案目錄
mkdir -p /home/mysql/{master,slave}/{logs,data,conf,mysql-files}
在/home/mysql/master/conf目錄下建立my.cnf配置檔案並寫入一下資訊
#主從複製-主機配置
[mysqld]
#設定主機啟動埠3316埠
port=3316
#主伺服器唯一ID
server-id = 1
#啟用二進位制日誌
log-bin=mysql-bin
#設定logbin格式
binlog_format = Row
#設定mysql的安裝目錄
#basedir=
#設定mysql資料庫的資料的存放目錄
datadir=/home/mysql/master/data
#允許最大連線數
max_connections=200
#允許連線失敗的次數
max_connect_errors=10
#服務端使用的字符集預設為utf8mb4
character-set-server=utf8mb4
#建立新表時將使用的預設儲存引擎
default-storage-engine=INNODB
#預設使用mysql_native_password外掛認證
#mysql_native_password
default_authentication_plugin=mysql_native_password
#忽略大小寫
lower-case-table-names = 1
[mysql]
#設定mysql客戶端預設字符集
default-character-set=utf8mb4
#MySQL匯入匯出檔案限制
#secure_file_priv =
[client]
#設定mysql客戶端連線服務端時預設使用的埠
port=3316
default-character-set=utf8mb4
第二步:拉取MySQL映象
docker pull mysql
第三步:建立MySQL容器
docker run \
-p 3316:3316 \
-e MYSQL_ROOT_PASSWORD=123456 \
-v /home/mysql/master/data:/home/mysql/master/data:rw \
-v /home/mysql/master/logs:/var/log/mysql:rw \
-v /home/mysql/master/conf/my.cnf:/etc/mysql/my.cnf:rw \
-v /home/mysql/mster/mysql-files:/var/lib/mysql-files \
-v /etc/localtime:/etc/localtime:ro \
--name mysql3316_master \
--privileged=true \
--restart=always \
-d mysql
第四步:建立canal進行資料同步的MySQL使用者
create user canal@'%' IDENTIFIED by 'canal';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%' ;
FLUSH PRIVILEGES;
第五步:建立canal
docker run -p 11111:11111 --name canal \
-e canal.destinations=canal_test \
-e canal.instance.master.address=192.168.0.100:3316 \
-e canal.instance.dbUsername=canal \
-e canal.instance.dbPassword=canal \
-e canal.instance.connectionCharset=UTF-8 \
-e canal.instance.tsdb.enable=true \
-e canal.instance.gtidon=false \
-e canal.instance.filter.regex=canal_test\\..* \
--privileged=true \
-d canal/canal-server:v1.1.5
說明:
-
-p 11111:11111
:這是canal的預設監聽埠 -
-e canal.instance.master.address=mysql:3316
-
-e canal.instance.dbUsername=canal
:資料庫使用者名稱 -
-e canal.instance.dbPassword=canal
:資料庫密碼 -
-e canal.instance.filter.regex=
:要監聽的表名稱
表名稱監聽支援的語法:
mysql 資料解析關注的表,Perl正規表示式.
多個正則之間以逗號(,)分隔,轉義符需要雙斜槓(\\)
常見例子:
1. 所有表:.* or .*\\..*
2. canal schema下所有表: canal\\..*
3. canal下的以canal打頭的表:canal\\.canal.*
4. canal schema下的一張表:canal.test1
5. 多個規則組合使用然後以逗號隔開:canal\\..*,mysql.test1,mysql.test2
檢視canal是否建立成功
docker exec -it canal bash 進入容器內部
檢視日誌,若顯示canal正在進行資料同步,則說明搭建成功
建立springboot專案
pom.xml新增依賴
<dependency>
<groupId>top.javatool</groupId>
<artifactId>canal-spring-boot-starter</artifactId>
<version>1.2.1-RELEASE</version>
</dependency>
application.yml檔案新增配置
canal:
destination: canal_test
server: master:11111
建立測試實體類
import org.springframework.data.annotation.Id;
import javax.persistence.Column;
import javax.persistence.Table;
@Table(name = "order")
public class Order {
@Id
@Column(name = "order_id")
private Long orderId;
@Column(name = "order_name")
private String orderName;
@Column(name = "order_status")
private Integer orderStatus;
@Column(name = "user_id")
private Long userId;
public Long getOrderId() {
return orderId;
}
public void setOrderId(Long orderId) {
this.orderId = orderId;
}
public String getOrderName() {
return orderName;
}
public void setOrderName(String orderName) {
this.orderName = orderName;
}
public Integer getOrderStatus() {
return orderStatus;
}
public void setOrderStatus(Integer orderStatus) {
this.orderStatus = orderStatus;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
@Override
public String toString() {
return "Order{" +
"orderId=" + orderId +
", orderName='" + orderName + '\'' +
", orderStatus=" + orderStatus +
", userId=" + userId +
'}';
}
}
建立測試handler
import org.springframework.stereotype.Component;
import top.javatool.canal.client.annotation.CanalTable;
import top.javatool.canal.client.context.CanalContext;
import top.javatool.canal.client.handler.EntryHandler;
import top.javatool.canal.client.model.CanalModel;
@Component
@CanalTable("order")
public class TestHandler implements EntryHandler<Order> {
@Override
public void insert(Order order) {
System.out.println("insert:" + order);
CanalModel canal = CanalContext.getModel();
System.out.println("================================================");
System.out.println(canal);
System.out.println("================================================");
}
@Override
public void update(Order before, Order after) {
System.out.println("update before:" + before);
System.out.println("update after:" + after);
CanalModel canal = CanalContext.getModel();
System.out.println("================================================");
System.out.println(canal);
System.out.println("================================================");
}
@Override
public void delete(Order order) {
System.out.println("delete:" + order);
CanalModel canal = CanalContext.getModel();
System.out.println("================================================");
System.out.println(canal);
System.out.println("================================================");
}
}
啟動springboot專案
隨意一下修改資料則可以看到以下日誌
version: 1
logfileName: "mysql-bin.000004"
logfileOffset: 2217
serverId: 1
serverenCode: "UTF-8"
executeTime: 1630830321000
sourceType: MYSQL
schemaName: ""
tableName: ""
eventLength: 90
}
entryType: TRANSACTIONBEGIN
storeValue: " +"
, header {
version: 1
logfileName: "mysql-bin.000004"
logfileOffset: 2378
serverId: 1
serverenCode: "UTF-8"
executeTime: 1630830321000
sourceType: MYSQL
schemaName: "canal_test"
tableName: "order"
eventLength: 67
eventType: UPDATE
props {
key: "rowsCount"
value: "1"
}
}
entryType: ROWDATA
storeValue: "\bf\020\002P\000b\263\002\n\034\b\000\020\004\032\border_id \001(\0000\000B\0011R\003int\n\'\b\001\020\f\032\norder_name \000(\0000\000B\001aR\fvarchar(255)\n*\b\002\020\f\032\forder_status \000(\0000\000B\00212R\fvarchar(255)\n$\b\003\020\f\032\auser_id \000(\0000\000B\0011R\fvarchar(255)\022\034\b\000\020\004\032\border_id \001(\0000\000B\0011R\003int\022(\b\001\020\f\032\norder_name \000(\0010\000B\00212R\fvarchar(255)\022*\b\002\020\f\032\forder_status \000(\0000\000B\00212R\fvarchar(255)\022$\b\003\020\f\032\auser_id \000(\0000\000B\0011R\fvarchar(255)"
, header {
version: 1
logfileName: "mysql-bin.000004"
logfileOffset: 2445
serverId: 1
serverenCode: "UTF-8"
executeTime: 1630830321000
sourceType: MYSQL
schemaName: ""
tableName: ""
eventLength: 31
}
entryType: TRANSACTIONEND
storeValue: "\022\003300"
],raw=false,rawEntries=[]]
update before:Order{orderId=null, orderName='a', orderStatus=null, userId=null}
update after:Order{orderId=1, orderName='12', orderStatus=12, userId=1}
================================================
CanalModel{id=4, database='canal_test', table='order', executeTime=1630830321000, createTime=null}
================================================