Canal realizes Mysql data synchronization

Canal is an open source data synchronization tool of Alibaba. For related introduction and working principle, please refer to the official website:
Ali canal official introduction
This article mainly introduces the use of cannal to realize data synchronization between Mysql databases.

1. Canal environment construction

The principle of canal is based on the mysql binlog technology, so the mysql binlog write function must be enabled here.

(1) Check whether the binlog writing function is enabled

mysql> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin       | OFF    |
+---------------+-------+
1 row in set (0.00 sec)

(2) If log_bin is OFF, it means it is not enabled, and the binlog writing function is enabled

1. Modify the mysql configuration file my.cnf

vi /etc/my.cnf 
追加内容:
log-bin=mysql-bin     #binlog文件名
binlog_format=ROW     #选择row模式
server_id=1           #mysql实例id,不能和canal的slaveId重复

2. Restart mysql

service mysql restart	

3. Log in to the mysql client and view the log_bin variable

mysql> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin       | ON|
+---------------+-------+
1 row in set (0.00 sec)

log_bin is ON means it is turned on.

2. Download and install Canal service

(1) Download

Canal download address
This time the source database uses the Linux system, upload the canal.deployer-1.1.4.tar.gz file to the Linux system and decompress it:
tar zxvf canal.deployer-1.1.4.tar.gz

(2) Modify the configuration file

Enter the canal decompression directory, conf/example/instance.properties, modify the content of the instance.properties file:

#需要改成自己的数据库信息
canal.instance.master.address=Linux服务器IP:3306

#需要改成自己的数据库用户名与密码(Linux服务器)
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal

#需要改成同步的数据库表规则,例如只是同步一下表
#监听所有库所有表
canal.instance.filter.regex=.*\\..*
#监听指定库指定表
#canal.instance.filter.regex=test.user

(3) start canal

Go to the bin directory, ./startup.sh

(4) The server opens the port

If it is the source database running on the virtual machine, close the firewall of the virtual machine;
if it is a cloud server, take Alibaba Cloud as an example, add security group rules, and open port 11111, because the running port of canal is fixed at 11111.

Three, springboot integration canal

(1) Introduce dependencies

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

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

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <!--canal-->
    <dependency>
        <groupId>com.alibaba.otter</groupId>
        <artifactId>canal.client</artifactId>
    </dependency>
</dependencies>

(2) Add properties file configuration

# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root

(3) canal synchronization class

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

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

@Component
public class CanalClient {
    
    

    //sql队列
    private Queue<String> SQL_QUEUE = new ConcurrentLinkedQueue<>();

    @Resource
    private DataSource dataSource;

    /**
     * canal入库方法
     */
    public void run() {
    
    

        CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("192.168.44.132",
                11111), "example", "", "");
        int batchSize = 1000;
        try {
    
    
            connector.connect();
            connector.subscribe(".*\\..*");
            connector.rollback();
            try {
    
    
                while (true) {
    
    
                    //尝试从master那边拉去数据batchSize条记录,有多少取多少
                    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大于一定数值的时候就模拟执行
                    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<Entry> entrys) throws InvalidProtocolBufferException {
    
    
        for (Entry entry : entrys) {
    
    
            if (EntryType.ROWDATA == entry.getEntryType()) {
    
    
                RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
                EventType eventType = rowChange.getEventType();
                if (eventType == EventType.DELETE) {
    
    
                    saveDeleteSql(entry);
                } else if (eventType == EventType.UPDATE) {
    
    
                    saveUpdateSql(entry);
                } else if (eventType == EventType.INSERT) {
    
    
                    saveInsertSql(entry);
                }
            }
        }
    }

    /**
     * 保存更新语句
     *
     * @param entry
     */
    private void saveUpdateSql(Entry entry) {
    
    
        try {
    
    
            RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
            List<RowData> rowDatasList = rowChange.getRowDatasList();
            for (RowData rowData : rowDatasList) {
    
    
                List<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<Column> oldColumnList = rowData.getBeforeColumnsList();
                for (Column column : oldColumnList) {
    
    
                    if (column.getIsKey()) {
    
    
                        //暂时只支持单一主键
                        sql.append(column.getName() + "=" + column.getValue());
                        break;
                    }
                }
                SQL_QUEUE.add(sql.toString());
            }
        } catch (InvalidProtocolBufferException e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 保存删除语句
     *
     * @param entry
     */
    private void saveDeleteSql(Entry entry) {
    
    
        try {
    
    
            RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
            List<RowData> rowDatasList = rowChange.getRowDatasList();
            for (RowData rowData : rowDatasList) {
    
    
                List<Column> columnList = rowData.getBeforeColumnsList();
                StringBuffer sql = new StringBuffer("delete from " + entry.getHeader().getTableName() + " where ");
                for (Column column : columnList) {
    
    
                    if (column.getIsKey()) {
    
    
                        //暂时只支持单一主键
                        sql.append(column.getName() + "=" + column.getValue());
                        break;
                    }
                }
                SQL_QUEUE.add(sql.toString());
            }
        } catch (InvalidProtocolBufferException e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 保存插入语句
     *
     * @param entry
     */
    private void saveInsertSql(Entry entry) {
    
    
        try {
    
    
            RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
            List<RowData> rowDatasList = rowChange.getRowDatasList();
            for (RowData rowData : rowDatasList) {
    
    
                List<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();
        }
    }

    /**
     * 入库
     * @param sql
     */
    public void execute(String sql) {
    
    
        Connection con = null;
        try {
    
    
            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);
        }
    }
}

(4) Startup class

@SpringBootApplication
public class CanalApplication implements CommandLineRunner {
    
    
    @Resource
    private CanalClient canalClient;

    public static void main(String[] args) {
    
    
        SpringApplication.run(CanalApplication.class, args);
    }

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

So far, the data synchronization between the local Mysql and the remote Mysql database can be realized by starting the project. When the remote database changes, the local database synchronization changes.

Using canal to realize data synchronization between mysql and redis can be found in the blog post canal realizes data synchronization between mysql and redis

Guess you like

Origin blog.csdn.net/weixin_42194695/article/details/125935200