ドルイドの詳細な解釈

ドルイドの効率的なアーキテクチャ

Druid が大規模なデータ セットのリアルタイム取り込みと効率的な複雑なクエリ パフォーマンスを同時に提供できることはわかっていますが、その主な理由は、その独自のアーキテクチャ設計と、データソースとセグメントに基づくデータ ストレージ構造です。次に、データ ストレージとシステム ノード アーキテクチャの側面から Druid のアーキテクチャを詳しく見ていきます。

データストレージ

Druid は、データを読み取り最適化構造に編成します。これが、Druid が対話型クエリをサポートする機能の鍵となります。Druid のデータは、RDMS のテーブルと同様、いわゆるデータソースに保存されます。各データソースは時間によって分割されており、必要に応じて他の属性によってさらに分割できます。各時間範囲はチャンクと呼ばれます (たとえば、日ごとに分割した場合、1 つのチャンクは 1 日になります)。チャンクでは、データは 1 つまたは複数のセグメントに分割されます (セグメントはデータの実際のストレージ構造であり、データソースとチャンクは単なる論理概念です)。各セグメントは個別のファイルであり、通常は数百万行のデータが含まれています。これらのセグメント時間に従ってチャンクに編成されるため、時間に従ってデータをクエリする場合は非常に効率的です。

情報元

データパーティション

分散ストレージ/コンピューティング システムでは、ストレージとコンピューティングのバランス、およびデータの並列化を達成するために、データを合理的に分割する必要があります。Druid 自体がイベント データを処理し、各データにはタイムスタンプがあるため、パーティショニングに時間を使用するのは自然なことです。たとえば、上の図では、パーティション粒度を日次として指定しているため、毎日のデータは個別に保存され、クエリが実行されます (1 つのパーティションに複数のセグメントがある理由については、以下を参照してください)。
タイム パーティショニングを使用する場合、各期間のデータ量が不均衡になる可能性が高いという問題が容易に考えられます (ビジネス シナリオを考えてください)。Duid はこの問題を解決するために「セカンダリ パーティショニング」を提供します。各セカンダリ パーティションはシャードと呼ばれます (これは物理パーティションです)。各シャードが保存できる目標値とシャード ポリシーを設定して、シャードのパーティショニングを完了します。Druid は現在、ハッシュ (ディメンション値に基づくハッシュ) とレンジ (特定のディメンションの値範囲に基づく) という 2 つのシャード戦略をサポートしています。上図では、2000-01-01 と 2000-01-03 の各パーティションがシャードですが、2000-01-02 はデータ量が比較的多いため、シャードが 2 つあります。

セグメント

シャードが永続化されると、セグメントと呼ばれます。セグメントは、データのストレージ、レプリケーション、バランシング (履歴ロード バランシング)、および計算の基本単位です。セグメントは不変です。セグメントが作成されると (MiddleManager ノードがリリースされた後)、変更することはできません。古いバージョンのセグメントを置き換える唯一の方法は、新しいセグメントを生成することです。

セグメント内部ストレージ構造

次に、セグメント ファイルの内部ストレージ構造を見てみましょう。Druid は列型ストレージを使用するため、データの各列は独立した構造に保存されます (すべての列が 1 つのファイルに保存されるため、独立したファイルではなく、独立したデータ構造になります)。セグメントのデータ型は主にタイムスタンプ、ディメンション列、インジケーター列の 3 種類に分かれます。


セグメントデータ列

タイムスタンプ列とインジケーター列の場合、実際のストレージは配列であり、Druid は LZ4 を使用して各列の整数または浮動小数点数を圧縮します。クエリリクエストを受信すると、必要な行データを取り出し(不要な列は取り出しません)、解凍します。解凍後、特定の集計関数が適用されます。
ディメンション列は、フィルターとグループ化をサポートする必要があるため、インジケーター列やタイムスタンプほど単純ではありません。そのため、Druid は辞書エンコーディング (Dictionary Encoding) とビットマップ インデックス (Bitmap Index) を使用して各ディメンション列を保存します。各ディメンション列には 3 つのデータ構造が必要です。

  1. ディメンション値 (ディメンション列の値は文字列型とみなされます) を整数 ID にマッピングするには、ディクショナリ データ構造が必要です。
  2. 上記の辞書エンコーディングを使用して、その列のすべてのディメンション値をリストに配置します。
  3. 列内の個別の値については、ビットマップ データ構造を使用して、それらの値が含まれる行を識別します。

Druid は次の理由からディメンション列にこれら 3 つのデータ構造を使用します。

  1. 辞書を使用して文字列を整数 ID にマッピングすると、構造 2 と 3 の値をコンパクトに表現できます。
  2. ビットマップ ビットマップ インデックスを使用すると、ビットマップは AND および OR 演算を迅速に実行できるため、高速なフィルタリング操作 (読み取られるデータ量を削減するための基準を満たす行番号を検索) を実行できます。
  3. group by および TopN 操作の場合、構造 2 の列値リストを使用する必要があります。

上記の「ページ」ディメンション列を例として、Druid がこれら 3 つのデータ構造を使用してディメンション列を保存する方法を詳しく見ることができます。

1. 使用字典将列值映射为整数
{
"Justin Bieher":0,
"ke$ha":1
}
2. 使用1中的编码,将列值放到一个列表中
[0,0,1,1]
3. 使用bitmap来标识不同列值
value = 0: [1,1,0,0] //1代表该行含有该值,0标识不含有
value = 1: [0,0,1,1]

次の図は、広告主列を例として、広告主列の実際のストレージ構造を説明しています。


広告主の列値のストレージ

最初の 2 つのストレージ構造は、最悪の場合のデータ量に応じて直線的に増加します (列データの各行は異なります)。一方、3 番目のストレージ構造はビットマップ ストレージ (それ自体がスパース行列) を使用するため、圧縮により非常に客観的な圧縮率。Druid はまた、Roaring Bitmap ( http://roaringbitmap.org/ ) を使用して圧縮ビットマップに対してブール演算を直接実行します。これにより、クエリ効率とストレージ効率が大幅に向上します (解凍する必要はありません)。

セグメントの命名

効率的なデータ クエリは、ファイル コンテンツのストレージ構造に反映されるだけでなく、ファイルの名前付けも非常に重要です。想像してみてください。データソースの下に何百万ものセグメント ファイルがある場合、どうすれば必要なファイルをすばやく見つけることができるでしょうか? 答えは、ファイル名による高速インデックス検索です。
セグメントの名前付けには、データ ソース (データソース)、時間間隔 (開始時刻と終了時刻を含む)、バージョン番号、パーティション (セグメントにシャードがある場合にのみ使用可能) の 4 つの部分が含まれます。

test-datasource_2018-05-21T16:00:00.000Z_2018-05-21T17:00:00.000Z_2018-05-21T16:00:00.000Z_1
数据源名称_开始时间_结束时间_版本号_分区

フラグメント番号は 0 から始まります。パーティション番号が 0 の場合は省略できます: test-datasource_2018-05-21T16:00:00.000Z_2018-05-21T17:00:00.000Z_2018-05-21T16:00:00.000Z
時間間隔セグメントが複数のシャードで構成されている場合、セグメントをクエリするときは、すべてのシャードがロードされるまで待ってからクエリを実行する必要があることに注意してください (ロードが完了していないときにクエリを実行できる線形シャード仕様を使用している場合を除く) )。

分野 それは必要ですか 説明する
情報元 はい セグメントが配置されているデータソース
開始時間 はい このセグメントに保存されている最も古いデータは ISO 8601 時間形式です。開始時刻と終了時刻は、segmentGranularity で設定された時間間隔です。
終了時間 はい このセグメントに保存されている最新のデータ、時間形式は ISO 8601 です
バージョンナンバー はい Druid はバッチ上書き操作をサポートしているため、同じデータ ソースおよび同じ時間間隔からのデータがバッチで取り込まれると、データは上書きされ、この時点でバージョン番号が更新されます。Druid システムの他の部分がこの信号を感知すると、古いデータが削除され、新しいバージョンのデータが使用されます (この切り替えは非常に高速です)。バージョン番号には ISO 8601 タイムスタンプも使用されますが、このタイムスタンプは最初の起動時間を表します。
パーティション番号 いいえ パーティショニングを使用する場合、セグメントにはこのロゴのみが表示されます。

セグメント物理ストレージインスタンス

セグメントがどのような形式で保存されるかを例に挙げて、ローカル インポートを使用して次のデータを Druid にインポートします。

{
    
    "time": "2018-11-01T00:47:29.913Z","city": "beijing","sex": "man","gmv": 20000}
{
    
    "time": "2018-11-01T00:47:33.004Z","city": "beijing","sex": "woman","gmv": 50000}
{
    
    "time": "2018-11-01T00:50:33.004Z","city": "shanghai","sex": "man","gmv": 10000}

Druid はスタンドアロン モードで実行するため、Druid によって生成されたセグメント ファイルは ${DRUID_HOME}/var/druid/segments ディレクトリにあります。

セグメントディレクトリ

セグメントは datasource_beginTime_endTime_version_shard によって一意に識別され、実際のストレージ内のディレクトリの形式で表されます。

セグメントディレクトリ

セグメントには、セグメント記述ファイル (descriptor.json) と圧縮されたインデックス データ ファイル (index.zip) が含まれていることがわかります。主に、index.zip ファイルを参照して解凍します。


セグメントデータファイル

まず、factory.json ファイルを見てください。このファイルは、セグメント データを特別に格納するファイルではありません。Druid は MMap (メモリマップドファイル方式) を使って Segment ファイルにアクセスしているので、このファイルの内容を見ると MMap がファイルを読み込むために使われているようです (MMap についてはよく知りません)?

#factory.json文件内容
{
    
    "type":"mMapSegmentFactory"}

Druid によって保存される実際のセグメント データ ファイルは、version.bin、meta.smoosh、xxxxx.smoosh です。これら 3 つのファイルの内容をそれぞれ見てみましょう。
version.bin は 4 バイトを保存するバイナリ ファイルです。これはセグメントの内部バージョン番号です (Druid の開発に伴い、セグメント形式も開発されます)。現在は V9 です。Sublime でファイルを開くと、次の内容が表示されます。

0000 0009 

meta.smoosh には、他の smoosh ファイル (xxxxx.smoosh) に関するメタデータが保存されます。このメタデータには、各列に対応するファイルとファイル内のオフセットが記録されます。列情報に加えて、smoosh ファイルには、セグメントに関する追加のメタデータ情報であるindex.drd およびmetadata.drd も含まれています。

#版本号,该文件所能存储的最大值(2G),smooth文件数
v1,2147483647,1
# 列名,文件名,起始偏移量,结束偏移量
__time,0,0,154
city,0,306,577
gmv,0,154,306
index.drd,0,841,956
metadata.drd,0,956,1175
sex,0,577,841

00000.smoosh ファイルを見る前に、まずこのファイルがなぜこのような名前になっているのか考えてみましょう。開いているファイル ハンドルの数を最小限に抑えるために、Druid はセグメントのすべての列データを smoosh ファイル (ファイル xxxxx.smoosh) に保存するためです。ただし、Druid は MMap を使用してセグメント ファイルを読み取り、MMap は各ファイルのサイズが 2G (Java の MMapByteBuffer 制限) を超えないようにする必要があるため、smoosh ファイルが 2G より大きい場合、Druid は新しいデータをファイル内の次のスムース。これが、これらのファイルがこのように命名されている理由であり、これは、メタ ファイル内で識別列が配置されているファイル名が必要な理由にも対応しています。
また、meta.smoosh のオフセットからも、00000.smoosh ファイル内のデータは列に格納されており、上から順に時間列、インジケーター列、ディメンション列が格納されていることがわかります。各列には、主に ColumnDescriptor とバイナリ データの 2 つの部分の情報が含まれています。columnDescriptor は、Jackson を使用してシリアル化されたオブジェクトで、データ型、複数値かどうかなど、列に関するメタデータ情報が含まれています。バイナリは、さまざまなデータ型に従って圧縮および保存されたバイナリ データです。

^@^@^@d{
     
     "valueType":"LONG","hasMultipleValues":false,"parts":[{
     
     "type":"long","byteOrder":"LITTLE_ENDIAN"}]}^B^@^@^@^C^@^@ ^@^A^A^@^@^@^@"^@^@^@^A^@^@^@^Z^@^@^@^@¢yL½Ìf^A^@^@<8c>X^H^@<80>¬^WÀÌf^A^@^@^@^@^@d{"valueType":"LONG","hasMultipleValues":false,"parts":[{"type":"long","byteOrder":"LITTLE_ENDIAN"}]}^B^@^@^@^C^@^@ ^@^A^A^@^@^@^@ ^@^@^@^A^@^@^@^X^@^@^@^@1 N^@^A^@"PÃ^H^@<80>^P'^@^@^@^@^@^@^@^@^@<9a>{
     
     "valueType":"STRING","hasMultipleValues":false,"parts":[{
     
     "type":"stringDictionary","bitmapSerdeFactory":{
     
     "type":"concise"},"byteOrder":"LITTLE_ENDIAN"}]}^B^@^@^@^@^A^A^@^@^@#^@^@^@^B^@^@^@^K^@^@^@^W^@^@^@^@beijing^@^@^@^@shanghai^B^A^@^@^@^C^@^A^@^@^A^A^@^@^@^@^P^@^@^@^A^@^@^@^H^@^@^@^@0^@^@^A^A^@^@^@^@^\^@^@^@^B^@^@^@^H^@^@^@^P^@^@^@^@<80>^@^@^C^@^@^@^@<80>^@^@^D^@^@^@<9a>{
     
     "valueType":"STRING","hasMultipleValues":false,"parts":[{
     
     "type":"stringDictionary","bitmapSerdeFactory":{
     
     "type":"concise"},"byteOrder":"LITTLE_ENDIAN"}]}^B^@^@^@^@^A^A^@^@^@^\^@^@^@^B^@^@^@^G^@^@^@^P^@^@^@^@man^@^@^@^@woman^B^A^@^@^@^C^@^A^@^@^A^A^@^@^@^@^P^@^@^@^A^@^@^@^H^@^@^@^@0^@^A^@^A^@^@^@^@^\^@^@^@^B^@^@^@^H^@^@^@^P^@^@^@^@<80>^@^@^E^@^@^@^@<80>^@^@^B^A^@^@^@^@&^@^@^@^C^@^@^@^G^@^@^@^O^@^@^@^V^@^@^@^@gmv^@^@^@^@city^@^@^@^@sex^A^A^@^@^@^[^@^@^@^B^@^@^@^H^@^@^@^O^@^@^@^@city^@^@^@^@sex^@^@^AfÌ<91>Ð^@^@^@^AfѸ,^@^@^@^@^R{
     
     "type":"concise"}{
     
     "container":{},"aggregators":[{
     
     "type":"longSum","name":"gmv","fieldName":"gmv","expression":null}],"timestampSpec":{
     
     "column":"time","format":"auto","missingValue":null},"queryGranularity":{
     
     "type":"none"},"rollup":true}

スムースファイル内のバイナリデータはLZ4またはBitmapで圧縮されているため、データ本来の内容を見ることはできません。

スムーズ ファイルの最後には、index.drd と metadata.drd という 2 つの部分のデータも含まれています。Index.drd には、セグメントで使用されるメジャー、ディメンション、時間範囲、およびビットマップが含まれます。Metadata.drd には、インジケーター集計関数、クエリ粒度、タイムスタンプ設定などが保存されます (上記コンテンツの最後の部分)。
下図は物理ストレージ構造図で、一番右の内容が非圧縮・符号化データの保存です。

セグメント物理ストレージ

セグメントの作成

セグメントは MiddleManager ノードで作成され、MiddleManager のセグメントは変更可能でコミットされていません (DeepStorage に送信された後、データは変更できません)。
セグメントは、MiddleManager で作成されてから Historical に伝播されるまで、次の手順を実行します。

  1. MiddleManager でセグメント ファイルを作成し、Deep Storage に公開します。
  2. セグメント関連のメタデータ情報は MetaStore に保存されます。
  3. コーディネーター プロセスは、メタストアからセグメント関連のメタデータ情報を学習した後、ルール設定に従って、それを複合条件の履歴ノードに割り当てます。
  4. コーディネーターの指示を受信した後、履歴ノードは自動的に DeepStorage からセグメント データ ファイルを取得し、セグメント データに関連するクエリ サービスを提供する責任があることを Zookeeper を通じてクラスターに宣言します。
  5. MiddleManager は、Historical がセグメントを担当していることを認識すると、セグメント ファイルを破棄し、セグメントに関連するクエリに対する責任がなくなったことをクラスターに宣言します。

パーティションの構成方法

セグメントの時間間隔は、granularitySpec ( http://druid.io/docs/latest/ingestion/ingestion-spec.html#granularityspec )のsegmentGranularity を通じて設定できますDruid クエリの効率を確保するために、各セグメント ファイルのサイズは 300MB ~ 700MB にすることをお勧めします。この範囲を超える場合は、時間間隔を変更するか、最適化のためにパーティショニングを使用できます (partitioningSpec で targetPartitionSize を構成します。公式の推奨事項は、500 万行を超える行を設定することです。http://druid.io/docs/latest/ingestion/hadoop ) .html#partitioning-仕様)。

システムアーキテクチャの詳細な説明

Druid ノードには、オーバーロード、ミドルマネージャー、コーディネーター、ヒストリカル、ブローカーの 5 つのタイプがあることがわかっています。


ドルイド建築

オーバーロードと MiddleManager は主にデータの取り込みを担当します (未公開セグメントの場合、MiddleManager はクエリ サービスも提供します)。コーディネーターとヒストリカルは主に履歴データのクエリを担当します。ブローカー ノードは主にクライアント クエリ リクエストの受信とサブクエリの MiddleManager ノードとヒストリカル ノードへの分割を担当します。その後、マージされたクエリ結果がクライアントに返されます。このうち、Overload は MiddleManager のマスター ノードであり、Coordinator は Historical のマスター ノードです。

インデックスサービス

Druid は、インデックス サービスをサポートする一連のコンポーネント、つまり Overload ノードと MiddleManager ノードを提供します。インデックス サービスは、インデックス関連のタスクを実行するために使用される可用性の高い分散サービスです。インデックス サービスは、データ取り込み用のセグメントを作成および破棄する主な方法です (リアルタイム ノードを使用する方法もありますが、現在は廃止されています) .)。インデックス サービスは、プル モードまたはプッシュ モードでの外部データの取り込みをサポートします。
インデックス サービスはマスター/スレーブ アーキテクチャを採用しており、Overload がマスター ノード、MiddleManager がスレーブ ノードとなります。インデックス サービスのアーキテクチャ図を以下に示します。

インデックスサービス

インデックス サービスは、タスクを実行するための Peon (レイバー) コンポーネント、Peon を管理するための MiddleManager コンポーネント、および MiddleManager にタスクを割り当てるための Overload コンポーネントの 3 つのコンポーネントで構成されます。MiddleManager コンポーネントと Overload コンポーネントは同じノードまたは複数のノードにデプロイできますが、Peon と MiddleManager は同じノードにデプロイされます。
インデックス サービスのアーキテクチャは Yarn のアーキテクチャとよく似ています。

  • Overlaod ノードは Yarn の ResourceManager に相当し、クラスターのリソース管理とタスクの割り当てを担当します。
  • MiddleManager ノードは Yarn の NodeManager に相当し、タスクの受け入れとこのノードのリソースの管理を担当します。
  • Peon ノードは Yarn のコンテナに相当し、ノード上で特定のタスクを実行します。

過負荷ノード

オーバーロードはインデックス サービスのマスター ノードとして、インデックス作成タスクを外部から受け入れ、タスクを内部で分解して MiddleManager に発行する責任があります。オーバーロードには 2 つの動作モードがあります。

  • ローカルモード: デフォルトモード。ローカル モードでのオーバーロードは、タスクの調整を担当するだけでなく、特定のタスクを完了するために一部のタスクを開始することも担当します。
  • リモート モード: このモードでは、オーバーロードと MiddleManager は異なるノードで実行され、タスクの調整のみを担当し、特定のタスクの完了は担当しません。

オーバーロードは、タスクの表示、タスクの実行、タスクの終了などに使用できる UI クライアントを提供します。

http://<OVERLORD_IP>:<port>/console.html

オーバーロードは RESETful アクセス フォームを提供するため、クライアントは HTTP POST を通じて要求元のノードにタスクを送信できます。

http://<OVERLORD_IP>:<port>/druid/indexer/v1/task //提交任务
http://<OVERLORD_IP>:<port>/druid/indexer/v1/task/{task_id}/shutdown //杀死任务

中間マネージャーノード

MiddleManager はタスクを実行する作業ノードです。MiddleManager は、別の JVM 上で実行されている各 Peon にタスクを送信します (リソースとログを分離する必要があるため)。各 Peon は一度に 1 つのタスクのみを実行できます。

ペオンノード

Peons は単一の JVM で単一のタスクを実行し、MiddleManager はタスク用の Peons を作成する責任があります。

コーディネーターノード

コーディネーターは Historical のマスター ノードであり、主にセグメントの管理と配布を担当します。具体的な作業は、ヒストリカルにセグメントのロードまたは削除を指示し、セグメントのコピーを管理し、ヒストリカルでセグメントの負荷を分散することです。
コーディネーターは定期的に実行され、実行間隔は構成パラメーターを通じて構成できます。コーディネーターが実行されるたびに、Zookeeper を通じて現在のクラスターのステータスを取得し、クラスターのステータスを評価することで適切なアクション (負荷セグメントの分散など) を実行します。コーディネーターは、セグメント情報とルール (Rule) を保存するデータベース (MetaStore) に接続します。セグメント テーブルには、クラスターにロードする必要があるすべてのセグメントがリストされます。コーディネーターが実行されるたびに、セグメント テーブルからセグメント リストが取得され、現在のクラスターのセグメントと比較されます。データベースには存在しますが、まだクラスター内にあります。存在する場合、クラスターから削除されます。ルール テーブルは、セグメントの処理方法を定義します。ルールの機能は、セグメントを操作するための一連のルールを構成できることです。クラスターを使用してセグメントをロードまたは削除します。ルールの構成方法については、http://druid.io/docs/latest/operations/rule-configuration.htmlを参照してください。

履歴ノードがセグメントをロードする前に、容量によってソートされ、セグメントが最も少ない履歴ノードが最も高いロード権限を持ちます。コーディネーターはヒストリカル ノードと直接通信するのではなく、セグメント情報をキューに入れます。ヒストリカル ノードはキューに移動してセグメントの説明情報を取得し、セグメントをこのノードにロードします。
コーディネーターは、クラスター情報とルール構成を表示するための UI インターフェイスを提供します。

http://<COORDINATOR_IP>:<COORDINATOR_PORT>

履歴ノード

ヒストリカル ノードは、ヒストリカル セグメントの管理を担当します。ヒストリカル ノードは、Zookeeper を介して指定されたパスを監視し、ロードする必要がある新しいセグメントがあるかどうかを検出します (コーディネーターは、割り当てアルゴリズムを通じて特定のヒストリカルを指定します)。
上記のコーディネーターから分かるように、新しいセグメントをロードする必要がある場合、コーディネーターはそれをキューに入れます。履歴ノードは新しいセグメントを受信すると、ローカル キャッシュとディスクをチェックして、セグメントに関する情報があるかどうかを確認します。履歴ノードがない場合は、セグメント関連の情報が Zookeeper から取得されてダウンロードされます。


履歴ロードセグメント

ブローカ

ブローカー ノードは、クライアント クエリ リクエストの転送を担当します。ブローカーは、zookeeper を通じてどのセグメントがどのノードにあるかを知ることができ、クエリを対応するノードに転送します。すべてのノードがデータを返した後、ブローカーはすべてのノードのデータをマージしてクライアントに返します。
ブローカーには、各セグメントの結果をキャッシュするための LRU (キャッシュ無効化ポリシー) があります。このキャッシュはローカル キャッシュにすることも、外部キャッシュ システム (memcached など) を使用することもでき、サードパーティ キャッシュはすべてのブローカー間でセグメント結果を共有できます。ボーカーはクエリリクエストを受信すると、まずローカルに対応するクエリデータが存在するかどうかを確認し、存在しないセグメントデータについてはリクエストをヒストリカルノードに転送します。

ブローカークエリ

リアルタイム データは信頼性の低い状態にあるため、ブローカーはリアルタイム データをキャッシュしません。

おすすめ

転載: blog.csdn.net/qq_42264264/article/details/96998307