Kafka简介
消息队列
-
消息队列——用于存放消息的组件
-
程序员可以将消息放入到队列中,也可以从消息队列中获取消息
-
很多时候消息队列不是一个永久性的存储,是作为临时存储存在的(设定一个期限:设置消息在MQ中保存10天)
-
消息队列中间件:消息队列的组件,例如:Kafka、Active MQ、RabbitMQ、RocketMQ、ZeroMQ
Kafka的应用场景
-
异步处理
-
可以将一些比较耗时的操作放在其他系统中,通过消息队列将需要进行处理的消息进行存储,其他系统可以消费消息队列中的数据
-
比较常见的:发送短信验证码、发送邮件
-
-
系统解耦
-
原先一个微服务是通过接口(HTTP)调用另一个微服务,这时候耦合很严重,只要接口发生变化就会导致系统不可用
-
使用消息队列可以将系统进行解耦合,现在第一个微服务可以将消息放入到消息队列中,另一个微服务可以从消息队列中把消息取出来进行处理。进行系统解耦
-
-
流量削峰
-
因为消息队列是低延迟、高可靠、高吞吐的,可以应对大量并发
-
-
日志处理
-
可以使用消息队列作为临时存储,或者一种通信管道
-
消息队列的两种模型
-
生产者、消费者模型
-
生产者负责将消息生产到MQ中
-
消费者负责从MQ中获取消息
-
生产者和消费者是解耦的,可能是生产者一个程序、消费者是另外一个程序
-
-
消息队列的模式
-
点对点:一个消费者消费一个消息
-
发布订阅:多个消费者可以消费一个消息
-
Kafka集群搭建
-
Kafka集群是必须要有ZooKeeper的
注意:
-
每一个Kafka的节点都需要修改broker.id(每个节点的标识,不能重复)
-
log.dir数据存储目录需要配置
Kafka的生产者/消费者/工具
-
安装Kafka集群,可以测试以下
-
创建一个topic主题(消息都是存放在topic中,类似mysql建表的过程)
-
基于kafka的内置测试生产者脚本来读取标准输入(键盘输入)的数据,并放入到topic中
-
基于kafka的内置测试消费者脚本来消费topic中的数据
-
-
推荐大家开发的使用Kafka Tool
-
浏览Kafka集群节点、多少个topic、多少个分区
-
创建topic/删除topic
-
浏览ZooKeeper中的数据
-
Kafka的基准测试工具
-
Kafka中提供了内置的性能测试工具
-
生产者:测试生产每秒传输的数据量(多少条数据、多少M的数据)
5000000 records sent, 11825.446943 records/sec (11.28 MB/sec), 2757.61 ms avg latency
-
消费者:测试消费每条拉取的数据量
-
-
对比生产者和消费者:消费者的速度更快
Kafka Java API开发
生产者程序开发
-
创建连接
-
bootstrap.servers:Kafka的服务器地址
-
acks:表示当生产者生产数据到Kafka中,Kafka中会以什么样的策略返回
-
key.serializer:Kafka中的消息是以key、value键值对存储的,而且生产者生产的消息是需要在网络上传到的,这里指定的是StringSerializer方式,就是以字符串方式发送(将来还可以使用其他的一些序列化框架:Google ProtoBuf、Avro)
-
value.serializer:同上
-
-
创建一个生产者对象KafkaProducer
-
调用send方法发送消息(ProducerRecor,封装是key-value键值对)
-
调用Future.get表示等带服务端的响应
-
关闭生产者
public class KafkaProducerTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1. 创建用于连接Kafka的Properties配置
Properties props = new Properties();
props.put("bootstrap.servers", "node1.itcast.cn:9092");
props.put("acks", "all");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
// 2. 创建一个生产者对象KafkaProducer
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(props);
// 3. 发送1-100的消息到指定的topic中
for(int i = 0; i < 100; ++i) {
// 构建一条消息,直接new ProducerRecord
ProducerRecord<String, String> producerRecord = new ProducerRecord<>("test", null, i + "");
Future<RecordMetadata> future = kafkaProducer.send(producerRecord);
// 调用Future的get方法等待响应
future.get();
System.out.println("第" + i + "条消息写入成功!");
}
// 4.关闭生产者
kafkaProducer.close();
}
}
消费者程序开发
-
group.id:消费者组的概念,可以在一个消费组中包含多个消费者。如果若干个消费者的group.id是一样的,表示它们就在一个组中,一个组中的消费者是共同消费Kafka中topic的数据。
-
Kafka是一种拉消息模式的消息队列,在消费者中会有一个offset,表示从哪条消息开始拉取数据
-
kafkaConsumer.poll:Kafka的消费者API是一批一批数据的拉取
/**
* 消费者程序
*
* 1.创建Kafka消费者配置
* Properties props = new Properties();
* props.setProperty("bootstrap.servers", "node1.itcast.cn:9092");
* props.setProperty("group.id", "test");
* props.setProperty("enable.auto.commit", "true");
* props.setProperty("auto.commit.interval.ms", "1000");
* props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
* props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
*
* 2.创建Kafka消费者
* 3.订阅要消费的主题
* 4.使用一个while循环,不断从Kafka的topic中拉取消息
* 5.将将记录(record)的offset、key、value都打印出来
*/
public class KafkaConsumerTest {
public static void main(String[] args) {
// 1.创建Kafka消费者配置
Properties props = new Properties();
props.setProperty("bootstrap.servers", "node1.itcast.cn:9092");
// 消费者组(可以使用消费者组将若干个消费者组织到一起),共同消费Kafka中topic的数据
// 每一个消费者需要指定一个消费者组,如果消费者的组名是一样的,表示这几个消费者是一个组中的
props.setProperty("group.id", "test");
// 自动提交offset
props.setProperty("enable.auto.commit", "true");
// 自动提交offset的时间间隔
props.setProperty("auto.commit.interval.ms", "1000");
// 拉取的key、value数据的
props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
// 2.创建Kafka消费者
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<>(props);
// 3. 订阅要消费的主题
// 指定消费者从哪个topic中拉取数据
kafkaConsumer.subscribe(Arrays.asList("test"));
// 4.使用一个while循环,不断从Kafka的topic中拉取消息
while(true) {
// Kafka的消费者一次拉取一批的数据
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(5));
// 5.将将记录(record)的offset、key、value都打印出来
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
// 主题
String topic = consumerRecord.topic();
// offset:这条消息处于Kafka分区中的哪个位置
long offset = consumerRecord.offset();
// key\value
String key = consumerRecord.key();
String value = consumerRecord.value();
System.out.println("topic: " + topic + " offset:" + offset + " key:" + key + " value:" + value);
}
}
}
}
生产者使用异步方式生产消息
-
使用匿名内部类实现Callback接口,该接口中表示Kafka服务器响应给客户端,会自动调用onCompletion方法
-
metadata:消息的元数据(属于哪个topic、属于哪个partition、对应的offset是什么)
-
exception:这个对象Kafka生产消息封装了出现的异常,如果为null,表示发送成功,如果不为null,表示出现异常。
-
// 二、使用异步回调的方式发送消息
ProducerRecord<String, String> producerRecord = new ProducerRecord<>("test", null, i + "");
kafkaProducer.send(producerRecord, new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
// 1. 判断发送消息是否成功
if(exception == null) {
// 发送成功
// 主题
String topic = metadata.topic();
// 分区id
int partition = metadata.partition();
// 偏移量
long offset = metadata.offset();
System.out.println("topic:" + topic + " 分区id:" + partition + " 偏移量:" + offset);
}
else {
// 发送出现错误
System.out.println("生产消息出现异常!");
// 打印异常消息
System.out.println(exception.getMessage());
// 打印调用栈
System.out.println(exception.getStackTrace());
}
}
});