前言:
mysql數據的同步只能解決數據結構相同的數據同步,假如我想把mysql中的數據變更同步到oracle、es、mq、redis或者說同樣是mysql但是表結構不同的數據庫中,mysql自帶的主從功能已經滿足不了這種需求了;阿里巴巴推出了一個開源框架canal 就能解決這類問題。
環境:
1、canal.deployer:1.1.4
2、canal.adapter:1.1.4
3、elasticsearch:6.7.2 (1.1.4canal不支持7.x的es,支持的es版本爲 6.4.3-6.8)
4、mysql:5.7
5、jdk:1.8
注意:本文主要是講解canal,所以默認已經有一點es基礎,並且es、es的可視化工具和mysql已經安裝好了。。。
準備操作:
一、canal安裝:
下載地址: https://github.com/alibaba/canal/releases
說明:這裏需要下載的是:
1、canal.deployer1.1.4版本- - - 可以理解爲相當於canal的服務端
2、canal.adapter1.1.4版本- - - 可以理解爲相當於canal的插件
3、最新的是1.1.5版本,但是是快照版,我們這裏還是選擇穩定版本
二、mysql修改
1、配置文件
[mysqld]
log-bin=mysql-bin #開啓 binlog
binlog-format=ROW # 選擇 ROW 模式(不瞭解的可以看下博主之前的文章)
server_id=1 # 不要和 canal 的 slaveId 重複就行
注意:
1、mac下還需要修改:bind-address = 127.0.0.1->bind-address = 0.0.0.0
2、修改完配置文件一定要記得重啓mysql
2、創建一個可以遠程連接的用戶
CREATE USER canal IDENTIFIED BY 'canal';
GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;
FLUSH PRIVILEGES;
三、canal-deployer修改:
1、打開deployer/conf/example
2、刪除除instance.properties以外的所有東西(如果不是新下載的)
3、修改配置文件:instance.properties:
## mysql serverId , v1.0.26+ will autoGen
# 這個東西可以不設置,如果要設置別和上面mysql配置文件中的值重複就行
# canal.instance.mysql.slaveId=0
canal.instance.master.address=mysql啓動的ip:端口 #例如:192.168.34.66:3306
# username/password
canal.instance.dbUsername=canal # 剛纔自己創建賬號
canal.instance.dbPassword=canal # 剛纔自己創建密碼
canal.instance.connectionCharset = UTF-8
# enable druid Decrypt database password
# 這個可以根據需要修改過濾,默認是直接監聽所有
canal.instance.filter.regex=.*\\..* #例如:canal_tsdb.test_table1
四、啓動canal-deployer
linux/mac:切換到deployer/bin目錄下 sh startup.sh 即可
windows:切換到deployer/bin目錄下雙擊startup.bat 即可
數據同步一:java代碼實現
1、創建一個maven工程,添加以下依賴
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client</artifactId>
<version>1.1.4</version>
</dependency>
2、編寫java代碼,獲取mysql中數據的變化(這段代碼canal的github上有)
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 java.net.InetSocketAddress;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
public class SimpleCanalClientExample {
public static void main(String args[]) {
// 創建鏈接
//192.168.35.254爲canal-deployer啓動的ip地址
//canal-deployer如果你沒有修改過的話,默認端口是11111
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("192.168.35.254", 11111), "example", "", "");
//底層默認是1000
int batchSize = 1;
try {
connector.connect();
connector.subscribe(".*\\..*");
// connector.subscribe("canal_tsdb.test_table1");
//把上次停止後未提交的數據回滾,因爲不確定是否已處理
connector.rollback();
while (true) {
System.out.println("當前時間爲:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
//每次獲取多少條數據
Message message = connector.getWithoutAck(batchSize);
//System.out.println("內容爲:"+ JSON.toJSONString(message));
long batchId = message.getId();
int size = message.getEntries().size();
if (batchId == -1 || size == 0) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
} else {
try {
System.out.println(batchId);
printEntry(message.getEntries());
// int i=1/0;
connector.ack(batchId); // 提交確認
} catch (Exception e) {
connector.rollback(batchId); // 處理失敗, 回滾數據
}
}
}
} finally {
connector.disconnect();
}
}
private static void printEntry(List<Entry> entrys) {
System.out.println("讀取長度爲:" + entrys.size());
for (Entry entry : entrys) {
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
continue;
}
RowChange rowChage = null;
try {
rowChage = RowChange.parseFrom(entry.getStoreValue());
} catch (Exception e) {
throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
e);
}
EventType eventType = rowChage.getEventType();
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));
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<Column> columns) {
for (Column column : columns) {
System.out.println(column.getName() + " : " + column.getValue() + " update=" + column.getUpdated());
}
}
}
3、運行之後,只要我們修改mysql中的數據,就會有東西打印出來,如圖所示:
4、分析: 上圖可以看到,其實在java代碼中已經能拿到mysql中數據的變化,可以具體到那個數據源,那張表,那條記錄,更新的甚至都能知道更新前和更新後分別是什麼
5、思考: 個人覺得這種方式比較靈活,在java代碼中拿到了數據源,表,字段,具體什麼操作等信息之後,如果想同步數據,不是分分鐘的事情,無論你是es,oracle,mq,redis,mysql還是什麼hbase,只要java能連的東西,都能同步數據,但是這種方式同樣也有缺點,就是主庫只要一發生變化,從庫就要執行相同的操作,而且是用java去操作對應的備份單元,這樣就要求從庫和主庫的性能不能差太多。。。
數據同步二:mysql同步到myql中
1、準備兩個mysql數據庫
2、主從庫的賬號密碼都得保證能遠程連接
3、注意: mysql版本不能是8.x,目前測出來,canal1.1.4不支持mysql8.x
4、修改canal-adapter/conf/application.yml配置文件:
canal.conf:
mode: tcp
# 修改成自己canal-deployer啓動的ip和端口
canalServerHost: 192.168.35.254:11111
batchSize: 500
syncBatchSize: 1000
retries: 0
timeout:
accessKey:
secretKey:
srcDataSources:
defaultDS:
# 換成自己mysql主庫的地址和數據源
url: jdbc:mysql://192.168.34.66:3306/canal_tsdb?useUnicode=true
username: canal # 換成自己mysql主庫的賬號
password: canal # 換成自己mysql主庫的密碼
canalAdapters:
- instance: example
groups:
- groupId: g1
outerAdapters:
- name: logger
- name: rdb
key: mysql1
properties:
jdbc.driverClassName: com.mysql.jdbc.Driver
jdbc.url: jdbc:mysql://從庫ip:3306/從庫數據源?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
jdbc.username: 從庫賬號
jdbc.password: 從庫密碼
5、修改canal-adapter/conf/rdb/mytest_user.yml配置文件:
dataSourceKey: defaultDS
destination: example
groupId: g1
outerAdapterKey: mysql1
concurrent: true
dbMapping:
#配置數據庫--主庫
database: canal_tsdb
#配置表--主庫
table: test1
#從庫
targetTable: bqorderdb.test2
targetPk:
idid: id
#如果兩張表的結構是一樣的話,直接設置爲true
#mapAll: true
targetColumns:
#從表字段名字: 主表字段名字
idid: id
name: name
address: address
6、啓動canal-adapter:
linux/mac:切換到adapter/bin目錄下 sh startup.sh 即可
windows:切換到adapter/bin目錄下雙擊startup.bat 即可
7、效果: 主庫canal_tsdb下的test1表和從庫下bqorderdb下的test2表實現了同步
8、注意:
1、canal-adapter/conf/rdb/mytest_user.yml配置文件修改的時候,需要注意主庫和從庫之間的表結構的問題,如果相同,可以直接配置mapAll: true,如果不一樣,則需要配置targetColumns,格式爲 從表字段名字: 主表字段名字
2、canal-adapter/conf/rdb/mytest_user.yml的groupId應該和canal-adapter/conf/application.yml中的保持一致
9、思考: 如果需要同時同步多張表怎麼辦,或者說來自不同數據源的表?
10、解決: 在canal-adapter/conf/rdb/mytest_user.yml和canal-adapter/conf/application.yml中再增加一個groupId即可
canal.conf:
mode: tcp
canalServerHost: 192.168.35.254:11111
batchSize: 500
syncBatchSize: 1000
retries: 0
timeout:
accessKey:
secretKey:
srcDataSources:
defaultDS:
url: jdbc:mysql://192.168.34.66:3306/canal_tsdb?useUnicode=true
username: canal
password: canal
canalAdapters:
- instance: example
groups:
- groupId: g1
outerAdapters:
- name: logger
- name: rdb
key: mysql1
properties:
jdbc.driverClassName: com.mysql.jdbc.Driver
jdbc.url: jdbc:mysql://從庫ip:3306/從庫數據源?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
jdbc.username: 從庫賬號
jdbc.password: 從庫密碼
- groupId: g2
outerAdapters:
- name: logger
- name: rdb
key: mysql1
properties:
jdbc.driverClassName: com.mysql.jdbc.Driver
jdbc.url: jdbc:mysql://從庫ip:3306/從庫數據源?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
jdbc.username: 從庫賬號
jdbc.password: 從庫密碼
dataSourceKey: defaultDS
destination: example
groupId: g1
outerAdapterKey: mysql1
concurrent: true
dbMapping:
#配置數據庫--主庫
database: canal_tsdb
#配置表--主庫
table: test1
#從庫
targetTable: bqorderdb.test2
targetPk:
idid: id
#如果兩張表的結構是一樣的話,直接設置爲true
#mapAll: true
targetColumns:
#從表字段名字: 主表字段名字
idid: id
name: name
address: address
groupId: g2
outerAdapterKey: mysql1
concurrent: true
dbMapping:
#配置數據庫--主庫
database: canal_tsdb
#配置表--主庫
table: canal_test
#從庫
targetTable: bqorderdb.test3
targetPk:
idid: id
#如果兩張表的結構是一樣的話,直接設置爲true
#mapAll: true
targetColumns:
#從表字段名字: 主表字段名字
idid: id
name: name
address: address
數據同步三:mysql同步到es中
1、準備一個mysql數據庫
2、準備一個es,但是版本不能低於6.4.3也不能高於6.8
3、創建一個index、type和mappings
4、修改canal-adapter/conf/application.yml配置文件:
canal.conf:
mode: tcp
canalServerHost: 192.168.35.254:11111
batchSize: 500
syncBatchSize: 1000
retries: 0
timeout:
accessKey:
secretKey:
srcDataSources:
defaultDS:
url: jdbc:mysql://192.168.34.66:3306/canal_tsdb?useUnicode=true
username: canal
password: canal
canalAdapters:
- instance: example
groups:
- groupId: g1
outerAdapters:
- name: logger
- name: es
hosts: es啓動的ip地址:9300
properties:
cluster.name: elasticsearch
5、修改canal-adapter/conf/test1.yml配置文件:
dataSourceKey: defaultDS
destination: example
groupId: g1
esMapping:
_index: test1
_type: _doc
_id: _id
upsert: true
sql: "select a.id as _id,a.name,a.address from canal_tets a"
commitBatch: 3000
6、注意:
1、canal-adapter/conf/rdb/mytest_user.yml的groupId應該和canal-adapter/conf/application.yml中的保持一致
2、在創建es的index的時候,如果是7以下的版本,還是需要創建type的,這裏用的es版本是6.7.2,所以也是需要創建的
3、es同步的主要關鍵在於這句sql,他能把mysql表中的字段映射到es的field上,要映射幾個字段就查幾個字段
7、效果: 我們可以看到mysql中操作一條數據,es中也會對應的變更