09 Redis と MySQL データの二重書き込み整合性プロジェクトのランディング ケース

運河

とは

  • canal [kə'næl] は中国語で水路/パイプライン/溝/運河として翻訳され、主に MySQL データベースの増分ログ データのサブスクリプション、消費、分析に使用されます。Alibaba によって開発およびオープンソースであり、Java 言語で開発されています。 ;
  • 歴史的な背景としては、アリババの初期に杭州と米国に二重コンピューター室を展開したことにより、コンピューター室間のデータ同期がビジネス上必要となり、その実装方法は主にビジネストリガーに基づいていました(トリガー) を使用して増分変更を取得します。2010 年以来、アリババは徐々にデータベース ログの解析を使用して同期のための増分変更を取得することを試み、その結果運河プロジェクトが生まれました。
  • Canal は、MySQL 変更ログの増分サブスクリプションと消費に基づくコンポーネントです

何ができますか

  • データベースミラーリング
  • データベースのリアルタイムバックアップ
  • インデックスの構築とリアルタイムのメンテナンス (分割異種インデックス、転置インデックスなど)
  • ビジネスキャッシュの更新
  • ビジネスロジックによる増分データ処理

どこへ行く

動作原理

従来の MySQL マスター/スレーブ レプリケーションの動作原理

ここに画像の説明を挿入
MySQL のマスター/スレーブ レプリケーションは次の手順を実行します。

  1. マスターマスターサーバー上のデータが変更されると、その変更はバイナリイベントログファイルに書き込まれます。
  2. スレーブサーバーは、一定時間内にマスターマスターサーバー上のバイナリログを検出し、変更があったかどうかを検出し、マスターマスターサーバーのバイナリイベントログが変更されたことを検出すると、I/Oスレッドを開始します。マスターバイナリイベントログをリクエストします。
  3. 同時に、マスターサーバーは各 I/O スレッドに対してダンプ スレッドを開始し、バイナリ イベント ログを送信します。
  4. スレーブは、サーバーから受信したバイナリ イベント ログを独自のローカル リレー ログ ファイルに保存します。
  5. スレーブ サーバーは SQL スレッドを開始してリレー ログからバイナリ ログを読み取り、それをローカルで再生してデータをマスター サーバーと一致させます。
  6. 最後に、I/O スレッドと SQL スレッドはスリープ状態になり、次のウェイクアップを待ちます。

運河の仕組み

ここに画像の説明を挿入

  • canal は MySQL スレーブのインタラクティブ プロトコルをシミュレートし、MySQL スレーブのふりをして、MySQL マスターにダンプ プロトコルを送信します。
  • MySQL マスターはダンプ リクエストを受け取り、バイナリ ログをスレーブ (つまり運河) にプッシュし始めます。運河はバイナリ ログ オブジェクト (元はバイト ストリーム) を解析します。
  • 分散システムには最終整合性しかなく、強整合性を実現するのは困難

mysql-canal-redis 二重書き込みの整合性

mysql

CREATE TABLE `t_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `userName` varchar(100) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4
  • MySQLのバイナリログ書き込み機能を有効にする
  • canal に MySQL アカウントへの接続を許可する
    • mysql のデフォルトのユーザーは、mysql ライブラリのユーザー テーブルにあります
    • デフォルトでは運河アカウントはありません。ここで新規作成 + 認証します

運河サーバー

  • ダウンロード
  • 解凍後、/mycanalパスに丸ごと入れます。
  • 構成の変更
    • /mycanal/canal.deployer-1.1.5/conf/example パス

    • ここに画像の説明を挿入

    • インスタンスのプロパティ

    • ここに画像の説明を挿入

    • mysql で独自の新しい canal アカウントに置き換えます

    • ここに画像の説明を挿入

  • 起動
    • /mycanal/canal.deployer-1.1.5/bin パスで実行します。
    • ./startup.sh
  • サーバーログの表示
  • ここに画像の説明を挿入
  • インスタンスのログを表示する
  • ここに画像の説明を挿入

canalクライアント(Javaで書かれた業務プログラム)

  • ビルドモジュール

  • ポンポンを変更する

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.5.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.zzyy.study</groupId>
        <artifactId>canal_demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>canal_demo</name>
        <description>Demo project for Spring Boot</description>
    
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <junit.version>4.12</junit.version>
            <log4j.version>1.2.17</log4j.version>
            <lombok.version>1.16.18</lombok.version>
            <mysql.version>5.1.47</mysql.version>
            <druid.version>1.1.16</druid.version>
            <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>com.alibaba.otter</groupId>
                <artifactId>canal.client</artifactId>
                <version>1.1.0</version>
            </dependency>
            <!--guava-->
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>23.0</version>
            </dependency>
            <!--web+actuator-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
            <!--SpringBoot与Redis整合依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-pool2</artifactId>
            </dependency>
            <!-- jedis -->
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>3.1.0</version>
            </dependency>
            <!-- springboot-aop 技术-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <!-- redisson -->
            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson</artifactId>
                <version>3.13.4</version>
            </dependency>
            <!--Mysql数据库驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
            </dependency>
            <!--集成druid连接池-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.1.10</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <!--mybatis和springboot整合-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.spring.boot.version}</version>
            </dependency>
            <!-- 添加springboot对amqp的支持 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-amqp</artifactId>
            </dependency>
            <!--通用基础配置-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>${log4j.version}</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>RELEASE</version>
                <scope>compile</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.73</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
  • YMLを書く

    server.port=5555
    
  • メインブート

  • ビジネスクラス

    • Redisユーティリティ
      package com.zzyy.study.util;
      
      import redis.clients.jedis.Jedis;
      import redis.clients.jedis.JedisPool;
      import redis.clients.jedis.JedisPoolConfig;
      
      /**
       * @auther zzyy
       * @create 2020-10-11 14:33
       */
      public class RedisUtils
      {
              
              
          public static JedisPool jedisPool;
      
          static {
              
              
              JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
              jedisPoolConfig.setMaxTotal(20);
              jedisPoolConfig.setMaxIdle(10);
              jedisPool=new JedisPool(jedisPoolConfig,"192.168.111.147",6379);
          }
      
          public static Jedis getJedis() throws Exception {
              
              
              if(null!=jedisPool){
              
              
                  return jedisPool.getResource();
              }
              throw new Exception("Jedispool is not ok");
          }
      
      
          /*public static void main(String[] args) throws Exception
          {
              try(Jedis jedis = RedisUtils.getJedis())
              {
                  System.out.println(jedis);
      
                  jedis.set("k1","xxx2");
                  String result = jedis.get("k1");
                  System.out.println("-----result: "+result);
                  System.out.println(RedisUtils.jedisPool.getNumActive());//1
              }catch (Exception e){
                  e.printStackTrace();
              }
          }*/
      }
      
    • RedisCanalClientの例
       
      package com.zzyy.study.t1;
      
      import com.alibaba.fastjson.JSONObject;
      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.zzyy.study.util.RedisUtils;
      import org.springframework.beans.factory.annotation.Autowired;
      import redis.clients.jedis.Jedis;
      
      import java.net.InetSocketAddress;
      import java.util.List;
      import java.util.concurrent.TimeUnit;
      
      /**
       * @auther zzyy
       * @create 2020-11-11 17:13
       */
      public class RedisCanalClientExample
      {
              
              
      
          public static final Integer _60SECONDS = 60;
      
          public static void main(String args[]) {
              
              
      
              // 创建链接canal服务端
              CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("192.168.111.147",
                      11111), "example", "", "");
              int batchSize = 1000;
              int emptyCount = 0;
              try {
              
              
                  connector.connect();
                  //connector.subscribe(".*\\..*");
                  connector.subscribe("db2020.t_user");
                  connector.rollback();
                  int totalEmptyCount = 10 * _60SECONDS;
                  while (emptyCount < totalEmptyCount) {
              
              
                      Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
                      long batchId = message.getId();
                      int size = message.getEntries().size();
                      if (batchId == -1 || size == 0) {
              
              
                          emptyCount++;
                          try {
              
               TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {
              
               e.printStackTrace(); }
                      } else {
              
              
                          emptyCount = 0;
                          printEntry(message.getEntries());
                      }
                      connector.ack(batchId); // 提交确认
                      // connector.rollback(batchId); // 处理失败, 回滚数据
                  }
                  System.out.println("empty too many times, exit");
              } finally {
              
              
                  connector.disconnect();
              }
          }
      
          private static 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("================&gt; 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.INSERT) {
              
              
                          redisInsert(rowData.getAfterColumnsList());
                      } else if (eventType == EventType.DELETE) {
              
              
                          redisDelete(rowData.getBeforeColumnsList());
                      } else {
              
              //EventType.UPDATE
                          redisUpdate(rowData.getAfterColumnsList());
                      }
                  }
              }
          }
      
          private static void redisInsert(List<Column> columns)
          {
              
              
              JSONObject jsonObject = new JSONObject();
              for (Column column : columns)
              {
              
              
                  System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
                  jsonObject.put(column.getName(),column.getValue());
              }
              if(columns.size() > 0)
              {
              
              
                  try(Jedis jedis = RedisUtils.getJedis())
                  {
              
              
                      jedis.set(columns.get(0).getValue(),jsonObject.toJSONString());
                  }catch (Exception e){
              
              
                      e.printStackTrace();
                  }
              }
          }
      
          private static void redisDelete(List<Column> columns)
          {
              
              
              JSONObject jsonObject = new JSONObject();
              for (Column column : columns)
              {
              
              
                  jsonObject.put(column.getName(),column.getValue());
              }
              if(columns.size() > 0)
              {
              
              
                  try(Jedis jedis = RedisUtils.getJedis())
                  {
              
              
                      jedis.del(columns.get(0).getValue());
                  }catch (Exception e){
              
              
                      e.printStackTrace();
                  }
              }
          }
      
          private static void redisUpdate(List<Column> columns)
          {
              
              
              JSONObject jsonObject = new JSONObject();
              for (Column column : columns)
              {
              
              
                  System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
                  jsonObject.put(column.getName(),column.getValue());
              }
              if(columns.size() > 0)
              {
              
              
                  try(Jedis jedis = RedisUtils.getJedis())
                  {
              
              
                      jedis.set(columns.get(0).getValue(),jsonObject.toJSONString());
                      System.out.println("---------update after: "+jedis.get(columns.get(0).getValue()));
                  }catch (Exception e){
              
              
                      e.printStackTrace();
                  }
              }
          }
      
      
      }
      

おすすめ

転載: blog.csdn.net/m0_56709616/article/details/131033196