MySQL シャーディングの詳細

MySQL シャーディングの詳細

1. ビジネスシナリオの紹介

MySQLを使用する電子商取引システムがあるとします。大量のデータを保存でき、同時実行性が高く、拡張性に優れたソリューションを設計する必要があります。データベースにはユーザー テーブルがあります。ユーザー数が多く、高いスケーラビリティを実現する必要があります。どのように設計しますか?まず、従来のシャーディング方法を見てみましょう

もちろん、州/地域や特定のビジネス関係に応じてデータベースを分割する方法を知っている友人もいます。

さて、ここでの疑問は、データが異なるライブラリの異なるテーブルに保存されるようにするにはどうすればよいかということです。ライブラリで同時実行の圧力を軽減しますか?サブライブラリとテーブル分割のルールをどのように定式化すればよいでしょうか?心配しないでください、もうすぐ来ます

2. 水平データベースとテーブル分割方式

1.範囲

最初の方法は、次の図に示すように、100万に対して1つのテーブルを使用して、1から1000000、1000001-2000000などのデータ範囲を指定してテーブルを分割することです。

ここに画像の説明を挿入 もちろん、この方法では、特に分散環境ではテーブル ID を維持する必要があります。この分散 ID については、サードパーティのテーブル シャーディング ツールを使用せずにRedis使用することをお勧めします。Redis Redis incr操作により、分散テーブル ID を簡単に維持できます。

RANGE方式の利点:拡張が簡単で、事前にデータベースとテーブルを構築するだけ

RANGE 方式の欠点:ほとんどの読み取りと書き込みは新しいデータにアクセスするため、IO ボトルネックが発生します。これにより、新しいデータベースに過度の負荷がかかるため、推奨されません。

2. ハッシュ係数

RANGEテーブル パーティショニングの IO ボトルネック問題を解決するには、図に示すように、ユーザーID HASGの係数を使用してデータベースとテーブルをパーティション分割する方法を使用できます。

このようにして、データをさまざまなデータベースやテーブルに分散することができ、IO ボトルネックの問題を回避できます。

HASHモジュラス方式の利点:データが異なるデータベースやテーブルに均等に分散され、データベースの負荷が軽減される。

HASHモジュール方式のデメリット:拡張が難しく、データを移行するたびにハッシュ値を再計算して異なるデータベースやテーブルに割り当てる必要がある

3. 一貫性のあるハッシュ

HASH によるモジュロは最も完璧な方法ではありませんが、それは何でしょうか?

一貫性のあるHASHアルゴリズムを使用すると、問題を完全に解決できます。

一般的なHASHアルゴリズム:

一般的なハッシュ アルゴリズムは、任意の長さのバイナリ値を、固定長の短いバイナリ値にマッピングします。この小さなバイナリ値はハッシュ値と呼ばれます。ハッシュ値は、データの一意かつ非常にコンパクトな数値表現です。

分散アプリケーションにおける通常のhashアルゴリズムの欠点:分散ストレージ システムでは、データを特定のノードに保存する必要があります。ルーティングに通常のhashアルゴリズムを使用し、データを特定のノードにマップする場合 (例: key%nkeyはデータのkey 、n はマシン ノードの数)。マシンがクラスターに参加またはクラスターから離脱すると、すべてのデータ マッピングが無効になります。永続ストレージの場合は、データの移行を行う必要があります。分散キャッシュの場合は、他のキャッシュが無効になります。

一貫性のあるハッシュ アルゴリズム:一般的に使用されるhashアルゴリズムを使用して、対応するキーを 2^32 ノードの空間、つまり 0 から (2^32)-1 までのデジタル空間にハッシュします。ここで、これらの数字を端から端まで接続し、下の図に示すように閉じた円として想像することができます。

このリングは端から端まで接続されています。データベース サーバー ノードが 3 つあるとします。 node1node2node3です。各ノードは、ユーザー データの独自の部分を保存する役割を担っています。ユーザーは user1、user2、user3 であるとします。サーバー ノードで HASH 操作を実行できます。HASH 計算後、user1 はnode1に、 user2node2に、user3 は user3 に該当するとします。

さて、ここでノード3に障害が発生したと仮定しましょう。

user3ノード1に着陸し、ノード1とノード2の以前のデータは変更されません。ノード4が追加されたと仮定します。

user3 は node4 に落ちることがわかります。ノードの追加と削除の分析を通じて、一貫性のあるハッシュ アルゴリズムは単調性を維持しながらデータ移行を最小限に抑えることができることがわかります。このようなアルゴリズムは分散クラスターに非常に適しており、大量のデータ移行を回避し、サーバーの負荷を軽減します。

もちろん、まだ解決しなければならない問題が 1 つあります。それはバランスです。図から、サーバー ノードが比較的少ない場合、大量のデータが必然的に 1 つのノードに集中し、別のノードにはほとんどデータが集中しないという問題が発生することがわかります。

このデータスキュー問題を解決するために、コンシステントハッシュアルゴリズムは仮想ノードメカニズムを導入します。このメカニズムは、各サービスノードに対して複数のハッシュを計算し、各計算結果の場所に仮想ノードと呼ばれるノードを配置します。具体的なアプローチとしては、まず各物理ノードに関連付けられている仮想ノードの数を決定し、次に IP またはホスト名の後に数字を追加します。例えば、上記の場合、各サーバーごとに3つの仮想ノードを計算できるため、「 node 1-1 」、「 node 1-2 」、「 node 1-3 node 2-1 」、「 node 2-2 」、「 node 2-3 、「ノード3-1」、「 node 3-1 node 3-2 、「 node 3-3 」のハッシュ値をそれぞれ計算でき、9つの仮想ノードを形成できます。

たとえば、user1 はnode 1-1node 1-2node 1-3に配置されていますが、これらはすべてnode1に配置されています。これにより、サービス ノードが少ない場合にデータ スキューの問題を解決できます。もちろん、仮想ノードの数は 3 つ、または最大でも 3 つ以上で固定されているわけではありません。これは単なる例です。具体的な仮想ノードの数は、実際のビジネス状況に応じて決定する必要があります。

一貫性のある HASH 方式の利点:仮想ノードにより、データが異なるデータベースやテーブルに均等に分散され、ノードの追加や削除が他のノードのデータに影響を与えないことが保証され、高い可用性と強力な災害耐性が実現します。

コンシステント・モジュラス法のデメリット:まあ、上記2つに比べれば、無いと言ってもいいでしょう。

3. ユニットテスト

さて、もうナンセンスは終わりにして、次はユニットテストです。3つのノードがあり、各ノードには3つの仮想ノードがあると仮定します。

パッケージ com.hyh.core.test;

com.hyh.utils.common.StringUtils をインポートします。
org.junit.Test をインポートします。

java.util.LinkedList をインポートします。
java.util.List をインポートします。
java.util.SortedMap をインポートします。
java.util.TreeMap をインポートします。

/**
 * 一貫性ハッシュテスト
 *
 * @著者 heyuhua
 * @create 2021/1/31 19:50
 */
パブリッククラスConsistentHashTest {

    //ハッシュリングに追加するサーバーのリスト private static String[] servers = {"192.168.5.1", "192.168.5.2", "192.168.5.3"};

    // 実際のノード リスト。サーバーがオンラインになったりオフラインになったりするシナリオ、つまり追加と削除のシナリオがより頻繁に発生することを考慮すると、ここでは LinkedList を使用する方が適切です。 private static List<String> realNodes = new LinkedList<>();

    //仮想ノード、キーは仮想ノードのハッシュ値を表し、値は仮想ノードの名前を表します private static SortedMap<Integer, String> virtualNodes = new TreeMap<>();

    // 1 つの実際のノードは 3 つの仮想ノードに対応します private static final int VIRTUAL_NODES = 3;

    /**
     * 仮想ノードでHASHの一貫性をテストする
     */
    @テスト
    パブリックボイドtestConsistentHash() {
        初期化ノード();
        文字列[] ユーザー = {"user1", "user2", "user3", "user4", "user5", "user6", "user7", "user8", "user9"};
        (int i = 0; i < users.length; i++) の場合
            System.out.println("[" + users[i] + "]のハッシュ値は" +
                    getHash(users[i]) + "、node[" + getServer(users[i]) + "]" にルーティングされます。
    }

    /**
     * まず元のサーバーを実ノードリストに追加します */
    パブリック void initNodes() {
        (int i = 0; i < servers.length; i++) の場合
            realNodes.add(サーバ[i]);
        (文字列str:実ノード) {
            (int i = 0; i < VIRTUAL_NODES; i++) の場合 {
                文字列 virtualNodeName = str + "-仮想ノード" + String.valueOf(i);
                int ハッシュ = getHash(仮想ノード名);
                System.out.println("仮想ノード [" + virtualNodeName + "] が追加されました。ハッシュ値は " + hash です);
                仮想ノードをハッシュに格納します。
            }
        }
        システム出力のprintln();
    }

    // FNV1_32_HASH アルゴリズムを使用してサーバーのハッシュ値を計算します。ここでは hashCode を書き換える方法は使用されず、最終的な効果は変わりません private static int getHash(String str) {
        最終的な int p = 16777619;
        整数ハッシュ = (int) 2166136261L;
        (int i = 0; i < str.length(); i++) の場合
            ハッシュ = (ハッシュ^str.charAt(i)) * p;
        ハッシュ += ハッシュ << 13;
        ハッシュ ^= ハッシュ >> 7;
        ハッシュ += ハッシュ << 3;
        ハッシュ ^= ハッシュ >> 17;
        ハッシュ += ハッシュ << 5;

        // 計算値が負の場合、その絶対値を取得します if (hash < 0)
            ハッシュ = Math.abs(ハッシュ);
        ハッシュを返します。
    }

    //ルーティング先のノードを取得する private static String getServer(String key) {
        //キーのハッシュ値を取得します。 int hash = getHash(key);
        // ハッシュ値より大きいすべてのマップを取得します
        SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash);
        文字列仮想ノード;
        サブマップが空の場合(){
            //キーより大きいハッシュ値がない場合は、最初のノードから開始します。Integer i = virtualNodes.firstKey();
            //対応するサーバーを返します。virtualNode = virtualNodes.get(i);
        } それ以外 {
            //最初のキーは時計回り方向にノードに最も近いノードです。Integer i = subMap.firstKey();
            //対応するサーバーを返します。virtualNode = subMap.get(i);
        }
        //virtualNode 仮想ノード名をインターセプトする必要があります if (StringUtils.isNotBlank(virtualNode)) {
            virtualNode.substring(0, virtualNode.indexOf("-")) を返します。
        }
        null を返します。
    }
}

ここでは、9つのユーザーオブジェクトがハッシュされた後にルーティングされる状況をシミュレートします。結果を見る

要約:

分散マイクロサービス アーキテクチャ環境では、シャーディングに一貫性のあるHASHアルゴリズムを使用することを強くお勧めします。もちろん、分散環境ではビジネス データの一貫性と分散トランザクションの問題も発生します。次回は、データの一貫性と分散トランザクションのソリューションについて説明します。

MySQLシャーディングの詳細に関するこの記事はこれで終わりです。MySQL シャーディングの詳細については、 MySQLの以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

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

<<:  フローチャートとUIフローの違い

>>:  行間隔が広い場合の解決策(IE では 5 ピクセル多い)

推薦する

ネイティブ JS カプセル化 vue タブ切り替え効果

この記事の例では、ネイティブJSカプセル化vueタブ切り替えの具体的なコードを参考までに共有していま...

Divの境界と透明度に関する設定

フレーム:スタイル=”border-style:solid;border-width:5px;bor...

nginx と keepalived を組み合わせて高可用性を実現するための手順を完了する

序文システムの高可用性を満たすためには、通常、クラスターを構築する必要があります。ホストがクラッシュ...

HTML テーブルレイアウト例の説明

HTML ドキュメント内の要素は次々に配置され、ブロックレベル要素の前後に改行が追加されるだけで、合...

Centos7.5 構成 Java 環境のインストール Tomcat の説明

Tomcat は Java 言語をベースにした Web サーバー ソフトウェアです。この記事では主に...

js と jQuery での Ajax の使用例の詳細

目次ネイティブJS GETリクエストの送信方法投稿リクエストの送信方法パラメータ付きのGETリクエス...

Tomcat の設定と Eclipse での起動方法

目次Tomcat8のインストールと設定方法tomcat ダウンロードTomcat マネージャーを有効...

Windows サービス 2016 Datacenter\Stand\Embedded アクティベーション方法 (2021)

管理者権限でcmdを実行する slmgr /ipk CB7KF-BWN84-R7R2Y-793K2-...

MySQL の不正な文字列値の解決方法

MySQL を使用して中国語の文字を挿入すると、多くの友人から次のエラーが報告されます。 これは、文...

redhat7 に yum 経由で mysql5.7.17 をインストールするチュートリアル

RHEL/CentOS シリーズの Linux オペレーティング システムには MySQL ソース自...

nginx + fastcgi を使用して画像認識サーバーを実装する

背景ディープラーニング モデルの推論には、特定のデバイスが使用されます。マシンは、モデルの読み込み、...

フロントエンドの vue+express ファイルのアップロードとダウンロードの例

新しいserver.jsを作成する糸初期化 -y 糸を追加エクスプレスノードモン -D var ex...

Vue3 の動的コンポーネントはどのように機能しますか?

目次1. コンポーネントの登録1.1 グローバル登録1.2 グローバルコンポーネントの登録プロセス1...

MySQLのインデックスシステムがB+ツリーを使用する理由の分析

目次1. インデックスとは何ですか? 2. インデックスはなぜ必要なのでしょうか? 3. インデック...