序文
最近、Huawei Cloud Yaoyun Server L インスタンスがリリースされたので、私も構築して遊んでみましたが、このブログでは、Canal の Docker イメージを Huawei Cloud にデプロイする方法と、春のプロジェクトでの事前アプリケーションを紹介します。
その他の関連する Huawei Cloud Yaoyun Server L インスタンスの評価記事のリストは次のとおりです。
-
Java8 環境のインストール & 環境変数の設定 & Spring プロジェクトのデプロイ & [!] 未解決の問題があります
-
Spring プロジェクトのデプロイ ポート開放問題の解決策とサーバー プロジェクト環境のセットアップ MySQL、Redis、Minio... ガイド
記事ディレクトリ
導き出す
1.Canal: MySQL データベースの増分ログ分析に基づいて、増分データのサブスクリプションと消費を提供します;
2. Canal の使用法、MySQL 構成、Docker Canal のインストール;
3. クラウド サーバーのオープン ポート、サーバー ポートが開いているかどうかをテストする方法;
4春の運河の予備施用。
1. 運河のパイプラインを理解する
1.運河とは何ですか?
https://github.com/alibaba/canal
https://github.com/alibaba/canal/wiki/ClientExample
Canal は、Mysql binlog に基づく Alibaba オープン ソースの増分サブスクリプションおよび消費コンポーネントです。これを通じて、データベースの binlog ログをサブスクライブし、データ ミラーリング、データ異質性、データ インデックス作成、キャッシュ更新などのデータ消費を実行できます。等 メッセージ キューと比較して、このメカニズムはデータの順序付けと一貫性を実現できます。
canal [kə'næl]、水路/パイプライン/溝と訳され、その主な目的は、MySQL データベースの増分ログ分析に基づいて増分データのサブスクリプションと消費を提供することです。
アリババの初期には、杭州と米国に 2 つのコンピュータ ルームを展開していたため、コンピュータ ルーム間の同期がビジネス上必要となり、その実装方法は主にビジネス トリガーに基づいて段階的な変更を取得することでした。2010 年以来、企業は同期のための増分変更を取得するためにデータベース ログを解析することを徐々に試み、その結果、データベースの増分サブスクリプションおよび消費ビジネスが多数誕生しました。
ログの増分サブスクリプションと消費に基づくビジネスには以下が含まれます。
- データベースミラーリング
- データベースのリアルタイムバックアップ
- インデックスの構築とリアルタイムのメンテナンス (分割異種インデックス、転置インデックスなど)
- ビジネスキャッシュの更新
- ビジネスロジックによる増分データ処理
現在の運河は、5.1.x、5.5.x、5.6.x、5.7.x、8.0.x を含むソース MySQL バージョンをサポートしています。
2.運河の予備原理
MySQL マスター/スレーブの原理
- MySQL マスターは、データの変更をバイナリ ログに書き込みます (バイナリ ログ。レコードはバイナリ ログ イベントと呼ばれ、show binlog events で表示できます)。
- MySQL スレーブは、マスターのバイナリ ログ イベントをリレー ログ (リレー ログ) にコピーします。
- MySQL スレーブはリレー ログ内のイベントを再生し、データの変更を自身のデータに反映します。
運河の原理→奴隷に変装する
- canal は、MySQL スレーブの対話プロトコルをシミュレートし、MySQL スレーブとして偽装し、ダンプ プロトコルを MySQL マスターに送信します。
- MySQL マスターはダンプ リクエストを受信し、バイナリ ログをスレーブ (つまり運河) にプッシュし始めます。
- canal はバイナリ ログ オブジェクト (元はバイト ストリーム) を解析します。
運河の応用展望
- canal は MySQL からデータを取得し、それを Redis に同期します。MySQL と Redis の間のデータの一貫性を確保し、遅延二重削除によって引き起こされるその他の問題を回避するために、遅延二重削除を実行する必要はありません。
2. MySQL コンテナが canal ユーザーを作成する
docker exec -it mysql_3306 bash
mysql -uroot -p
show VARIABLES like 'log_%';
create user 'canal'@'%' IDENTIFIED with mysql_native_password by 'canal';
grant SELECT, REPLICATION SLAVE, REPLICATION CLIENT on *.* to 'canal'@'%';
FLUSH PRIVILEGES;
3. 運河の画像をプルしてコンテナを作成します
1. 運河画像をクエリして取得します
docker pull canal/canal-server
root@hcss-ecs-52b8:~# docker pull canal/canal-server
Using default tag: latest
latest: Pulling from canal/canal-server
1c8f9aa56c90: Pull complete
c5e21c824d1c: Pull complete
4ba7edb60123: Pull complete
80d8e8fac1be: Pull complete
705a43657e98: Pull complete
28e38bfb6fe7: Pull complete
7d51a00deff6: Pull complete
4f4fb700ef54: Pull complete
Digest: sha256:0d1018759efd92ad331c7cc379afa766c8d943ef48ef8d208ade646f54bf1565
Status: Downloaded newer image for canal/canal-server:latest
docker.io/canal/canal-server:latest
2.運河コンテナを実行し、設定ファイルを取得します
その後のマウントと起動の準備をする
docker run --name canal -itd canal/canal-server
ドッカーラン
- -i: コンテナーを対話モードで実行します。
- -t: 疑似入力端子をコンテナに再割り当てします。
- –name: コンテナ名
- –privileged: コンテナーのパブリック権限を設定します (デフォルトは true)
- -p: マップされたポート Linux ポート: コンテナーの組み込みポート (mysql のデフォルトのポートは 3306)
- -v: Linux でマウントされたフォルダー/ファイルとコンテナー内のパスのマッピング
- -e: コンテナ環境変数 (mysql のデフォルトのユーザー名とパスワードを設定)
- -d: コンテナをバックグラウンドで実行し、コンテナ ID を返します。
docker exec -it canal bash
/home/admin/canal-server/conf/canal.properties
/home/admin/canal-server/conf/example/instance.properties
docker cp canal:/home/admin/canal-server/conf/canal.properties ./
docker cp canal:/home/admin/canal-server/conf/example/instance.properties ./
CanalのDockerコンテナから設定ファイルをコピーします。
設定ファイルのコピー結果
3. 運河設定ファイルを編集する
vim instance.properties
設定ファイルの編集に必要なパラメータ、mysql の内部 IP アドレス、binlog ファイル名、ログの場所
docker inspect mysql_3306 | grep IPA
show master status;
ポート番号の追加を忘れないように注意してください
MySQL データベースに接続するためのユーザー名とパスワードを変更します。
4. 以前の運河コンテナを削除し、マウント開始コンテナを作成します
docker stop canal
docker rm canal
docker run -itd --name canal \
-p 11111:11111 --privileged=true \
-v /usr/local/software/canal/conf/instance.properties:/home/admin/canal-server/conf/example/instance.properties \
-v /usr/local/software/canal/conf/canal.properties:/home/admin/canal-server/conf/example/canal.properties \
canal/canal-server
5. ログを表示する
docker logs canal
ログを確認してください。操作は成功しました。
6. 運河の港を開く
firewall-cmd --zone=public --add-port=11111/tcp --permanent
firewall-cmd --reload
firewall-cmd --zone=public --list-ports
ps: Huawei Cloud バックエンドでポートを開く必要があります
ヒント: サービスポートが開発されているかどうかを監視する方法
ncはネットキャットです。netcat は、TCP または UDP プロトコルを使用してネットワーク接続間でデータの読み取りおよび書き込みを行うシンプルな Unix ツールです。
これは、直接使用することも、他のプログラムやスクリプトから単に呼び出すこともできる、信頼性の高いバックエンド ツールとなるように設計されています。
同時に、必要なほぼすべての種類の接続を作成でき、いくつかの興味深い機能も組み込まれているため、機能が豊富なネットワーク デバッグおよび探索ツールでもあります。
Netcat には、接続モード、リスニング モード、トンネル モードという 3 つの機能モードがあります。
nc (netcat) コマンドの一般的な構文:
$ nc [-options] [HostName or IP] [PortNumber]
コマンドの詳細:
nc
: つまり、実行されたコマンドの主題。z
:ゼロ I/O モード (スキャンに使用);v
: 明示的に出力します。w3
: タイムアウトを 3 秒に設定します。192.168.1.8
: ターゲット システムの IP アドレス。22
: 検証する必要があるポート。
使用例
[root@localhost conf]# nc -zvw3 124.80.139.65 3927
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connection timed out.
[root@localhost conf]# nc -zvw3 124.80.139.65 3927
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connected to 124.80.139.65:3927.
Ncat: 0 bytes sent, 0 bytes received in 0.02 seconds.
4.スプリングブーツは運河を統合します
1.依存関係を導入する
<!-- canal管道-->
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client</artifactId>
<version>1.1.0</version>
</dependency>
2.公式Webサイトのケースコードを変換する
package com.woniu.fresh.config.redis;
import java.net.InetSocketAddress;
import java.util.List;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.common.utils.AddressUtils;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.CanalEntry.Column;
import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
import com.alibaba.otter.canal.protocol.CanalEntry.RowData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
/**
* 用canal管道监听MySQL数据变化,自动更新redis缓存
*/
@Slf4j
@Component
public class AutoUpdateRedis {
@Value("${canal.host}")
private String host;
@Value("${canal.port}")
private Integer port;
public void run() {
// 创建链接
final InetSocketAddress HOST = new InetSocketAddress(host,port);
// final InetSocketAddress HOST = new InetSocketAddress("192.168.111.130",11111);
CanalConnector connector = CanalConnectors.newSingleConnector(HOST, "example", "", "");
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");
} finally {
connector.disconnect();
}
}
private void printEntry(List<Entry> entrys) {
for (Entry entry : entrys) {
if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
continue;
}
RowChange rowChage = null;
try {
rowChage = RowChange.parseFrom(entry.getStoreValue());
} catch (Exception e) {
throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
e);
}
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 (RowData rowData : rowChage.getRowDatasList()) {
if (eventType == EventType.DELETE) {
printColumn(rowData.getBeforeColumnsList()); // 删除
} else if (eventType == EventType.INSERT) {
printColumn(rowData.getAfterColumnsList()); // 添加
} else {
// 修改
log.debug("-------修改之前before");
updateBefore(rowData.getBeforeColumnsList());
log.debug("-------修改之后after");
updateAfter(rowData.getAfterColumnsList());
}
}
}
}
private static void printColumn(List<Column> columns) {
for (Column column : columns) {
System.out.println(column.getName() + " : " + column.getValue() + " update=" + column.getUpdated());
}
}
/**
* 数据库更新之前
* @param columns
*/
@Autowired
private StringRedisTemplate stringRedisTemplate;
private void updateBefore(List<Column> columns) {
for (Column column : columns) {
System.out.println(column.getName() + " : " + column.getValue() + " update=" + column.getUpdated());
// // 如果数据更新,就更新缓存的数据
// if ("username".equals(column.getName())){
// // 把更新之前的数据删除
// stringRedisTemplate.opsForSet().remove("usernames", column.getValue());
// break;
// }
}
}
private void updateAfter(List<Column> columns) {
for (Column column : columns) {
System.out.println(column.getName() + " : " + column.getValue() + " update=" + column.getUpdated());
// // 如果数据更新,就更新缓存的数据
// if ("username".equals(column.getName()) && column.getUpdated()){
// // 把更新后的数据放入缓存
// stringRedisTemplate.opsForSet().add("usernames", column.getValue());
// break;
// }
}
}
}
3. メインスタートアップクラスが運河を開始します
package com.woniu.fresh;
import com.woniu.fresh.config.redis.AutoUpdateRedis;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.CommandLineRunner;
@SpringBootApplication
@Slf4j
public class FreshApp implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(FreshApp.class);
}
@Autowired
private AutoUpdateRedis autoUpdateRedis;
@Override
public void run(String... args) throws Exception {
log.debug(">>>>>启动缓存自动更新");
autoUpdateRedis.run();
}
}
4. canalを開始してデータベースを変更する
5. バックグラウンドで変化を監視する
要約する
1.Canal: MySQL データベースの増分ログ分析に基づいて、増分データのサブスクリプションと消費を提供します;
2. Canal の使用法、MySQL 構成、Docker Canal のインストール;
3. クラウド サーバーのオープン ポート、サーバー ポートが開いているかどうかをテストする方法;
4春の運河の予備施用。