mysql 與Redis 數據一致性問題 直接將Redis清空
中間件 canal框架 基於 docker環境構建
canal 框架 原理:
<u>https://gitee.com/mirrors/canal?utm_source=alading&utm_campaign=repo</u>
canal 框架原理
1,canal僞裝成mysql從節點 訂閱mysql 主節點的binlog文件
2,當我們的mysql 主節點 binlog 文件發生了變化,則將binlog 文件發送給canal服務器端
3,canal 服務器端將該binlog 文件二進制轉換成json格式給canal客戶端
4,canal客戶端在將改數據同步到Redis/ES
基於Binlog 開啓方式
1.mysql 開啓binlog 文件配置
windows 配置
查詢 my.ini配置文件位置
C:\ProgramData\MySQL\MySQL Server 5.7
2, linux mysql
docker cp mysql_slave:/etc/mysql/my.cnf /usr/local/mysql/slave/my.cnf
cd /usr/local/mysql/slave
Vi my.conf
log-bin=mysql-bin
server-id=2
安裝canal
docker pull canal/canal-server:latest
docker run -p 11111:11111 --name canal -id canal/canal-server
進入容器
docker exec -it canal /bin/bash
編輯配置文件
vi /home/admin/canal-server/conf/canal.properties
# 修改canal.id 不能與之前的mysql配置id相同
vi /home/admin/canal-server/conf/example/instance.properties
重啓canal
docker restart canal
# 可以設置開機啓動 適用於所有容器
docker update --restart=always canal
Docker-compose 構建canal
version: '3'
services:
canal-server:
image: canal/canal-server:v1.1.4
container_name: canal-server
ports:
- 11111:11111
environment:
- canal.instance.mysql.slaveId=3
- canal.auto.scan=false
- canal.destinations=mayikt-commodity
- canal.instance.master.address=192.168.75.144:3306
- canal.instance.dbUsername=canal
- canal.instance.dbPassword=canal
canal.instance.mysql.slaveId:slaveId不能與mysql的serverId一樣
canal.instance.master.address:mysql地址
canal.instance.dbUsername:mysql賬號
canal.instance.dbPassword:mysql密碼
-f 指定使用的 Compose 模板文件,默認爲 docker-compose.yml,可以多次指定。
docker-compose -f docker-compose.yml up -d
package com.mayikt.canal.demo;
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.util.List;
public class ClientSample {
/**
* 不推薦使用tcp模式
* canaltcp 模式
* 高併發的情況下 建議整合canal+kafka (效率是非常)
* 需要注意 消費者消費順序一致性的問題
*
* @param args
*/
public static void main(String args[]) {
// 創建鏈接
CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("192.168.75.144",
11111), "mayikt_canal_test", "canal", "canal");
int batchSize = 1000;
int emptyCount = 0;
try {
connector.connect();
connector.subscribe(".*\\..*"); // 監聽所有的表結構
connector.rollback();
int totalEmptyCount = 120;
while (emptyCount < totalEmptyCount) {
Message message = connector.getWithoutAck(batchSize); // 獲取指定數量的數據
long batchId = message.getId();
int size = message.getEntries().size();
if (batchId == -1 || size == 0) {
// emptyCount++;
// System.out.println("empty count : " + emptyCount);
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// }
} else {
emptyCount = 0;
// System.out.printf("message[batchId=%s,size=%s] \n", batchId, size);
printEntry(message.getEntries());
}
connector.ack(batchId); // 提交確認
// connector.rollback(batchId); // 處理失敗, 回滾數據
}
System.out.println("empty too many times, exit");
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
connector.disconnect();
}
}
private static void printEntry(List<CanalEntry.Entry> entrys) {
for (CanalEntry.Entry entry : entrys) {
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN || entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
continue;
}
CanalEntry.RowChange rowChage = null;
try {
rowChage = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
} catch (Exception e) {
throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
e);
}
CanalEntry.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 (CanalEntry.RowData rowData : rowChage.getRowDatasList()) {
// 延遲的概率非常大 kafka 對消費者集羣 Redis 需要注意 消費 消費順序一致性問題
switch (eventType) {
case DELETE:
delRedisUser(rowData);
break;
case INSERT:
default:
updateRedisUser(rowData);
}
// if (eventType == CanalEntry.EventType.DELETE) {
//
// } else if (eventType == CanalEntry.EventType.INSERT) {
// printColumn(rowData.getAfterColumnsList());
// // 同步 Redis 調用Redis api 插入 一條key
// } else {
// // 同步 Redis 調用Redis api update
// System.out.println("-------> before");
// printColumn(rowData.getBeforeColumnsList());
// System.out.println("-------> after");
// printColumn(rowData.getAfterColumnsList());
// }
}
}
}
private static void printColumn(List<CanalEntry.Column> columns) {
for (CanalEntry.Column column : columns) {
System.out.println(column.getName() + " : " + column.getValue() + " update=" + column.getUpdated());
}
}
/**
* 1.在Redis key 都是爲 mysql表中的 主鍵id;
* 2.Redis key mysql表中 該主鍵id 對應的一行數據
*/
private static void updateRedisUser(CanalEntry.RowData rowData) {
// id列、name 列
CanalEntry.Column idColumns = rowData.getAfterColumns(0);
String idValue = idColumns.getValue();
System.out.println(idValue);
// name列
CanalEntry.Column nameColumns = rowData.getAfterColumns(1);
String nameValue = nameColumns.getValue();
System.out.println(nameValue);
JedisUtils.getJedis().set(idValue, nameValue);// 同步es
//canal 整合kafka-----kafka消費則拿到數據json 直接同步到Redis----整合kafka api封裝
}
private static void delRedisUser(CanalEntry.RowData rowData) {
// id列、name 列
CanalEntry.Column idColumns = rowData.getBeforeColumns(0);
rowData.getBeforeColumns(0);// 獲取刪除之前的數據---該行數據是存在的
rowData.getAfterColumns(0);// 獲取 index 越界
String idValue = idColumns.getValue();
JedisUtils.getJedis().del(idValue); // 同步es
// es mongdb
}
}
package com.mayikt.canal.demo;
import redis.clients.jedis.Jedis;
public class JedisUtils {
private static Jedis jedis = new Jedis("127.0.0.1", 6379);
public static Jedis getJedis() {
return jedis;
}
}