需求
现有500个Topic,每个Topic有8个分区,需要一直进行消费
问题
500个线程8个分区,可以为每个分区设置一个消费者消费吗
最好不
需要考虑服务器压力,以及kafka生产的速率
如果每个分区一个消费者,那么会建立4000个线程来同时消费,这样会压垮服务器
考虑到服务器压力,加入CPU核数为32核,因此可以设置固定的32线程,每个线程来执行不同的Topic的8个分区
为什么不为每个分区设置一个comsumer
这样会导致消费到的数据时间差值太大,可能生产者在源源不断的生产数据,这时候同个topic的8个分区,其中有些分区有消费者消费,导致数据已经到了偏移量50000的时候,其他同个topic的有些分区暂时没有消费者消费,当线程来消费的时候,才从10000开始消费,这样同个topic的数据消费差值太大
所以为了保证数据消费尽量的一致性需要同个消费者来消费同个topic
那么只设置32个线程,不是只能执行32个topic吗
是的,这里32个线程,相当于并行处理32个topic,其他几百个topic不执行了吗
要执行的,这里是需要考虑到生产者生产的效率的,如果这里消费者消费得快,清空了内存中的挤压数据,并且设置一个数据未拉取到时间,假设5s没拉取到数据,就释放该消费者,并让topic重新排队等到线程执行。这样就能保证其他topic也能执行了,数据多的topic可能会一直占用线程,数据少的可能就占用几分钟释放。
代码
import org.apache.kafka.common.TopicPartition;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
public class SimplifiedKafkaMultiThreadConsumer {
private static final String BOOTSTRAP_SERVERS = "localhost:9092";
private static final String GROUP_ID = "dynamic-consumer-group";
private static final String[] TOPICS = {
"topic1", "topic2", "topic3", "topic4", /*... more topics ...*/};
private static final int NUM_THREADS = 32; // 假设 32 个线程
private static final int PARTITIONS_PER_THREAD = 8; // 每个线程消费 8 个分区
private static final long MAX_IDLE_TIME_MS = 5000; // 如果 5 秒没有拉取到数据,则释放该线程
// 使用 LinkedBlockingQueue 来作为任务队列
private static final Queue<KafkaConsumerTask> taskQueue = new LinkedBlockingQueue<>();
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(NUM_THREADS);
// 为每个 topic 创建任务,并将每个分区分配给线程
for (String topic : TOPICS) {
List<TopicPartition> partitions = new ArrayList<>();
for (int i = 0; i < 8; i++) {
partitions.add(new TopicPartition(topic, i));
}
for (int i = 0; i < partitions.size(); i += PARTITIONS_PER_THREAD) {
int end = Math.min(i + PARTITIONS_PER_THREAD, partitions.size());
List<TopicPartition> partitionsForThread = partitions.subList(i, end);
taskQueue.add(new KafkaConsumerTask(partitionsForThread));
}
}
// 提交所有任务
while (!taskQueue.isEmpty()) {
executorService.submit(taskQueue.poll());
}
executorService.shutdown();
}
// 每个消费者线程
static class KafkaConsumerTask implements Runnable {
private final List<TopicPartition> partitions;
private long lastPollTime = System.currentTimeMillis(); // 上次拉取时间
private long idleTime = 0; // 拉取不到数据的累计时长
public KafkaConsumerTask(List<TopicPartition> partitions) {
this.partitions = partitions;
}
@Override
public void run() {
Properties consumerProps = new Properties();
consumerProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, BOOTSTRAP_SERVERS)