順序再構築に関する簡単な説明: MySQL シャーディング

順序再構築に関する簡単な説明: MySQL シャーディング

1. 目的

この記事は次の目標を達成します:

  • サブテーブル数: 256 サブデータベース数: 4
  • ユーザーID (user_id) をデータベースシャーディングキーとして使用します
  • 最後に、user_id に基づいて、注文の作成、更新、削除、単一注文番号のクエリ、およびリストのクエリ操作をテストします。

アーキテクチャ図:

テーブル構造は次のとおりです。

テーブル「order_XXX」を作成します(
  `order_id` bigint(20) 符号なし NOT NULL,
  `user_id` int(11) デフォルト '0' コメント '注文ID',
  `status` int(11) デフォルト '0' コメント '注文ステータス',
  `booking_date` 日時 デフォルト NULL、
  `create_time` datetime デフォルト NULL、
  `update_time` 日時 デフォルト NULL、
  主キー (`order_id`)、
  キー `idx_user_id` (`user_id`)、
  キー `idx_bdate` (`booking_date`)、
  キー `idx_ctime` (`create_time`)、
  キー `idx_utime` (`update_time`)
)ENGINE=InnoDB デフォルト文字セット=utf8;

注: 000<= XXX <= 255。この記事では、データベースとテーブルのシャーディングの実践に焦点を当てています。代表的なフィールドのみが保持されます。これに基づいて、他のシナリオを改善できます。

世界的にユニークなIDデザイン

要件: 1. グローバルに一意であること 2: 大まかに順序付けられていること 3: 発信番号が可逆であること

  • 1ビット + 39ビットの時間差 + 8ビットのマシン番号 + 8ビットのユーザー番号(ライブラリ番号) + 8ビットの自動インクリメントシーケンス

注文番号の構成要素予約フィールドミリ秒の時間差マシンの数ユーザーID(テーブルID)自動増分シーケンス
占有バイト数(単位:ビット) 1 39 8 8 8

単一マシンの最大QPS: 256,000 耐用年数: 17年

2. 環境整備

1. 基本情報

アイテムバージョン述べる
スプリングブート2.1.10.リリース
マンゴー1.6.16 Wikiアドレス: https://github.com/jfaster/mango
ひかりCP 3.2.0
マイスク5.7 dockerワンクリックビルドを使用したテスト

2. データベース環境の準備

mysql を入力します:

#メインデータベース mysql -h 172.30.1.21 -uroot -pbyarch

#ライブラリから mysql -h 172.30.1.31 -uroot -pbyarch


コンテナに入る

# メイン docker exec -it db_1_master /bin/bash

#docker exec -it db_1_slave /bin/bashから


実行状態を確認する

#メイン docker exec db_1_master sh -c 'mysql -u root -pbyarch -e "SHOW MASTER STATUS \G"'
#docker から exec db_1_slave sh -c 'mysql -u root -pbyarch -e "SHOW SLAVE STATUS \G"'

3. データベースを構築し、サブテーブルをインポートする

(1)MySQLマスターインスタンスにデータベースを作成する

172.30.1.21(オーダーデータベース1)、172.30.1.22(オーダーデータベース2)、

172.30.1.23(オーダーデータベース3)、172.30.1.24(オーダーデータベース4)

(2)テーブルを作成するためのSQLコマンドを順番にインポートします。

ディレクトリは、mysql -uroot -pbyarch -h172.30.1.21 order_db_1<fast-cloud-mysql-sharding/doc/sql/order_db_1.sql;
mysql -uroot -pbyarch -h172.30.1.22 order_db_2<fast-cloud-mysql-sharding/doc/sql/order_db_2.sql;
ディレクトリは、mysql -uroot -pbyarch -h172.30.1.23 order_db_3<fast-cloud-mysql-sharding/doc/sql/order_db_3.sql;
ディレクトリは、mysql -uroot -pbyarch -h172.30.1.24 order_db_4<fast-cloud-mysql-sharding/doc/sql/order_db_4.sql;  

3. 構成と実践

1. pomファイル

     <!-- mango データベースおよびテーブル シャーディング ミドルウェア --> 
            <依存関係>
                <groupId>org.jfaster</groupId>
                <artifactId>マンゴースプリングブートスターター</artifactId>
                <バージョン>2.0.1</バージョン>
            </依存関係>
         
             <!-- 分散 ID ジェネレーター -->
            <依存関係>
                <グループID>com.byarch</グループID>
                <artifactId>高速クラウド ID ジェネレータ</artifactId>
                <バージョン>${バージョン}</バージョン>
            </依存関係>

            <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
            <依存関係>
                <グループID>mysql</グループID>
                <artifactId>mysql-コネクタ-java</artifactId>
                <バージョン>6.0.6</バージョン>
            </依存関係>

2. 一定の構成

パッケージ com.byarch.fast.cloud.mysql.sharding.common;

/**
 * データベースとテーブルシャーディング戦略の共通定数*/
パブリッククラスShardingStrategyConstant {
    /**
     * データベース論理名、実際のデータベース名は order_db_XXX です
     */
    パブリック静的最終文字列 LOGIC_ORDER_DATABASE_NAME = "order_db";
    /**
     * サブテーブルの数は256で、一度確定すると変更できません*/
    パブリック静的最終int SHARDING_TABLE_NUM = 256;

    /**
     * サブデータベースの数を変更することは推奨されません。変更することは可能ですが、DBA がデータを移行する必要があります。*/
    パブリック静的最終int SHARDING_DATABASE_NODE_NUM = 4;
}

3. yml設定

4 つのマスター データベース構成と 4 つのスレーブ データベース構成。ここでは、デフォルトの root ユーザー パスワードのみをテストします。実稼働環境では、root ユーザーの使用は推奨されません。

マンゴー:
  スキャンパッケージ: com.byarch.fast.cloud.mysql.sharding.dao
  データソース:
    - 名前: order_db_1
      マスター:
        ドライバークラス名: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://172.30.1.21:3306/order_db_1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false
        ユーザー名: root
        パスワード: bysearch
        最大プールサイズ: 10
        接続タイムアウト: 3000
      奴隷:
        - ドライバークラス名: com.mysql.cj.jdbc.Driver
          jdbc-url: jdbc:mysql://172.30.1.31:3306/order_db_1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false
          ユーザー名: root
          パスワード: bysearch
          最大プールサイズ: 10
          接続タイムアウト: 3000
    - 名前: order_db_2
      マスター:
        ドライバークラス名: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://172.30.1.22:3306/order_db_2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false
        ユーザー名: root
        パスワード: bysearch
        最大プールサイズ: 10
        接続タイムアウト: 3000
      奴隷:
        - ドライバークラス名: com.mysql.cj.jdbc.Driver
          jdbc-url: jdbc:mysql://172.30.1.32:3306/order_db_2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false
          ユーザー名: root
          パスワード: bysearch
          最大プールサイズ: 10
          接続タイムアウト: 3000
    - 名前: order_db_3
      マスター:
        ドライバークラス名: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://172.30.1.23:3306/order_db_3?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false
        ユーザー名: root
        パスワード: bysearch
        最大プールサイズ: 10
        接続タイムアウト: 3000
      奴隷:
        - ドライバークラス名: com.mysql.cj.jdbc.Driver
          jdbc-url: jdbc:mysql://172.30.1.33:3306/order_db_3?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false
          ユーザー名: root
          パスワード: bysearch
          最大プールサイズ: 10
          接続タイムアウト: 3000
    - 名前: order_db_4
      マスター:
        ドライバークラス名: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://172.30.1.24:3306/order_db_4?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false
        ユーザー名: root
        パスワード: bysearch
        最大プールサイズ: 10
        接続タイムアウト: 3000
      奴隷:
        - ドライバークラス名: com.mysql.cj.jdbc.Driver
          jdbc-url: jdbc:mysql://172.30.1.34:3306/order_db_4?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedState&connectTimeout=1000&socketTimeout=5000&useSSL=false
          ユーザー名: root
          パスワード: bysearch
          最大プールサイズ: 10
          接続タイムアウト: 300

4. データベースとテーブルのシャーディング戦略

1). order_idをshardKeyとして使用してデータベースとテーブルを分割する

パッケージ com.byarch.fast.cloud.mysql.sharding.strategy;

com.byarch.fast.cloud.mysql.sharding.common.ShardingStrategyConstant をインポートします。
com.byarch.id.generator.IdEntity をインポートします。
com.byarch.id.generator.SeqIdUtil をインポートします。
org.jfaster.mango.sharding.ShardingStrategy をインポートします。

/**
 * 注文番号サブライブラリとサブテーブル戦略*/
パブリッククラス OrderIdShardingStrategy は ShardingStrategy<Long, Long> を実装します {
    @オーバーライド
    パブリック文字列 getDataSourceFactoryName(Long orderId) {
        (注文ID == null || 注文ID < 0L)の場合 {
            新しい IllegalArgumentException をスローします ("order_id が無効です!");
        }
        IdEntity idEntity = SeqIdUtil.decodeId(orderId);
        idEntity.getExtraId() >= ShardingStrategyConstant.SHARDING_TABLE_NUM の場合 {
            throw new IllegalArgumentException("シャーディング テーブル Num が無効です。tableNum:" + idEntity.getExtraId());
        }
        //1. ステップの長さを計算します int step = ShardingStrategyConstant.SHARDING_TABLE_NUM / ShardingStrategyConstant.SHARDING_DATABASE_NODE_NUM;
        //2. ライブラリ番号を計算します long dbNo = Math.floorDiv(idEntity.getExtraId(), step) + 1;
        //3. データ ソース名を返します。 return String.format("%s_%s", ShardingStrategyConstant.LOGIC_ORDER_DATABASE_NAME, dbNo);
    }

    @オーバーライド
    パブリック String getTargetTable(String logicTableName, Long orderId) {
        (注文ID == null || 注文ID < 0L)の場合 {
            新しい IllegalArgumentException をスローします ("order_id が無効です!");
        }
        IdEntity idEntity = SeqIdUtil.decodeId(orderId);
        idEntity.getExtraId() >= ShardingStrategyConstant.SHARDING_TABLE_NUM の場合 {
            throw new IllegalArgumentException("シャーディング テーブル Num が無効です。tableNum:" + idEntity.getExtraId());
        }
        // 規則に基づいて、実際のテーブル名は logicTableName_XXX になります。XXX が 3 桁未満の場合は、0 を追加します。
        String.format("%s_%03d", logicTableName, idEntity.getExtraId()); を返します。
    }
}

2). user_idをshardKeyとして使用してデータベースとテーブルを分割する

パッケージ com.byarch.fast.cloud.mysql.sharding.strategy;

com.byarch.fast.cloud.mysql.sharding.common.ShardingStrategyConstant をインポートします。
org.jfaster.mango.sharding.ShardingStrategy をインポートします。

/**
 *シャーディング KEY とデータベース/テーブル シャーディング戦略を指定します*/
パブリッククラス UserIdShardingStrategy は ShardingStrategy<Integer, Integer> を実装します {

    @オーバーライド
    パブリック文字列 getDataSourceFactoryName(Integer userId) {
        //1. ステップの長さ、つまり単一のデータベース内のテーブル数を計算します。 int step = ShardingStrategyConstant.SHARDING_TABLE_NUM / ShardingStrategyConstant.SHARDING_DATABASE_NODE_NUM;
        //2. データベース番号を計算します。long dbNo = Math.floorDiv(userId % ShardingStrategyConstant.SHARDING_TABLE_NUM, step) + 1;
        //3. データ ソース名を返します。 return String.format("%s_%s", ShardingStrategyConstant.LOGIC_ORDER_DATABASE_NAME, dbNo);
    }

    @オーバーライド
    パブリック文字列 getTargetTable(文字列 logicTableName、整数 userId) {
        // 規則に基づいて、実際のテーブル名は logicTableName_XXX になります。XXX が 3 桁未満の場合は、0 を追加します。
        String.format("%s_%03d", logicTableName, userId % ShardingStrategyConstant.SHARDING_TABLE_NUM) を返します。
    }
}

5. 道層の書き込み

1). OrderPartitionByIdDao

パッケージ com.byarch.fast.cloud.mysql.sharding.dao;

com.byarch.fast.cloud.mysql.sharding.common.ShardingStrategyConstant をインポートします。
com.byarch.fast.cloud.mysql.sharding.pojo.entity.OrderEntity をインポートします。
com.byarch.fast.cloud.mysql.sharding.strategy.OrderIdShardingStrategy をインポートします。
org.jfaster.mango.annotation.* をインポートします。

@DB(名前 = ShardingStrategyConstant.LOGIC_ORDER_DATABASE_NAME、テーブル = "order")
@Sharding(シャーディング戦略 = OrderIdShardingStrategy.class)
パブリックインターフェース OrderPartitionByIdDao {

    @SQL("#table (order_id, user_id, status, booking_date, create_time, update_time) VALUES に INSERT" +
            「(:orderId,:userId,:status,:bookingDate,:createTime,:updateTime)」
    )
    int insertOrder(@TableShardingBy("orderId") @DatabaseShardingBy("orderId") OrderEntity の orderEntity);

    @SQL("UPDATE #table set update_time = now()" +
            "#if(:bookingDate != null),booking_date = :bookingDate #end " +
            "#if (:status != null)、ステータス = :status #end" +
            "WHERE order_id = :orderId"
    )
    OrderEntity を更新します。


    @SQL("SELECT * FROM #table WHERE order_id = :1")
    OrderEntity の OrderById を取得します (@TableShardingBy @DatabaseShardingBy の Long orderId)。

    @SQL("SELECT * FROM #table WHERE order_id = :1")
    @使用マスター
    OrderEntity をマスターから取得して OrderById を取得します (@TableShardingBy @DatabaseShardingBy の Long orderId)。

6. ユニットテスト

@SpringBootTest(クラス = {Application.class})
SpringJUnit4ClassRunner クラスで実行します。
パブリッククラスShardingTest {
    オートワイヤード
    パーティションをIdDaoで並べ替えます。

    オートワイヤード
    ユーザーID によるパーティションの順序付け。

    @テスト
    パブリック void testCreateOrderRandom() {
        (int i = 0; i < 20; i++) の場合 {
            int ユーザー ID = ThreadLocalRandom.current().nextInt(1000,1000000);
            OrderEntity は、新しい OrderEntity() です。
            orderEntity.setOrderId(SeqIdUtil.nextId(userId % ShardingStrategyConstant.SHARDING_TABLE_NUM));
            注文エンティティのステータスを設定します(1);
            orderEntity.setUserId(ユーザーID);
            orderEntity.setCreateTime(新しい日付());
            orderEntity.setUpdateTime(新しい日付());
            orderEntity.setBookingDate(新しい日付());
            戻り値: orderPartitionByIdDao.insertOrder(orderEntity);
            アサートします。
        }
    }

    @テスト
    パブリックボイドtestOrderAll() {
        //入れる
        int ユーザー ID = ThreadLocalRandom.current().nextInt(1000,1000000);
        OrderEntity は、新しい OrderEntity() です。
        orderEntity.setOrderId(SeqIdUtil.nextId(userId % ShardingStrategyConstant.SHARDING_TABLE_NUM));
        注文エンティティのステータスを設定します(1);
        orderEntity.setUserId(ユーザーID);
        orderEntity.setCreateTime(新しい日付());
        orderEntity.setUpdateTime(新しい日付());
        orderEntity.setBookingDate(新しい日付());
        orderPartitionByIdDao に orderEntity を挿入します。
        アサートします。

        //マスターから取得
        OrderEntity の orderInfo = orderPartitionByIdDao.getOrderByIdFromMaster(orderEntity.getOrderId());
        アサート。assertNotNull(orderInfo);
        OrderEntity が OrderInfo の getOrderId() を返す場合、OrderEntity は OrderId() を返します。

        //スレーブから取得
        OrderEntity のスレーブオーダー情報 = orderPartitionByIdDao.getOrderById(orderEntity.getOrderId());
        アサート。assertNotNull(スレーブオーダー情報)。
        //アップデート
        OrderEntity を更新します。
        Entity を更新します。
        エンティティを更新します。
        エンティティを更新します。
        int 影響行 = orderPartitionByIdDao.updateOrderByOrderId(updateEntity);
        Assert.assertTrue(affectRows > 0);
    }

    @テスト
    パブリック void testGetListByUserId() {
        int ユーザー ID = ThreadLocalRandom.current().nextInt(1000,1000000);
        (int i = 0; i < 5; i++) の場合 {
            OrderEntity は、新しい OrderEntity() です。
            orderEntity.setOrderId(SeqIdUtil.nextId(userId % ShardingStrategyConstant.SHARDING_TABLE_NUM));
            注文エンティティのステータスを設定します(1);
            orderEntity.setUserId(ユーザーID);
            orderEntity.setCreateTime(新しい日付());
            orderEntity.setUpdateTime(新しい日付());
            orderEntity.setBookingDate(新しい日付());
            orderPartitionByIdDao.insertOrder(orderEntity);
        }
        試す {
            //マスタースレーブ遅延による検証エラーを防ぐ Thread.sleep(1000);
        } キャッチ (InterruptedException e) {
            e.printStackTrace();
        }
        リスト<OrderEntity> orderListByUserId = orderPartitionByUserIdDao.getOrderListByUserId(userId);
        アサート。assertNotNull(orderListByUserId);
        アサートします。assertTrue(orderListByUserId.size() == 5);
    }
}

完了です:

IV. 結論

この記事では、Java 版の Mango フレームワークを使用した MySQL シャーディングの実践的な実装を主に紹介します。シャーディング ミドルウェアは ShardingJDBC に類似したものを使用することも、独自に開発することもできます。

上記のサブデータベースとサブテーブルの数は、デモンストレーションの参考用です。実際の作業では、サブテーブルとサブデータベースの数は、企業の実際のビジネスデータの増加率、ピーク QPS、物理マシン構成などの要素に基づいて計算されます。

これで、順序再構築における MySQL シャーディングの実践的な適用に関するこの記事は終了です。MySQL シャーディングの詳細については、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • MySQL シャーディング入門ガイド
  • MySQL シャーディングの詳細
  • MySQL でよく使用されるデータベースとテーブル シャーディング ソリューションの概要
  • MySQLデータベースシャーディングとテーブルシャーディングが完全に崩壊
  • MySQLデータベースとテーブルシャーディング後の主キー処理のいくつかの方法
  • SpringBoot+MybatisPlus+Mysql+Sharding-JDBC シャーディング
  • MySQLデータベースとテーブルを分割するいくつかの方法

<<:  Nodejs のグローバル変数とグローバルオブジェクトの知識ポイントと使用方法の詳細

>>:  テキストエリアをレイアウトしたときにテキストが左下にあり、サイズを変更できない問題の解決策

推薦する

MySql COALESCE 関数の使用コード例

COALESCE は、各パラメータ式 (expression_1、expression_2、...、...

Nginx キャッシュ設定例

Web アプリケーションの開発とデバッグを行う際には、テストのためにブラウザのキャッシュをクリアした...

CSS calc() の数式に関する詳細な理解

数式 calc() は CSS の関数であり、主に数学演算に使用されます。 calc() を使用する...

Vueでタイマーをエレガントにクリアする方法

目次序文最適化派生的な質問: beforeDestroy はトリガーされませんか?序文タイマーをクリ...

Node.js で MySQL データベースにバッチデータを挿入する方法

プロジェクト(nodejs)では、一度に複数のデータをデータベースに挿入する必要があります。データベ...

Nginx で WordPress を設定する方法

以前、私は自分で WordPress を構築していましたが、当時はサードパーティの仮想ホストを使用し...

要素 ui の el-table の列にさまざまなスタイルのデータを動的に実装する例

問題の説明Ele.me UI のフレームワークでは、入力データは el-form であり、出力データ...

MySQL実行計画の詳細な分析

序文前回の面接では、実行計画について質問されたとき、多くの人がそれが何なのか知りませんでした。実行計...

TypeScript の条件型に関する詳細な読書と実践記録

目次ジェネリック型での条件型の使用ツールタイプ脱出ポッド矢印関数で条件型を使用する型推論による条件型...

Jenkins の docker-compose デプロイメントと構成に関する詳細なチュートリアル

Docker-compose デプロイメント構成 Jenkins 1. Docker-compose...

HTMLがHikvisionカメラのリアルタイム監視機能を実現

最近、同社は CCFA 関連のいくつかの作業を行う予定で、その 1 つはカメラのリアルタイム監視を再...

要素の読み込み効果を実現するための純粋なHTML+CSS

これは Element UI の読み込みコンポーネントのエフェクトです。かっこいいですね。実装してみ...

Docker-compose ネットワークの詳細な例

今日は Docker でのネットワーク設定を試し、後で忘れないようにプロセスを記録しました。 (シス...

hrefパラメータ転送における中国語の文字化けについて

パラメータを渡すために href が必要で、パラメータが中国語の場合、文字化けした文字が表示されます...