データベースシリーズ-Canalに基づくMySQLインクリメンタルデータ同期

環境への備え:

1. Redis(デフォルトポート6379)

2. Zookeeper(デフォルトポート2181)

3. Kafka(デフォルトポート9092)

4.運河(デフォルトポート11111)

5. MySQL(デフォルトポート3306

この記事のGithubコードアドレスhttps//github.com/cheriduk/spring-boot-integration-template

運河の紹介:公式の紹介を引用する


canalは、AlibabaMySQLデータベースBinlogの増分サブスクリプションおよび消費コンポーネントです。

名前:canal [kə'næ l]
翻訳:水路/パイプ/溝
言語:純粋なJava開発
ポジショニング:データベースの増分ログ分析に基づいて、増分データのサブスクリプションと消費を提供し、現在主にMySQLをサポートしています

当初、アリババのB2B企業は、杭州と米国にデュアルコンピュータールームが配備されていたため、コンピュータールーム間で同期する必要がありました。ただし、初期のデータベース同期ビジネスは、主にトリガー方式に基づいて増分変更を取得していましたが、2010年以降、Alibabaの企業は、データベースベースのログ分析を徐々に試みて同期の増分変更を取得し始め、その結果、増分変更が発生しました。それ以来、大量のサブスクリプションと消費のビジネスは新しい時代を開きました。ps。現在内部で使用されている同期は、MySQL8.xおよびOracleの一部のバージョンのログ解析をすでにサポートしています。

ログの増分サブスクリプションと消費によってサポートされるサービス:

  1. データベースミラーリング

  2. リアルタイムのデータベースバックアップ

  3. マルチレベルインデックス(売り手と買い手は別々にインデックスが付けられます)

  4. 検索ビルド(エラスティック検索)

  5. ビジネスキャッシュの更新(redis)

  6. 価格変更などの重要なビジネスニュース

運河のしくみ:

原理は比較的単純です:

  1. Canalは、mysqlスレーブの対話型プロトコルをシミュレートし、mysqlスレーブのふりをして、ダンププロトコルをmysqlマスターに送信します。

  2. mysqlマスターはダンプ要求を受信し、バイナリログをスレーブ(つまり、運河)にプッシュし始めます。

  3. Canalはバイナリログオブジェクト(元々はバイトストリーム)を解析します

上記は公式紹介です

 

運河リンク:https://pan.baidu.com/s/1HIT4b30BtXrkHym-w4peww抽出コード:ar6c


 プロジェクト開発でどのように使用しますか?

実際のプロジェクトでは、RocketMQまたはKafka使用してMQモード構成し、canalはデータをMQトピックに送信し、メッセージキューのコンシューマーを介してデータを消費します

この記事では、Canalのデプロイ、Kafkaの使用、およびデータのRedisへの同期について説明します。

アーキテクチャ図から、使用するコンポーネント(MySQL、Canal、Kafka、ZooKeeper、Redis)が明確にわかります。

誰もがMySQLの構築方法を知っている必要があります。ZooKeeperやRedisなど、インターネット上にも多くのリファレンスがあります。

主にカフカ建設について話します

まず、公式Webサイトからインストールパッケージダウンロードします。
 

解凍し、/ config / server.properties構成ファイルを開き、ログディレクトリを変更します

最初にZooKeeperを起動し、バージョン3.4.13を使用します。

 
次に、Kafkaを起動し、Kafkaのbinディレクトリでcmdを開き、次のコマンドを入力します。

kafka-server-start.bat ../../config/server.properties

ZooInspectorを介してZooKeeperに登録されているKafka関連の構成情報を確認できます。

 

次に、運河から送信されたデータを受信するためのキューを作成する必要があります。次のコマンドを使用します。

 

kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic canaltopic

 作成されるキューの名前はcanaltopicです。

ここに画像の説明を挿入

Cannalサーバーを構成する

運河の公式ウェブサイトから関連するインストールパッケージをダウンロードします。

 

canal.deployer-1.1.4 / confディレクトリでcanal.properties構成ファイルを見つけます。

# tcp, kafka, RocketMQ 这里选择kafka模式
canal.serverMode = kafka
# 解析器的线程数,打开此配置,不打开则会出现阻塞或者不进行解析的情况
canal.instance.parser.parallelThreadSize = 16
# 配置MQ的服务地址,这里配置的是kafka对应的地址和端口
canal.mq.servers = 127.0.0.1:9092
# 配置instance,在conf目录下要有example同名的目录,可以配置多个
canal.destinations = example

次に、インスタンスを構成し、/ conf / example /instance.properties構成ファイルを見つけます。

## mysql serverId , v1.0.26+ will autoGen(自动生成,不需配置)
# canal.instance.mysql.slaveId=0

# position info
canal.instance.master.address=127.0.0.1:3306
# 在Mysql执行 SHOW MASTER STATUS;查看当前数据库的binlog
canal.instance.master.journal.name=mysql-bin.000006
canal.instance.master.position=4596
# 账号密码
canal.instance.dbUsername=canal
canal.instance.dbPassword=Canal@****
canal.instance.connectionCharset = UTF-8
#MQ队列名称
canal.mq.topic=canaltopic
#单队列模式的分区下标
canal.mq.partition=0

データベース構成、承認済みアカウントの作成

 運河の原則は、それ自体をmysqlスレーブとしてシミュレートすることであるため、ここではmysqlスレーブとしての関連するアクセス許可が必要です。 

CREATE USER canal IDENTIFIED BY 'canal';    
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';  
-- GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;  
FLUSH PRIVILEGES; 

     既存のアカウントの場合、付与を通じてアクセス許可を照会できます。

show grants for 'canal' 

構成が完了したら、運河を開始できます。

テスト検証

このとき、Kafkaのコンシューマウィンドウを開いて、Kafkaがメッセージを受信するかどうかをテストできます。

kafka-console-consumer.bat --bootstrap-server 127.0.0.1:9092 --from-beginning --topic canaltopic

コンソールに文字化けが表示される場合は、一時的にエンコードを設定する必要があります

cmdコマンドラインを実行する前にUTF-8エンコーディングに切り替えるだけで、次のコマンドラインを使用します:chcp 65001

MySQLデータベースのデータを操作してから、Kafkaでの変更を観察します。

返された文字列に対応するデータ構造

公式の指示があります:

https://github.com/alibaba/canal/wiki/ClientAPI#%E6%95%B0%E6%8D%AE%E5%AF%B9%E8%B1%A1%E6%A0%BC%E5%BC %8F%E7%AE%80%E5%8D%95%E4%BB%8B%E7%BB%8Dentryprotocolproto 

最新バージョンを使用していますが、公式ウェブサイトのドキュメントに記載されているデータ形式が時間内に更新されない場合があります。いくつかの違いがあります。

Redisを起動し、データをRedisに同期します。

環境がセットアップされたら、以下のRedisクライアントコードを記述します

まず、KafkaとRedisのMaven依存関係を紹介します。

 <dependencies>
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.28</version>
            <scope>compile</scope>
        </dependency>
</dependencies>

application.ymlファイルで構成します。

spring:  
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
    password: 123456

Redisを操作するためのツールクラスを作成します。
 

@Component
public class RedisClient {

    /**
     * 获取redis模版
     */
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 设置redis的key-value
     */
    public void setString(String key, String value) {
        setString(key, value, null);
    }

    /**
     * 设置redis的key-value,带过期时间
     */
    public void setString(String key, String value, Long timeOut) {
        stringRedisTemplate.opsForValue().set(key, value);
        if (timeOut != null) {
            stringRedisTemplate.expire(key, timeOut, TimeUnit.SECONDS);
        }
    }

    /**
     * 获取redis中key对应的值
     */
    public String getString(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 删除redis中key对应的值
     */
    public Boolean deleteKey(String key) {
        return stringRedisTemplate.delete(key);
    }
}

データ同期用のMQコンシューマーを作成する

kafka構成情報をapplication.yml構成ファイルに追加します。

spring:
  kafka:
  	# Kafka服务地址
    bootstrap-servers: 127.0.0.1:9092
    consumer:
      # 指定一个默认的组名
      group-id: consumer-group1
      #序列化反序列化
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringDeserializer
      value-serializer: org.apache.kafka.common.serialization.StringDeserializer
      # 批量抓取
      batch-size: 65536
      # 缓存容量
      buffer-memory: 524288

受信するCanalBeanオブジェクトを作成できます 

public class CanalBean {
    //数据
    private List<Student> data;
    //数据库名称
    private String database;
    private long es;
    //递增,从1开始
    private int id;
    //是否是DDL语句
    private boolean isDdl;
    //表结构的字段类型
    private MysqlType mysqlType;
    //UPDATE语句,旧数据
    private String old;
    //主键名称
    private List<String> pkNames;
    //sql语句
    private String sql;
    private SqlType sqlType;
    //表名
    private String table;
    private long ts;
    //(新增)INSERT、(更新)UPDATE、(删除)DELETE、(删除表)ERASE等等
    private String type;
    //getter、setter方法
}
public class MysqlType {
    private String id;
    private String commodity_name;
    private String commodity_price;
    private String number;
    private String description;
    //getter、setter方法
}
public class SqlType {
    private int id;
    private int commodity_name;
    private int commodity_price;
    private int number;
    private int description;
}

テスト用のビジネステストテーブルに対応するBeanを作成します

 

@Data // lombok插件依赖
public class Student implements Serializable {
    private Long id;

    private String name;

    private Integer age;

    private static final long serialVersionUID = 1L;
}

最後に、消費用のコンシューマーCanalConsumerを作成できます

package com.gary.sync.consumer;

import com.alibaba.fastjson.JSONObject;
import com.gary.sync.model.CanalBean;
import com.gary.sync.model.Student;
import com.gary.sync.model.TbCommodityInfo;
import com.gary.sync.redis.RedisClient;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;

@Component
public class CanalConsumer {
    //日志记录
    private static Logger log = LoggerFactory.getLogger(CanalConsumer.class);
    //redis操作工具类
    @Resource
    private RedisClient redisClient;
    //监听的队列名称为:canaltopic
    @KafkaListener(topics = "canaltopic")
    public void receive(ConsumerRecord<?, ?> consumer) {
        String value = (String) consumer.value();
        log.info("topic名称:{},key:{},分区位置:{},下标:{},value:{}", consumer.topic(), consumer.key(),consumer.partition(), consumer.offset(), value);
        //转换为javaBean
        CanalBean canalBean = JSONObject.parseObject(value, CanalBean.class);
        //获取是否是DDL语句
        boolean isDdl = canalBean.getIsDdl();
        //获取类型
        String type = canalBean.getType();
        //不是DDL语句
        if (!isDdl) {
            List<Student> students = canalBean.getData();
            //过期时间
            long TIME_OUT = 600L;
            if ("INSERT".equals(type)) {
                //新增语句
                for (Student student : students) {
                    Long id = student.getId();
                    //新增到redis中,过期时间是10分钟
                    redisClient.setString(String.valueOf(id), JSONObject.toJSONString(student), TIME_OUT);
                }
            } else if ("UPDATE".equals(type)) {
                //更新语句
                for (Student student : students) {
                    Long id = student.getId();
                    //更新到redis中,过期时间是10分钟
                    redisClient.setString(String.valueOf(id), JSONObject.toJSONString(student), TIME_OUT);
                }
            } else {
                //删除语句
                for (Student student : students) {
                    Long id = student.getId();
                    //从redis中删除
                    redisClient.deleteKey(String.valueOf(id));
                }
            }
        }
    }
}

MySQLとRedisのデータ同期をテストする

-zookeeper- "kafka-" canal- "redisを順番に開始します

動物園の飼育係

カフカ 

運河 

繰り返す

テストデータの準備:

最初にMySQLでテーブルを作成します

DROP TABLE IF EXISTS `student`;

CREATE TABLE `student` (
  `id` int NOT NULL,
  `name` varchar(25) DEFAULT NULL,
  `age` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

次に、プロジェクトを開始します 

 次に、データを追加します。

INSERT INTO `test`.`student` (`id`, `name`, `age`) 
VALUES
  ('777', '测试', '123') ;

新しいデータはstudentテーブルにあります。

Redisは対応するデータも見つけ、同期が成功したことを証明しました。

 

使用するシーン:

  1. Canalは増分データのみを同期できます。

  2. リアルタイム同期ではなく、準リアルタイム同期です。

増分同期、あまり実現可能ではないシナリオ

 

 

おすすめ

転載: blog.csdn.net/Coder_Boy_/article/details/111055381