cancal数据同步,a数据库同步到b数据库步骤,保证看了就会

184 阅读4分钟

业务情况:

在工作中遇到一个需求,就是在运维项目的时候,甲方要求我去推送接口数据,但是呢要求就是不能查原来的数据库,必须复制代码出来。这就有一个问题,就是说,我复制出来的数据库根本不是实时的,还需要每天去手动把数据拿过来,我感觉太麻烦了,由此我上网搜了解决方案,发现可以使用cancal实现数据同步功能,并且延迟低。

1.修改mysql文件,打开binlog日志

修改my.ini的配置文件

server-id = 1
#log-bin=D:\javaFiles\mysql8\binlog\bin-log.log
binlog-format=ROW 
binlog-do-db=ry-vue(指定哪个数据库打开binlog日志)

binlog-format三种模式对比

blog.csdn.net/wang0907/ar…

2.创建远程用户,并且权限为只读

create user 'canal'@'%'IDENTIFIED BY 'boc123'
grant all on *.* to 'canal'@'%'
flush privileges;

我们重启之后使用命令查看binlog的值

:show variables like 'log_bin' 查看值是不是为on


是就说明成功了

3.安装下载并配置cancl:

下载地址Releases · alibaba/canal · GitHub

选择第三个就行

对文件进行配置

配置D:\javaFiles\cancal\canal.deployer-1.1.7-SNAPSHOT\conf\example

下面的instance.properties
canal.instance.dbUsername=cancl

canal.instance.dbPassword=boc123
为读取数据库的账号密码,需要改成数据库的账号密码,刚才不是创建了一个吗,就用那个那个权限低。只读,保证安全。

canal.instance.filter.regex=.\..

这句话是监控所有表的意思
canal.instance.master.address=127.0.0.1:3306
监控数据库的地址

其他的就可以不改了

然后启动bin下面的start.bat文件

这个客户端就相当于运行好了

4.编写java代码:

客户端:

package com.ruoyi.web.controller.ces;

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.InvalidProtocolBufferException;
import org.apache.commons.dbutils.DbUtils;
import org.springframework.stereotype.Component;
import org.apache.commons.dbutils.QueryRunner;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.net.InetSocketAddress;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

@Component
public class CanalClient {

    //sql队列
    //下面判断拼接后的sql会加入这个队列
    private Queue<String> SQL_QUEUE = new ConcurrentLinkedQueue<>();

    @Resource
    private DataSource dataSource;

    /**
     * canal入库方法
     */
    //指定要监听的ip地址的canal端口号,默认开启为11111
    public void run() {
        CanalConnector connector = CanalConnectors.newSingleConnector(new
                                                                      InetSocketAddress("127.0.0.1",
                                                                                        11111), "example", "", ""); 
        //服务器的 IP 地址和端口,以及需要订阅的 instance 名称
        int batchSize = 1000;//每次拉取更新数据的批大小为 1000 条。
        try {
            connector.connect();
            connector.subscribe(".*\..*");
            connector.rollback(); //rollback 方法用于在新的一轮拉取之前,清除之前的状态。
            try {
                while (true) {
                    //尝试从master那边拉去数据batchSize条记录,有多少取多少
                    //监控上面ip的数据库是否变化
                    Message message = connector.getWithoutAck(batchSize);//拉取更新数据
                    long batchId = message.getId();
                    int size = message.getEntries().size();
                    if (batchId == -1 || size == 0) {
                        //没变化就睡
                        Thread.sleep(1000);
                    } else {
                        //有变化就同步
                        dataHandle(message.getEntries());
                    }
                    connector.ack(batchId);
                    //当队列里面堆积的sql大于一定数值的时候就模拟执行
                    //队列里如果有sql语句就执行
                    if (SQL_QUEUE.size() >= 1) {
                        executeQueueSql();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (InvalidProtocolBufferException e) {
                e.printStackTrace();
            }
        } finally {
            connector.disconnect();
        }
    }

    /**
     * 模拟执行队列里面的sql语句
     */
    public void executeQueueSql() {
        int size = SQL_QUEUE.size();
        for (int i = 0; i < size; i++) {
            String sql = SQL_QUEUE.poll();
            System.out.println("[sql]----> " + sql);
            this.execute(sql.toString());
        }
    }

    /**
     * 数据处理
     *
     * @param entrys
     */
    private void dataHandle(List<CanalEntry.Entry> entrys) throws
    InvalidProtocolBufferException {
        for (CanalEntry.Entry entry : entrys) {
            if (CanalEntry.EntryType.ROWDATA == entry.getEntryType()) {
                CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
                CanalEntry.EventType eventType = rowChange.getEventType();
                //判断当前是什么操作:删除、更新、插入
                if (eventType == CanalEntry.EventType.DELETE) {
                    saveDeleteSql(entry);
                } else if (eventType == CanalEntry.EventType.UPDATE) {
                    saveUpdateSql(entry);
                } else if (eventType == CanalEntry.EventType.INSERT) {
                    saveInsertSql(entry);
                }
            }
        }
    }
 
 
    //保存更新语句
    private void saveUpdateSql(CanalEntry.Entry entry) {
        try {
            CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
            List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
            for (CanalEntry.RowData rowData : rowDatasList) {
                List<CanalEntry.Column> newColumnList = rowData.getAfterColumnsList();
                StringBuffer sql = new StringBuffer("update " +
                        entry.getHeader().getTableName() + " set ");
                for (int i = 0; i < newColumnList.size(); i++) {
                    sql.append(" " + newColumnList.get(i).getName()
                            + " = '" + newColumnList.get(i).getValue() + "'");
                    if (i != newColumnList.size() - 1) {
                        sql.append(",");
                    }
                }
                sql.append(" where ");
                List<CanalEntry.Column> oldColumnList = rowData.getBeforeColumnsList();
                for (CanalEntry.Column column : oldColumnList) {
                    if (column.getIsKey()) {
                        //暂时只支持单一主键
                        sql.append(column.getName() + "=" + column.getValue());
                        break;
                    }
                }
                SQL_QUEUE.add(sql.toString());
            }
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
    }
 
    //保存删除语句
    private void saveDeleteSql(CanalEntry.Entry entry) {
        try {
            CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
            List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
            for (CanalEntry.RowData rowData : rowDatasList) {
                List<CanalEntry.Column> columnList = rowData.getBeforeColumnsList();
                StringBuffer sql = new StringBuffer("delete from " +
                        entry.getHeader().getTableName() + " where ");
                for (CanalEntry.Column column : columnList) {
                    if (column.getIsKey()) {
                        //暂时只支持单一主键
                        sql.append(column.getName() + "=" + column.getValue());
                        break;
                    }
                }
                SQL_QUEUE.add(sql.toString());
            }
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
    }
 
 
    //保存插入语句
    private void saveInsertSql(CanalEntry.Entry entry) {
        try {
            CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
            List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
            for (CanalEntry.RowData rowData : rowDatasList) {
                List<CanalEntry.Column> columnList = rowData.getAfterColumnsList();
                StringBuffer sql = new StringBuffer("insert into " +
                        entry.getHeader().getTableName() + " (");
                for (int i = 0; i < columnList.size(); i++) {
                    sql.append(columnList.get(i).getName());
                    if (i != columnList.size() - 1) {
                        sql.append(",");
                    }
                }
                sql.append(") VALUES (");
                for (int i = 0; i < columnList.size(); i++) {
                    sql.append("'" + columnList.get(i).getValue() + "'");
                    if (i != columnList.size() - 1) {
                        sql.append(",");
                    }
                }
                sql.append(")");
                SQL_QUEUE.add(sql.toString());
            }
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
    }
 
    //入库
    //与数据库连接,并执行队列里面取出的sql语句
    public void execute(String sql) {
        Connection con = null;
        try {
            System.out.println(sql);
            if (null == sql) return;
            con = dataSource.getConnection();
            QueryRunner qr = new QueryRunner();
            int row = qr.execute(con, sql);
            System.out.println("update: " + row);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DbUtils.closeQuietly(con);
        }
    }
 
 
}

启动类:

package com.ruoyi;

import com.ruoyi.web.controller.ces.CanalClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

/**
 * 启动程序
 * 
 * @author ruoyi
 */
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class RuoYiApplication implements CommandLineRunner
{


    @Autowired
    private CanalClient canalClient;


    public static void main(String[] args)
    {
        // System.setProperty("spring.devtools.restart.enabled", "false");
        SpringApplication.run(RuoYiApplication.class, args);
        System.out.println("(♥◠‿◠)ノ゙  若依启动成功   ლ(´ڡ`ლ)゙  \n" +
                           " .-------.       ____     __        \n" +
                           " |  _ _   \      \   \   /  /    \n" +
                           " | ( ' )  |       \  _. /  '       \n" +
                           " |(_ o _) /        _( )_ .'         \n" +
                           " | (_,_).' __  ___(_ o _)'          \n" +
                           " |  |\ \  |  ||   |(_,_)'         \n" +
                           " |  | \ `'   /|   `-'  /           \n" +
                           " |  |  \    /  \      /           \n" +
                           " ''-'   `'-'    `-..-'              ");
    }

    @Override
    public void run(String... args) throws Exception {
        //项目启动,执行canal客户端监听
        canalClient.run();
    }

}

maven:

<dependency>
  <groupId>com.alibaba.otter</groupId>
  <artifactId>canal.client</artifactId>
  <version>1.1.4</version>
</dependency>

<dependency>
  <groupId>commons-dbutils</groupId>
  <artifactId>commons-dbutils</artifactId>
  <version>1.7</version>
</dependency>

这样一个简单canal同步工具就写好了