要旨
この記事では、Kafkaでメッセージが物理的にどのように保存されるかから始め、Partition-Log Segment-Logのレイヤーを紹介します。
消費者向けコンテンツについて少し拡大し、消費者と消費者グループを区別し、そのようにデザインすることの意味について説明します。
消費者の消費によって発生する可能性のある問題に応じて、Kafkaの置換トピックと、消費者がこの置換トピックにどのように置換を提出しなければならないかを説明します。
最後に、消費者がリバランスをする理由と、リバランスが不十分な点についてお話しします。
log
前回、"パーティション "という概念について触れました。
その時に表明された意味は、メッセージの生産と消費は、主題の次元ではなく、トピックにおける仕切りの次元にあるということでした。
つまり、当時のKafkaの理解では、各パリティトンの下のトピックには「キュー」と呼ばれるデータ構造があり、トピックに送信されたメッセージはすべて、いずれかのパリトンに割り当てられます。
この設計により、メッセージキューの性能がIOのボトルネックになることを回避できます。
このセクションでは、Kafkaのメッセージストレージについて詳しく説明します。
メッセージ」として理解されているものは、Kafkaではログと呼ばれます。
各ブローカーには、{Topic}-{Parititon}という名前の複数のフォルダがあります。
ここでのアイデアは、このブローカーでトピックを Test、パーティション 1 と 2 としたメッセージを扱えるようにすることです。
しかし、"パリトン "という言葉も論理的な概念であり、ブローカー内のフォルダに対応していることに注意してください。
Topic}-{Parititon}フォルダの中には、64ビット長の整数ファイル名のファイルがたくさんあります。
例
この図では、複数のログセグメントを含むパーティションです。ここでのログセグメントも論理的な概念であり、特定のログファイルに固有である場合にのみ物理的であることに注意してください。
イメージの右端を見てください。ファイル名は20ビットの整数で、この数字はメッセージの「ベースオフセット」と呼ばれます。例えば、2番目のログセグメントは121から始まるので、このログセグメントの最初のメッセージのオフセットは121から始まるということです。
オフセットは0から始まるので、オフセット121には120ではなく121のデータがあることに注意してください。
次にファイル形式について説明し、ここには*.log、*.index、*.timeindexの3種類のファイルがあることを確認します。
ログ・フォーマット・ファイルはメッセージを記録し、indexはオフセット・インデックス、timeindexはタイムスタンプ・インデックスです。しかし、これでは話が広がりません。この記事の方向性は、まだ個々のコンポーネントを理解することに偏っています。
このようにして、ブローカーはプロデューサーからメッセージを受け取ると、最後のログセグメントにメッセージを書き込みます。これはメッセージの書き込みがシーケンシャルIOであるという利点もあり、このため最後のログセグメントは「アクティブログセグメント」と呼ばれます。
消費者と消費者団体
前回は "消費者 "という概念についてだけ触れました。
またこの記事では、Kafkaにある「コンシューマー」をより深く、より正確に見ていきます。
実際、Kafkaでは、コンシューマーはコンシューマーグループとして外部で消費されます。
消費者グループという概念がなく、同じトピックを購読している消費者が10人いると仮定すると、そのトピックに新しいメッセージがあったとき、この10人の消費者は、そのメッセージを消費するために「メッセージをつかみに行く」べきではないでしょうか?
これは資源の無駄遣いです。ですから、消費者グループも、負荷分散設計のためのより合理的な資源配分と考えることができます。
10パーティションのトピックを購読する同じコンシューマ・グループに属する5人のコンシューマがいると仮定すると、グループ内の各コンシューマは、2パーティションからのメッセージの処理を担当します。
これにより、メッセージがトピックに送信されると、そのメッセージは1つのコンシューマによってのみ消費され、重複して消費されることはありません。
さらに、コンシューマグループの設計により、システムの消費能力を水平方向に拡張することも容易です。システムがどんどんメッセージを溜め込んでいて、消費速度が生産速度に追いつかないことを想像してみてください。新しいコンシューマを追加するだけで、このコンシューマは元のコンシューマグループに割り当てられ、Kafkaは自動的にパーティション上のグループ内のコンシューマの分布を調整します。
ただし、グループ内のコンシューマの数は、トピック内のパーティションの数を超えてはならないことに注意してください。そうしないと、余分なコンシューマはアイドル状態になります。たとえば、トピックのパーティション数が10で、グループ内のコンシューマの数が11の場合、余分なコンシューマはアイドル状態になります。
Kafkaは、同じパーティションを1つのコンシューマーだけが消費できるように設計されていますが、これは置換管理に関連しており、後述します。
さらに、Kafka は同じトピックにサブスクライブしている複数のコンシューマーグループをサポートしており、同じメッセージがそのトピックにサブスクライブしているすべてのコンシューマーグループに送信されます。
注意:同じパーティションは同じコンシューマによってのみ消費されると述べられていますが、この記述はこれらのコンシューマが同じコンシューマ・グループに属していることを前提としています。つまり、異なる消費者グループ内の消費者は、同じテーマパーティションを消費することができます。
つまり、Kafkaのコンシューマー・グループは、ピアツーピアとブロードキャスト・メッセージングの両方を可能にするように設計されていると考えることもできます。
変位のテーマ
前のサブセクションでは、コンシューマ・グループ内のコンシューマはパーティション内で情報を消費し、コンシューマへの参加と退出があると述べました。
そこでこのセクションでは、Kafkaがどのようにして、コンシューマーが変わってもメッセージが失われたり、繰り返し消費されたりしないようにしているかについて説明します。
消費された変位を記録することによって、上記の目標を達成することができると容易に考えることができます。
zkで変位を保存する以前の実装に関係なく、この方法で変位テーマについて直接話してください。
Kafkaには置換トピックと呼ばれる特別な種類のトピックがあり、Kafkaでのトピックの名前は__consumer_offsetsです。
置換トピックもトピックなので、Kafkaのトピックの諸特性に準拠しており、メッセージの送信、メッセージのプル、トピックの削除が自由にできます。ただし、このトピックのデータはkafkaが設計しているため、適当にメッセージを送ったり、ブローカー側で解析できないとクラッシュしてしまいます。
次に、変位トピックに送信されるメッセージの形式について説明します。変位を保存することが望まれるので、これをKV構造と考えるのは簡単です。では、どのようなメッセージをKeyに保存すべきでしょうか?
キーには、サブジェクト名、パーティション名、コンシューマ・グループ名が含まれます。
つまり、どの消費者グループがどのトピックのどのパーティションでどれだけのデータを消費したかを特定できれば十分なのです。なぜか?前述したように、コンシューマは変更される可能性があり、コンシューマが変更されたときにどこで消費を続ければよいかをコンシューマに知らせることが目的だからです。したがって、消費者グループレベルまでの変位情報の精度で十分なのです。
また、バリューでは、消費変位だけを節約すれば十分です。
変位情報がどのように保存されるかを説明したところで、変位テーマそのものについて説明しましょう。変位トピックはテーマでもあるため、パーティション化され、コピーを持つことになります。では、消費者は情報を消費した後、その変位情報をどこに送るのでしょうか?
Kafkaの変位トピックは、最初の消費者が作成されたときに作成され、デフォルトでは50パーティションがあります。変位の提出の消費者は、自分のグループIDのハッシュ値に変位トピックのパーティション数をモジュロに従って、結果は、変位情報がパーティションIDに提出する必要があり、このパーティションIDのリーダーノードを見つけると、変位情報は、ブローカがリーダーノードに提出されます。
変位投稿
変位についてお話ししたことで、カフカが変位について状態を保存していることはご理解いただけたと思いますので、このセクションでは、変位がどのようにコミットされるかについてお話ししましょう。
ズレたコミットに関しては、ズレたテーマがあるからといって、そのメッセージが何度も消費されないということでも、メッセージが失われないということでもないことを明確にすべきです。
また、Kafkaは置換トピックで送信されたメッセージを厳密に強制します。例えば、0から20までのメッセージがすでに消費されている場合、100の置換を送信すると、次のプルは100から開始しなければならず、20から99までのメッセージは失われます。別の例としては、もしあなたが10の置換を提出した場合、10-20のメッセージが繰り返し消費されることになります。
カフカでは、自動と手動の2種類の変位があります。
自動投稿
変位の自動提出は、POLL操作時に行われます。
コンシューマPOLLが最新のメッセージをプルするとき、コンシューマPOLLはまず、ズレを提出するDeadline time pointに到達したかどうかを判断し、もし到達していれば、まずズレの提出が行われ、それからメッセージがプルされます。
なお、ここでは以下のようなことが起こる可能性があります:
ある時点で変位 100 が送信され、その後メッセージ 100-150 が送信されましたが、次の変位が送信される前にコンシューマがダウンしました。この時点では、メッセージ100-120だけが消費され、コンシューマが再起動した後、120の変位が提出されなかったので、その部分がもう一度消費されたのかもしれません。
別の状況を想像すると、あなたはメッセージ100から150を引っ張って、自動提出の時間に、この変位の150を提出し、この時間は、消費者がダウンしている、150からメッセージの処理を引っ張って開始する再起動し、この中の情報が失われます。
手動での提出
自動投稿による情報損失や二重消費を避けるために、手動投稿を行うことができます。
手動コミットはさらに、同期コミットと非同期コミットの2種類に分けられます。
同期コミットは、メッセージが置換トピックに書き込まれるまで戻りません。これは安全ですが、TPSが低下する問題が発生する可能性があります。
非同期コミットとは、アクションとしてコミットをトリガーしてリターンするものです。これは高速ですが、コミットに失敗する可能性があります。
Rebalance
そのようなケースのひとつが、上記で紹介したものです:
消費者グループ内のメンバーの追加や削除は、グループのメンバーが消費に責任を持つパーティションを再編成する必要があります。
これを「リバランス」(リバランス)と呼びます。
より専門的な用語では、これは消費者グループ内の消費者が、与えられたトピックのすべてのパーティションを消費する方法についてコンセンサスを得るプロセスと定義されます。
しかし、この処理はKafkaのスループット率に大きな影響を与えます。この処理はGCのSTWのようなもので、リバランスの間、すべてのコンシューマはリバランスという1つのことだけをしに行くことができ、メッセージを消費することはできません。
これがリバランスにつながる可能性があるものです:
- グループのメンバー数が変わりました
- 購読トピックの数が変更されました
- 購読スレッドのパーティション数が変更されました。
また、リバランス中に、あるコンシューマが終了し、パーティションがいくつか余ったとすると、Kafkaはその余ったパーティションを元のコンシューマに割り当てず、すべてのコンシューマが一緒にすべてのパーティションの再割り当てに参加します。
新しいコンシューマが参加する場合、元のコンシューマがそれぞれパーティションを分割して新しいコンシューマに渡すのではなく、すべてのコンシューマが一緒になってすべてのパーティションを再分配します。
このような配分戦略は奇妙で非効率的に聞こえますが、それを避ける方法はありません。
しかし、コミュニティは上記の仮定的な状況を実現する新しいStickyAssignor戦略を用意しています。