サブライブラリとサブテーブルについてそれらのことについて話す

この記事は読者に適しています。単一のデータベースと単一のテーブルから複数のデータベースと複数のテーブルに変換する必要がある初心者です。

この記事では、主に、サブライブラリとサブメーターの変換のプロセスで考慮する必要のある要素、対応するソリューション、および踏まれたピットについて説明します。

I.はじめに

サブデータベースとサブテーブルが必要なので、何かをする動機が常にあるはずです。したがって、それを行う前に、まず次の2つの質問を理解する必要があります。

1サブデータベースとサブテーブルとは何ですか?

実際、それは文字通りで理解しやすいです:

  • サブデータベース:単一のデータベースを複数のデータベースに分割するプロセスであり、データは複数のデータベースに分散されます。
  • テーブルの分割:単一のテーブルを複数のテーブルに分割するプロセスであり、データは複数のテーブルに分散されます。

2なぜサブデータベースとサブテーブルが必要なのですか?

キーワード:パフォーマンスの向上、使いやすさの向上。

パフォーマンスの観点から

単一のデータベース内のデータ量が大きくなり、データベースのクエリQPSが高くなるにつれて、それに応じて、データベースの読み取りと書き込みに必要な時間がますます長くなります。データベースの読み取りと書き込みのパフォーマンスは、ビジネス開発のボトルネックになる可能性があります。同様に、データベースのパフォーマンスを最適化する必要があります。この記事では、データベースレベルでの最適化についてのみ説明し、キャッシュなどのアプリケーションレベルの最適化方法については説明しません。

データベースのクエリQPSが高すぎる場合は、データベースを分割し、データベースを分割して単一のデータベースの接続圧力を共有することを検討する必要があります。たとえば、クエリQPSが3500の場合、1つのデータベースで1000の接続をサポートできると仮定すると、クエリの接続圧力を分散させるために、それを4つのデータベースに分割することを検討できます。

1つのテーブルのデータ量が多すぎる場合、データクエリまたはデータ更新のいずれの場合でも、データ量が特定のレベルを超えると、インデックス最適化などの純粋なデータベースレベルでの従来の最適化方法の後に、まだパフォーマンスの問題です。これが量的変化による質的変化ですが、現時点では、データ生成やデータ処理のソースから問題を解決するなど、問題を解決するための考え方を変える必要があります。データ量が多いので、分割して征服し、それをゼロに分割します。これにより、テーブルが分割され、特定のルールに従ってデータが複数のテーブルに分割され、単一のテーブル環境では解決できないアクセスパフォーマンスの問題が解決されます。

使いやすさの観点から

単一のデータベースで事故が発生した場合、すべてのデータが失われる可能性が非常に高くなります。特にクラウド時代では、多くのデータベースが仮想マシン上で稼働しており、仮想マシン/ホストマシンに事故が発生した場合、取り返しのつかない損失が発生する可能性があります。したがって、信頼性の問題を解決するための従来のマスタースレーブ、マスターマスター、およびその他の展開レベルに加えて、データ分割レベルからこの問題を解決することも検討できます。

ここでは、例としてデータベースのダウンタイムを取り上げます。

  • 単一データベース展開の場合、データベースがダウンしていると、障害の影響は100%であり、回復に時間がかかる場合があります。
  • 2つのライブラリに分割してそれらを異なるマシンにデプロイし、ライブラリの1つがダウンした場合、障害の影響は50%になり、データの50%は引き続き提供されます。
  • 4つのライブラリに分割し、それらを異なるマシンにデプロイし、ライブラリの1つがダウンした場合、障害の影響は25%になり、データの75%は引き続き提供され、回復時間は非常に長くなります。短い。。

もちろん、ライブラリを無制限に解体することはできません。これは、ストレージリソースを犠牲にして、パフォーマンスと可用性を向上させる方法でもあります。結局のところ、リソースは常に制限されています。

データベースとテーブルを分割する方法

1サブライブラリ?サブテーブル?それとも、サブデータベースとサブテーブルの両方ですか?

最初の部分で学習した情報から判断すると、サブデータベースサブテーブルスキームは、次の3つのタイプに分類できます。

2独自のセグメンテーションプランを選択するにはどうすればよいですか?

テーブルを分割する必要がある場合、適切なテーブルはいくつありますか?

すべてのテクノロジーがビジネスに役立つため、最初にデータの観点からビジネスの背景を確認します。

たとえば、当社のビジネスシステムは、XSpaceカスタマーサービスプラットフォームシステムを通じて会員の相談要求を解決し、会員にサービスを提供するように設計されています。現在、当社は主に同期されたオフライン作業指示データをデータソースとして使用して独自のデータを構築しています。

次の場合、各オフラインチケットが対応するメンバーのコンサルティング質問(私たちは質問シートと呼びます)を生成すると仮定します。

  • オンラインチャネル:セッションの50%がオフライン作業指示書を生成すると仮定すると、3wチャットセッションが毎日生成され、3w * 50%= 1.5w作業指示書を毎日生成できます。
  • ホットラインチャネル:コールの80%が作業指示書を生成すると仮定すると、2.5wのコールが毎日生成され、2.5w * 80%= 1日あたり2wのコールを生成できます。
  • オフラインチャネル:オフラインチャネルが1日あたり3wのペンを直接生成すると仮定します。

合計1.5w + 2w + 3w = 6.5wペン/日

今後もカバーされる可能性のある新しいビジネスシナリオを考慮して、事前に拡張スペースを確保する必要があります。ここでは、毎日8wの質問シートが生成されると想定しています。

質問シートに加えて、一般的に使用される2つのビジネスシートがあります。ユーザー操作ログシートとユーザー送信フォームデータシートです。

その中で、各質問順序は複数のユーザー操作ログを生成します。履歴統計によると、各質問順序は平均して約8つの操作ログを生成することがわかります。各質問順序が生成すると仮定して、スペースの一部を予約します。約10のユーザー操作ログ。

システムの設計耐用年数が5年の場合、アンケートデータの量は約= 5年、  365日/年 8w /日= 1億4600万であり、テーブルの推定数は次のとおりです。

  • 質問シートの必要性:1億4600万/ 500w = 29.2シート、32シートに従って分割します。
  • 操作ログに必要なもの:32  10 = 320テーブル、32  16 = 512テーブルに従って分割ます。

ライブラリを分割する必要がある場合、適切なライブラリはいくつありますか?

通常のビジネスピークの読み取りおよび書き込みQPSに加えて、たとえばサブデータベースを検討する必要がある場合、Double11プロモーション期間中に到達する可能性のあるピークを事前に見積もる必要があります。

実際のビジネスシナリオによると、質問シートのデータクエリソースは、主にAliカスタマーサービスのXiaomiのホームページから取得されます。したがって、過去のQPS、RT、およびその他のデータに基づいて評価できます。3500のデータベース接続のみが必要であると仮定すると、1つのデータベースが最大1000のデータベース接続に耐えられる場合、4つのデータベースに分割できます。

3データをセグメント化する方法は?

業界の慣例によれば、分割は通常、水平分割と垂直分割の2つの方法で実行されます。もちろん、複雑なビジネスシナリオでは、この2つの組み合わせを選択する場合もあります。

(1)水平分割

これは、メンバーシップディメンションに基づく一般的なセグメンテーションなど、ビジネスディメンションに基づく水平セグメンテーションであり、特定のルールに従って、さまざまなメンバー関連データがさまざまなデータベーステーブルに分散されます。私たちのビジネスシナリオの決定は、メンバーの観点からデータを読み書きすることであるため、データベースを水平方向に分割することを選択します。

(2)垂直分割

垂直セグメンテーションは、テーブルのさまざまなフィールドをさまざまなテーブルに分割することとして簡単に理解できます。

例:小規模なeコマースビジネスがあり、注文に関連する製品情報、購入者と販売者の情報、および支払い情報がすべて大きなテーブルに配置されているとします。垂直セグメンテーションを使用して、製品情報、購入者情報、販売者情報、および支払い情報を別々のテーブルに分割し、注文番号を介してそれらを基本的な注文情報に関連付けることを検討できます。

また、テーブルに10個のフィールドがあり、そのうち3個のフィールドだけを頻繁に変更する必要がある場合は、これらの3個のフィールドをサブテーブルに分割することを検討できます。これらの3つのデータを変更して、残りの7つのフィールドのクエリ行ロックに影響を与えないようにしてください。

3点ライブラリとサブテーブルの後の新しい問題

1サブデータベースとサブテーブルの後で、各サブデータベースとサブテーブルにデータを均等に分散させるにはどうすればよいですか?

たとえば、ホットイベントが発生した場合、特定のライブラリ/テーブルへのホットデータの集中アクセスを回避する方法。これにより、各サブデータベースとサブテーブルの読み取りと書き込みの圧力が不均一になるという問題が発生します。

実際、慎重に検討した結果、この問題は実際には負荷分散の問題と非常に似ていることがわかります。そのため、負荷分散のソリューションから学習して解決することができます。一般的なバランシングアルゴリズムは次のとおりです。

私たちの選択:コンシステントハッシュアルゴリズムに基づいて調整するコンシステントハッシュアルゴリズムと比較して、私たちの調整されたアルゴリズムは
主に次の点で異なります。

(1)ハッシュリングのノード数の違い

コンシステントハッシュには2 ^ 32-1ノードがあります。buyerIdに従ってセグメント化されており、buyerIdのベースが非常に大きいことを考慮すると、全体にある程度の均一性があるため、ハッシュリングの数を4096に減らします。

(2)DBインデックスアルゴリズムの違い

コンシステントハッシュは、ハッシュ(DBのIP)%2 ^ 32と同様の式を使用して、ハッシュリング内のDBの位置を計算します。DBの数が少ない場合は、ハッシュリングスキューの問題を解決するために仮想ノードを追加する必要があり、特にクラウド環境では、IPの変更に伴ってDBの場所が変わる可能性があります。

データはハッシュリングに均等に分散されます。前の判断の後、Math.abs(buyerId.hashCode())%4096を使用してハッシュリングの場所を計算できます。残りの問題は、DBをに均等に分散させることです。このハッシュ。リングに置くだけです。私たち全員がAliのTDDLミドルウェアを使用しているため、論理サブデータベースのインデックス番号を使用してDBを見つけるだけで済みます。したがって、このハッシュリングでサブデータベースDBを均等に分割できます。ハッシュリングの場合、4096があります。リンク。4つのライブラリが分割されている場合、4つのライブラリはそれぞれ1、1025、2049、および3073ノードに配置されます。サブライブラリのインデックスの位置は、式(Math.abs(buyerId.hashCode())%4096)/(4096 / DB_COUNT)で計算できます。

サブライブラリインデックスのJava擬似コードの実装は次のとおりです。

/**
 * 分库数量
 */
public static final int DB_COUNT = 4;

/**
 * 获取数据库分库索引号
 *
 * @param buyerId 会员ID
 * @return
 */
public static int indexDbByBuyerId(Long buyerId) {
    return (Math.abs(buyerId.hashCode()) % 4096) / (4096 / DB_COUNT);
}

2サブデータベースサブテーブルの環境でデータベースを分割した後の主キーIDの一意性の問題を解決するにはどうすればよいですか?

単一のデータベース環境では、質問シートのメインテーブルのIDは、MySQLの自己インクリメントの方法を採用しています。ただし、サブデータベースの後でデータベースの自己インクリメント方式を引き続き使用すると、各ドアで主キーIDが重複するという問題が発生しやすくなります。

この状況では、UUIDを使用するなど、多くの解決策がありますが、UUIDが長すぎる、クエリパフォーマンスが低すぎる、占有スペースも大きい、主キーのタイプも変更されているため、役に立ちません。アプリケーションの移行をスムーズにします。

実際、IDのセグメント化など、IDの分割を継続することもできます。異なるデータベーステーブルは異なるIDセグメントを使用しますが、新しい問題も発生します。このIDセグメントの長さはどれくらいですか。IDセグメントが割り当てられている場合、それは2番目のライブラリのIDセグメントを占有し、一意でないIDの問題を引き起こす可能性があります。

ただし、サブライブラリで使用されるすべてのIDセグメントを等差数列に従って分離し、IDセグメントが使い果たされるたびに、一定のステップ比に従って増加する場合、この問題は解決できます。

たとえば、以下に示すように、各割り当てのID間隔が1000、つまりステップ長が1000であるとすると、各割り当てのIDセグメントの開始インデックスと終了インデックスは次の式に従って計算できます。

  • X番目のライブラリとY番目の時間に割り当てられたIDセグメントの開始インデックスは次のとおりです。
X * 步长 + (Y-1) * (库数量 * 步长)
  • X番目のライブラリとY番目の時間に割り当てられたIDセグメントの終了インデックスは次のとおりです。
X * 步长 + (Y-1) * (库数量 * 步长) + (1000 -1)

4つのライブラリに分割されている場合、最終的に割り当てられるIDセグメントは次のようになります。

質問リストライブラリは、最初にIDをセグメント化し、次に固定ステップでIDを増やすこの方法を使用します。これは、TDDLが提供する公式ソリューションでもあります。

さらに、実際のシナリオでは、通常、問題の分析とトラブルシューティングのために、いくつかの追加情報がIDに追加されることがよくあります。たとえば、独自の問題チケットIDには、日付、バージョン、サブデータベースインデックスなどの情報が含まれます。

質問チケットID生成Java擬似コードリファレンス:

import lombok.Setter;
import org.apache.commons.lang3.time.DateFormatUtils;

/**
 * 问题单ID构建器
 * <p>
 * ID格式(18位):6位日期 + 2位版本号 + 2位库索引号 + 8位序列号
 * 示例:180903010300001111
 * 说明这个问题单是2018年9月3号生成的,采用的01版本的ID生成规则,数据存放在03库,最后8位00001111是生成的序列号ID。* 采用这种ID格式还有个好处就是每天都有1亿(8位)的序列号可用。* </p>
 */
@Setter
public class ProblemOrdIdBuilder {
  public static final int DB_COUNT = 4;    
    private static final String DATE_FORMATTER = "yyMMdd";

    private String version = "01";
    private long buyerId;
    private long timeInMills;
    private long seqNum;

    public Long build() {
        int dbIndex = indexDbByBuyerId(buyerId);
        StringBuilder pid = new StringBuilder(18)
            .append(DateFormatUtils.format(timeInMills, DATE_FORMATTER))
            .append(version)
            .append(String.format("%02d", dbIndex))
            .append(String.format("%08d", seqNum % 10000000));
        return Long.valueOf(pid.toString());
    }

    /**
     * 获取数据库分库索引号
     *
     * @param buyerId 会员ID
     * @return
     */
    public int indexDbByBuyerId(Long buyerId) {
        return (Math.abs(buyerId.hashCode()) % 4096) / (4096 / DB_COUNT);
    }
}

3サブデータベースおよびサブテーブル環境でトランザクションの問題を解決するにはどうすればよいですか?

分散環境では、トランザクションが複数のサブデータベースにまたがる可能性があるため、処理は比較的複雑です。現在、2つの一般的な解決策があります。

(1)分散トランザクションを使用する

  • 利点:アプリケーションサーバー/データベースが業務を管理します。これは実装が簡単です。
  • 短所:特に多数のサブライブラリが含まれる場合、パフォーマンスコストが高くなります。さらに、特定のアプリケーションサーバー/データベースによって提供される分散トランザクション実装ソリューションにも依存しています。

(2)アプリケーション+データベースによる共同制御

  • 原則:主要なイベントを小さくし、複数の主要なトランザクションを単一のサブデータベースで処理できる小さなトランザクションに分割し、アプリケーションプログラムがこれらのマイナーなトランザクションを制御します。
  • 利点:分散トランザクション調整処理レイヤーなしで、優れたパフォーマンス
  • 短所:トランザクション制御の柔軟な設計は、アプリケーション自体から行う必要があります。ビジネスアプリケーションを処理するには、変換のコストが高くなる必要があります。

上記の2つの分散トランザクションソリューションの場合、どのように選択する必要がありますか?

まず第一に、万能の解決策はなく、あなたに合ったものだけです。まず、私たちのビジネスにおけるトランザクションの使用シナリオを見てみましょう。

問題を相談するのは会員であるか、会員の問題を解決するカスタマーサービスジュニアであるか、またはサードパーティシステムからの関連データを同期するかどうか。主に2つのコアアクションがあります。

  • メンバーの問題データを含む、メンバーディメンション内の関連する進行状況データ、および対応する問題処理操作ログ/進行状況データを照会します。
  • 会員の視点で関連する伝票/フィードバックの新しい情報やその他のデータを提出するか、第2世代のカスタマーサービス会員がこれらのデータを提出します。提出されたデータは、問題が解決されたかどうか(クローズされたかどうか)を決定する場合もあります。

アンケートデータと操作ログは別々にクエリされるため、分散リレーショナルクエリシナリオは含まれず、これは無視できます。

その場合、データシナリオを送信するのはユーザーのみであり、問​​題シートと操作ログデータを同時に書き込むことができます。

使用シナリオが決定されたので、トランザクションソリューションを選択できます。分散トランザクションの実装は単純ですが、ミドルウェアはそれ自体の複雑さを解決するのに役立つため、単純です。複雑さが増すほど、必然的に特定のパフォーマンスの低下がもたらされます。さらに、現在のアプリケーションのほとんどはSpringBootに基づいて開発されており、組み込みのTomcatコンテナーがデフォルトで使用されます。IBMが提供するWebSphere Application ServerやOracleのWebLogicなどの重量のあるアプリケーションサーバーとは異なり、これらはすべて組み込みの分散トランザクションマネージャーを提供します。 。。したがって、アクセスする場合は、分散トランザクションマネージャーを自分で追加する必要があり、このアクセスコストはさらに高くなります。したがって、当面、このスキームは考慮されません。次に、大きなトランザクションを単一のデータベースで解決できる小さなトランザクションに分割する方法しか見つかりません。

したがって、同じメンバーの質問シートデータとこの質問シートに関連する操作ログデータを同じサブデータベースに書き込む方法が問題になります。実際、ソリューションは比較的単純です。メンバーIDはセグメンテーションに使用されるため、同じサブデータベースルーティングルールを使用できます。

最後に、最終的なTDDLサブデータベースサブテーブルルールの構成を見てみましょう。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
  <bean id="vtabroot" class="com.taobao.tddl.interact.rule.VirtualTableRoot" init-method="init">
    <property name="dbType" value="MYSQL" />
    <property name="defaultDbIndex" value="PROBLEM_0000_GROUP" />
    <property name="tableRules">
      <map>
        <entry key="problem_ord" value-ref="problem_ord" />
        <entry key="problem_operate_log" value-ref="problem_operate_log" />
      </map>
    </property>
  </bean>
  <!-- 问题(诉求)单表 -->
  <bean id="problem_ord" class="com.taobao.tddl.interact.rule.TableRule">
    <property name="dbNamePattern" value="PROBLEM_{0000}_GROUP" />
    <property name="tbNamePattern" value="problem_ord_{0000}" />
    <property name="dbRuleArray" value="((Math.abs(#buyer_id,1,4#.hashCode()) % 4096).intdiv(1024))" />
    <property name="tbRuleArray">
      <list>
        <value>
          <![CDATA[
            def hashCode = Math.abs(#buyer_id,1,32#.hashCode());
            int dbIndex = ((hashCode % 4096).intdiv(1024)) as int;
            int tableCountPerDb = 32 / 4;
            int tableIndexStart = dbIndex * tableCountPerDb;
            int tableIndexOffset = (hashCode % tableCountPerDb) as int;
            int tableIndex = tableIndexStart + tableIndexOffset;
            return tableIndex;
          ]]>
        </value>
      </list>
    </property>
    <property name="allowFullTableScan" value="false" />
  </bean>
  <!-- 问题操作日志表 -->
  <bean id="problem_operate_log" class="com.taobao.tddl.interact.rule.TableRule">
    <property name="dbNamePattern" value="PROBLEM_{0000}_GROUP" />
    <property name="tbNamePattern" value="problem_operate_log_{0000}" />
    <!-- 【#buyer_id,1,4#.hashCode()】 -->
    <!-- buyer_id 代表分片字段;1代表分库步长;4代表一共4个分库,当执行全表扫描时会用到 -->
    <property name="dbRuleArray" value="((Math.abs(#buyer_id,1,4#.hashCode()) % 4096).intdiv(1024))" />
    <property name="tbRuleArray">
      <list>
        <value>
          <![CDATA[
            def hashCode = Math.abs(#buyer_id,1,512#.hashCode());
            int dbIndex = ((hashCode % 4096).intdiv(1024)) as int;
            int tableCountPerDb = 512 / 4;
            int tableIndexStart = dbIndex * tableCountPerDb;
            int tableIndexOffset = (hashCode % tableCountPerDb) as int;
            int tableIndex = tableIndexStart + tableIndexOffset;
            return tableIndex;
          ]]>
        </value>
      </list>
    </property>
    <property name="allowFullTableScan" value="false" />
  </bean>
</beans>

4データベースがテーブルに分割された後、履歴データをスムーズに移行するにはどうすればよいですか?

データベースレプリケーションソリューションであるAlibabaCloudは、Alibabaが内部で使用していた以前のデータベースレプリケーションおよび移行ソリューション「DataTransmission Service」[1]もオープンしました。詳細については、AlibabaCloudカスタマーサービスまたはAlibabaCloudデータベースの専門家にお問い合わせください。

サブライブラリ切り替えのリリースプロセスでは、シャットダウンリリースとノンストップリリースのどちらかを選択できます。

(1)公開を停止することを選択した場合

  • まず、黒い風が強く、どこにも誰もいない夜を選びます。冷たい風が吹くと落ち着きがあり、周りに人がいないので、何かをしたり、データを盗んだりできます。午前4時に誰もいないときに切り替えることにしました。可能であれば、一時的に営業を終了することをお勧めします。アクセス入口。
  • 次に、DTSに完全なデータコピータスクを追加して、単一のデータベースのデータを新しいサブデータベースにコピーします(このプロセスは非常に高速で、数千万のデータを約10分で実行する必要があります)。
  • その後、TDDL構成(単一ライブラリ->サブライブラリ)を切り替え、アプリケーションを再起動して、有効になるかどうかを確認します。
  • 最後に、ビジネスアクセスポータルを開いてサービスを提供します。

(2)停止せずに公開することを選択した場合、プロセスは少し複雑になります

  • まず第一に、あなたはまたあなたのハンサムさを引き立たせるために暗くて風の強い夜を選ぶ必要があります。
  • 次に、DTSを使用して、今日より前の履歴データなど、特定の時点より前のデータをコピーします。
  • その後、単一のライブラリからサブライブラリに切り替えて(事前にアプリケーションを公開して構成を準備することをお勧めします)、再起動して有効になるまでに数分しかかかりません。サブデータベースに切り替える前に、DBAに連絡して、切り替え期間中の古い単一データベースの読み取りと書き込みを停止してください。
  • 最後に、サブライブラリの切り替えが完了した後、今朝以降に古い単一ライブラリで生成されたデータは、DTSを介して段階的にコピーされます。
  • 最後に、しばらく観察を続けます。問題がなければ、古い単一のライブラリをオフラインにすることができます。

5TDDLがサブデータベースとサブテーブルのルーティングを構成する際の注意事項

AliのTDDLミドルウェアはgroovyスクリプトを使用してサブデータベースとサブテーブルのルーティングを計算するため、groovyの/演算子または/ =演算子は、Javaのように整数ではなく、double型の結果を生成する可能性があるため、xを使用する必要があります。intdiv( y)関数は整数除算演算を実行します。

// 在 Java 中
System.out.println(5 / 3); // 结果 = 1

// 在 Groovy 中
println (5 / 3);       // 结果 = 1.6666666667          
println (5.intdiv(3)); // 结果 = 1(Groovy整除正确用法)

詳細については、Groovyの公式説明「除算演算子の場合」を参照してください。

カルテットデータベースサブテーブルテキストのケースイラスト

元のリンク

この記事はAlibabaCloudのオリジナルのコンテンツであり、許可なく複製することはできません。

おすすめ

転載: blog.csdn.net/weixin_43970890/article/details/114580539