Kafka是一种高吞吐量的分布式发布订阅消息系统。那么Kafka的高并发究竟如何实现呢,对于一个新手来说是一脸的茫然,感觉一点都不好用,还不如activemq好用。
经过一番实践,终于搞清楚了kafka的并发,这里分享给大家,欢迎批评指正。
1、搭建Kafka环境。
下载windows版本或linux版本的kafka,我这里的版本是kafka_2.12-2.2.0。这里以windows环境下的为例:
下载后直接解压到磁盘,直接启动即可。注意启动时先启动zookeeper,再启动kafka。
命令具体如下:
$ bin\windows\zookeeper-server-start.bat config\zookeeper.properties
$ bin\windows\kafka-server-start.bat config\server.properties
2、创建主题
bin\windows\kafka-topics.bat --create --zookeeper localhost:2181 --partitions 1 --replication-factor 1 --topic demo
如果不在这里创建,也可以在项目中配置主题名称,主题将在项目启动时自动创建。但此时的分区数量和副本数量将是默认的。分区默认值为1,副本数量也为1.,此配置在kafka的配置文件中,具体如下:
创建成功后,可通过命令查看到已创建主题的信息。
3、创建微服务项目,在pom文件中引入如下的jar包:
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-streams</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
在该项目中创建一个测试类,代码如下:
package com.demo.zjjg.kafka.stream;
import java.util.Properties;
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
public class HelloProducer {
public static void main(String[] args) {
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("acks", "all");
props.put("retries", 0);
props.put("batch.size", 16384);
props.put("linger.ms", 1);
props.put("buffer.memory", 33554432);
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
Producer<String,String> producer = new KafkaProducer<String,String>(props);
for(int i = 0;i < 12; i++) {
producer.send(new ProducerRecord<String,String>("demo",null,Integer.toString(1)),new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
if(null != exception) {
exception.printStackTrace();
}else {
System.err.println("callback: " + metadata.topic() + " " + metadata.partition() + " " + metadata.offset());
}
}
});
}
producer.close();
}
}
运行该main方法,打印结果如下:
说明这些消息发送到了demo主题中,第0个分区,也就是唯一的一个分区。
4、新建一个消费者项目,本demo依旧使用springboot框架创建,yml文件配置如下:
spring:
kafka:
listener:
concurrency: 1
consumer:
group-id: abc
bootstrap-servers:
- localhost:9092
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
消费者类源码如下:
package com.example.demo.kafka;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
/**
* @author xingzhej
*
*/
@Slf4j
public class MsgConsumer {
public static final String KAFKA_LOG_QUENE_NAME="demo";
@KafkaListener(topics = {MsgConsumer.KAFKA_LOG_QUENE_NAME})
public void receive(ConsumerRecord consumerRecord){
try {
System.out.println("####################################begin");
System.out.println("线程ID:"+Thread.currentThread().getId());
System.out.println("partition"+consumerRecord.partition());
System.out.println("####################################end");
}catch(Exception ex) {
ex.printStackTrace();
}
}
}
执行后,结果如下:
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
将消费者应用kafka消费者线程数改为3
spring:
kafka:
listener:
concurrency: 3
consumer:
group-id: abc
bootstrap-servers:
- localhost:9092
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
再次重新启动该醒目,执行结果如下:
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
说明多线程没起作用,为什么呢?
原来,kafka的多线程跟分区紧密相关。一个分区中的消息只能被一个进程中的一个线程消费。当然如果两个进程的groupid不一致,那么消息会全量广播给两个进程。
所以,为了使得多线程发挥作用,只能增加分区,经研究表明,分区数量和线程总量(所有进程的线程数量之和)一致是效率最高的。具体设置几个分区根据硬件配置和业务需要来确定。
好了,这里我们将分区数量修改为3.修改命令如下:
[E:\05.AppServer\kafka_2.12-2.2.0\bin\windows]$ kafka-topics.bat --alter --zookeeper localhost:2181 --topic demo --partitions 3
WARNING: If partitions are increased for a topic that has a key, the partition logic or ordering of the messages will be affected
Adding partitions succeeded!
修改结果查看如下:
重新测试生产者数据:运行HelloProducer的main方法,运行结果如下:
说明数据已经分别发送到demo主题的3个分区中。
这时,消费者端运行结果如下:
####################################begin
####################################begin
线程ID:23
线程ID:21
partition0
partition1
####################################end
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
####################################begin
线程ID:23
partition1
####################################end
线程ID:21
partition0
####################################end
####################################begin
线程ID:21
partition0
####################################end
####################################begin
线程ID:23
partition1
####################################end
####################################begin
线程ID:23
partition1
####################################end
####################################begin
线程ID:25
partition2
####################################end
####################################begin
线程ID:25
partition2
####################################end
####################################begin
线程ID:25
partition2
####################################end
####################################begin
线程ID:25
partition2
####################################end
三个线程分别获取到三个分区的数据进行处理。并发实现了。
当然你可以启动两个微服务,如果每个微服务消费者线程配置为3的话,总共是6个线程,相应的分片应该增加为6,否则有三个线程将不起作用。注意,要用两个进程实现并发效果,两个进程的groupid必须一致。否则将是广播效果。