97 基於Binlog實現MySQL與Redis數據一致性問題 -f 指定使用的 Compose 模板文件,默認爲 docker-compose.yml,可以多次指定。

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("================&gt; 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("-------&gt; before");
//                    printColumn(rowData.getBeforeColumnsList());
//                    System.out.println("-------&gt; 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;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章