パーティションの割り当てとリバランス
1. Kafka には、Range、RoundRobin、Sticky、CooperativeSticky の 4 つの主流パーティション戦略があります。パラメータpartition.assignment.strategyを構成することにより、パーティション割り当て戦略を変更できます。デフォルトの戦略は Ranage+Cooperative Sticky です。Kafka は、複数のパーティション割り当て戦略を同時に使用できます。
パラメータ | 説明 |
---|---|
ハートビート間隔.ms | Kafka コンシューマーとコーディネーター間のハートビート時間。デフォルトは 3 秒です。このエントリの値は session.timeout.ms 未満である必要があり、session.timeoyt.ms の 1/3 を超えてはなりません。 |
セッション.タイムアウト.ms | Kafka コンシューマーとコーディネーター間の接続タイムアウト。デフォルトは 45 秒です。この値を超えると、コンシューマは削除され、コンシューマはリバランスを実行します。 |
最大ポーリング間隔.ms | コンシューマーがメッセージを処理する最大時間。デフォルトは 5 分です。この値を超えるとメッセージは削除され、コンシューマーは再バランスを実行します。 |
パーティション割り当て戦略 | コンシューマ パーティション割り当て戦略。デフォルトの戦略は Range+CooperativeStickt です。Kafka は、複数のパーティション割り当て戦略を同時に使用できます。利用可能な戦略は次のとおりです: Range、RoundRobin、sticky、CooperativeSticky |
範囲とリバランス
Range パーティション戦略 Range の原則は
、トピックごとに、
まずシリアル番号に従って同じトピック内のパーティションを並べ替え、次にコンシューマーをアルファベット順に並べ替えることです。
現在、7 つのパーティションと 3 つのコンシューマがあります。ソート後、パーティションは 0、1、2、3、4、5、6 になります。ソート後のコンシューマは、パーティション/コンシューマの数に従って C0、C1、および C2 になります
。この数値により、各コンシューマーが消費するパーティションの数が決まります。割り切れない場合は、最初の数人のコンシューマーがさらに 1 つのパーティションを消費します。
例えば。7/3=2 で 1 が残り、分割不可能な場合、コンシューマ C0 はさらに 1 つのパーティションを消費します。8/3=2 と 2 は無尽蔵なので、C0 と C1 はそれぞれさらに 1 つ消費します。
注: トピックが 1 つだけの場合、C0 コンシューマに対するパーティションが 1 つ増えても影響は大きくありませんが、トピックが N 個ある場合、トピックごとにコンシューマ C0 はパーティションを 1 つ多く消費します。トピックが増えると、消費量も増えます。 C0 パーティションは他のコンシューマーよりも N パーティションを大幅に消費します
データ スキューを試すのは簡単です
テスト コード
package com.longer.range;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
/**
* 测试指定分区(partition)
*/
public class Producer {
public static void main(String[] args) throws InterruptedException {
//1、创建kafka生产者得配置对象
Properties properties=new Properties();
//2、给kafka配置对象添加配置信息
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop100:9092");
//3、key value 序列化
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
//4、创建kafka生产者对象
KafkaProducer<String,String> producer=new KafkaProducer<String, String>(properties);
for (int i = 0; i < 500; i++) {
//指定数据发送到1号分区,key为空(IDEA中,ctrl+p查看参数)
producer.send(new ProducerRecord<>("two", "longer " + i), new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception e) {
if(e==null){
System.out.println(String.format("主题:%s,分区:%s",metadata.topic(),metadata.partition()));
return;
}
e.printStackTrace();
}
});
Thread.sleep(1000);
}
//关闭资源
producer.close();
}
}
package com.longer.range;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
public class CustomConsumer1 {
public static void main(String[] args) {
//创建消费者的配置对象
Properties properties=new Properties();
//2、给消费者配置对象添加参数
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop100:9092");
//配置序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
//配置消费者组(组名任意起名)必须
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
//创建消费者对象
KafkaConsumer<String,String> kafkaConsumer=new KafkaConsumer<String, String>(properties);
//注册要消费的主题
ArrayList<String> topics=new ArrayList<>();
topics.add("two");
kafkaConsumer.subscribe(topics);
while (true){
//设置1s中消费一批数据
ConsumerRecords<String,String> consumerRecords=kafkaConsumer.poll(Duration.ofSeconds(1));
//打印消费到的数据
for(ConsumerRecord<String,String> record:consumerRecords){
System.out.println(record);
}
}
}
}
1 人のコンシューマが毎秒メッセージを送信し、3 人のコンシューマがそれを受信します。印刷状況を観察します。次に、消費者の 1 人を停止し、再度状況を観察します。
(1) コンシューマ 0 を停止し、すぐにメッセージを再送信して結果を確認します (45 秒以内、早いほど良い)。
コンシューマ No. 1: No. 3 および No. 4 パーティションからのデータを消費します。
コンシューマ No. 2: パーティション 5 と 6 からのデータを消費します。
コンシューマ 0 のタスクは、全体としてコンシューマ 1 またはコンシューマ 2 に割り当てられます。
注: コンシューマ 0 がハングアップした後、コンシューマ グループは 45 秒のタイムアウト時間に従って終了するかどうかを判断する必要があるため、待機する必要があります。45 秒までの時間が経過した後、
本当に終了すると判断された場合は、待機します。タスクを他のブローカーに割り当てて実行します。
(2) メッセージを再度送信して結果を確認します (45 秒後)。
コンシューマ No. 1: パーティション 0、1、2、および 3 からのデータを消費します。
コンシューマ No. 2: パーティション 4、5、および 6 からのデータを消費します。
説明: コンシューマ 0 はコンシューマ・グループから除外されたため、範囲方式に従って再割り当てされます。
ラウンドロビンとリバランス
クラスター内のすべてのトピックについて、
RoundRobin のフォール パーティション戦略では、すべてのパーティションとすべてのコンシューマをリストし、ハッシュコードに従って並べ替え、最後にフォール アルゴリズムを通じて各コンシューマにパーティションを割り当てます。
パーティション戦略を変更する
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,"org.apache.kafka.clients.consumer.RoundRobinAssignor");
テストコード
public class CustomConsumer1 {
public static void main(String[] args) {
//创建消费者的配置对象
Properties properties=new Properties();
//2、给消费者配置对象添加参数
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop100:9092");
//配置序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
//配置消费者组(组名任意起名)必须
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
//修改分区策略
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,"org.apache.kafka.clients.consumer.RoundRobinAssignor");
//创建消费者对象
KafkaConsumer<String,String> kafkaConsumer=new KafkaConsumer<String, String>(properties);
//注册要消费的主题
ArrayList<String> topics=new ArrayList<>();
topics.add("two");
kafkaConsumer.subscribe(topics);
while (true){
//设置1s中消费一批数据
ConsumerRecords<String,String> consumerRecords=kafkaConsumer.poll(Duration.ofSeconds(1));
//打印消费到的数据
for(ConsumerRecord<String,String> record:consumerRecords){
System.out.println(record);
}
}
}
}
(1) コンシューマ 0 を停止し、すぐにメッセージを再送信して結果を確認します (45 秒以内、早いほど良い)。
コンシューマ No. 1: パーティション 2 および 5 からのデータを消費します。
コンシューマ No. 2: パーティション 4 および 1 からのデータを消費します。
コンシューマ No. 0 のタスクは、ラウンドロビン方式に従ってデータ ポーリングをパーティション 0、6、および 3 に分割します。データは、
それぞれ消費者 No. 1 または消費者 No. 2 によって消費されます。
注: コンシューマ 0 がハングアップした後、コンシューマ グループは 45 秒のタイムアウト時間に従って終了するかどうかを判断する必要があるため、待機する必要があります。45 秒までの時間が経過した後、
本当に終了すると判断された場合は、待機します。タスクを他のブローカーに割り当てて実行します。
(2) メッセージを再度送信して結果を確認します (45 秒後)。
コンシューマ No. 1: パーティション 0、2、4、および
6 からデータを消費します。 コンシューマ No. 2: パーティション 1、3、および 5 からデータを消費します
。 説明: コンシューマ 0 はコンシューマ グループから追い出されたため、ラウンドロビン方式に従います。再度配布します。
スティッキーとリバランス
スティッキー パーティションの定義: 割り当ての結果は「スティッキー」であると理解できます。つまり、新しい割り当てを実行する前に、以前の割り当ての結果が考慮され、割り当ての変更が可能な限り少なく調整されます。大幅なオーバーヘッドを節約できます。
スティッキー パーティションの場合、Kafka はバージョン 0.11.x 以降、この割り当て戦略を導入しています。まず、最初に割り当てられた元のパーティションを変更しないように試行します。
テスト コード
package com.longer.sticky;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
public class CustomConsumer1 {
public static void main(String[] args) {
//创建消费者的配置对象
Properties properties=new Properties();
//2、给消费者配置对象添加参数
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop100:9092");
//配置序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
//配置消费者组(组名任意起名)必须
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"test");
//修改分区策略
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,"org.apache.kafka.clients.consumer.StickyAssignor");
//创建消费者对象
KafkaConsumer<String,String> kafkaConsumer=new KafkaConsumer<String, String>(properties);
//注册要消费的主题
ArrayList<String> topics=new ArrayList<>();
topics.add("two");
kafkaConsumer.subscribe(topics);
while (true){
//设置1s中消费一批数据
ConsumerRecords<String,String> consumerRecords=kafkaConsumer.poll(Duration.ofSeconds(1));
//打印消费到的数据
for(ConsumerRecord<String,String> record:consumerRecords){
System.out.println(record);
}
}
}
}
(1) コンシューマ 0 を停止し、すぐにメッセージを再送信して結果を確認します (45 秒以内、早いほど良い)。
コンシューマ No. 1: パーティション 2、5、および 3 からのデータを消費します。
コンシューマ No. 2: パーティション 4 と 6 からのデータを消費します。
コンシューマ 0 のタスクは、スティッキー ルールに従って、パーティション 0 とパーティション 1 のパーティション データにできるだけ均等にランダムに分割され、それぞれ
コンシューマ 1 またはコンシューマ 2 によって消費されます。
注: コンシューマ 0 がハングアップした後、コンシューマ グループは 45 秒のタイムアウト時間に従って終了するかどうかを判断する必要があるため、待機する必要があります。45 秒までの時間が経過した後、
本当に終了すると判断された場合は、待機します。タスクを他のブローカーに割り当てて実行します。
(2) メッセージを再度送信して結果を確認します (45 秒後)。
コンシューマ No. 1: パーティション 2、3、および 5 からのデータを消費します。
コンシューマ No. 2: パーティション 0、1、4、および 6 からのデータを消費します。
説明: コンシューマ 0 はコンシューマ・グループから追い出されたため、スティッキー・メソッドに従って再割り当てされます。
要約する
range会造成数据倾斜,RoundRobin不会造成,但是分区调整不会考虑最小变动。sticky,尽量少的调整分配变动,可以节省大量的开销。