一. 版本
Zookeeper
: zookeeper-3.4.14Kafk-server
: kafka_2.12-2.2.1Spring Boot
: 2.1.6.RELEASESpring Kafka
: 2.2.7.RELEASE
二. 项目创建
-
将
Zookeeper
和Kafka-Server
对应版本下载并启动 -
使用Idea创建SpringBoot项目时, 直接引入SpringKafka. Lombok, Web三个插件
若是手动引入SpringKafka, Maven代码如下:
<dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>2.2.7.RELEASE</version> </dependency>
三. 类介绍
在开始撸代码之前我们要大致的先了解各个类有什么含义
3.1 生产者
-
ProducerConfig
: 该类提供了生产者对象有哪些配置, 以及配置的详细描述 -
KafkaProducer
: Kafka生产者对象 -
DefaultKafkaProducerFatory
: 系统默认的创建KafkaProducer
的工厂类, 其中采用单例模式的方式创建KafkaProducer
对象, 方法如下:@Override public Producer<K, V> createProducer() { // ... if (this.producer == null) { synchronized (this) { if (this.producer == null) { // 采用单例模式创建KafkaProducer对象 this.producer = new CloseSafeProducer<K, V>(createKafkaProducer()); } } } return this.producer; }
-
KafkaTemplate
: 该类实现了KafkaOperations
接口, 主要就是用于定义生产者发送消息的方式public interface KafkaOperations<K, V> { // 消息只包含 消息实体 ListenableFuture<SendResult<K, V>> sendDefault(V data); // 消息有 键/值 组成 ListenableFuture<SendResult<K, V>> sendDefault(K key, V data); // 该消息指定了 键/值 并制定了消息所在的分区 ListenableFuture<SendResult<K, V>> sendDefault(Integer partition, K key, V data); // 该消息指定了 键/值 并制定了消息所在的分区并且指定了消息的时间戳 ListenableFuture<SendResult<K, V>> sendDefault(Integer partition, Long timestamp, K key, V data); }
-
ProducerRecord
: 具体发送的消息对象 -
ListenableFuture
: 是一个Future对象, 发送消息之后用来获取服务器返回信息 -
SendResult
: 发送消息的之后服务器具体的返回的封装对象
3.2 消费者
ConsumerConfig
: 该类提供了消费者对象有哪些属性配置, 以及介绍各个配置的含义KafkaConsumer
: 消费者对象DefaultKafkaConsumerFactory
: 具体用来创建KafkaConsumer
对象的对象,MessageListener
: 该接口继承GenericMessageListener
, 专门用来给用户重写onmessage()
方法来处理收到的消息KafkaMessageListenerContainer
: 消费者对象的依赖类, 该类专门用来启动和关闭该消费者服务器ContainerProperties
: 用来定义KafkaMessageListenerContainer
的属性, 比如MessageListener
和当前消费者消费的topic
主题等ComsumerRecord
: 具体消费的对象
3.3 特殊类
KafkaProperties
该类是Kafka在SpringBoot中的配置类, 我们可以在里面看到默认的配置信息,也可以在配置文件中修改配置信息KafkaListener
该类是一个注解类, 其作用相当于创建一个KafkaMessageListenerContainer
来监听当前的主题中的消息, 其注解的方法实体部分就是设置的MessageListener
, 其实就是处理该主题中的消息的方法.
四. 实现
在这里我们模拟通过请求传输主题和消息, 让生产者发送该主题和消息到服务器上, 最后通过监听该主题的消费者打印消息的具体内容.
4.1 配置
# kafka的配置信息具体参照 KafkaProperties 类
spring.kafka.consumer.group-id=test
# 消费者获取到的偏移量错误时从分区的起始位置读取, 注意要是用了该配置的话, 那么在启动项目的时候消费者会消费主题对应分区中的消息, 出现重复消费消息的现象. 具体配置大家可以参考配置说明
spring.kafka.consumer.auto-offset-reset=earliest
4.2 控制器层
package com.pyi.kafka.demo.controller;
import com.pyi.kafka.demo.service.impl.HelloServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* Slf4j 是Lombok插件的打印日志注解
* @author pyi
* @date 2019-06-26
*/
@RestController
@RequestMapping("hello")
@Slf4j
public class HelloController {
@Resource
private HelloServiceImpl helloService;
/**
* 这里我们监听请求携带主题和信息, 然后想服务器发送消息供消费者消费
* @param topic 主题
* @param message 信息
* @return String
*/
@GetMapping("hello/{topic}/{message}")
public String hello(@PathVariable String topic, @PathVariable String message) {
log.info("Topic = {}, Message = {}", topic, message);
helloService.hello(topic, message);
return "success";
}
}
4.3 业务逻辑层
package com.pyi.kafka.demo.service.impl;
import com.pyi.kafka.demo.service.HelloService;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.stereotype.Service;
import org.springframework.util.concurrent.ListenableFuture;
import javax.annotation.Resource;
/**
* @author pyi
* @date 2019-06-26
*/
@Service
public class HelloServiceImpl implements HelloService {
@Resource
private KafkaTemplate<Integer, String> kafkaTemplate;
@Override
public void hello(String topic, String message) {
// 这里如果我们忽略消息是否发送成功(生产者 acks = 0)的时候就不用处理返回值, 增强系统的吞吐量, 但是消息可能会丢失
handlerSendRecord(kafkaTemplate.send(topic, message));
}
private void handlerSendRecord(ListenableFuture<SendResult<Integer, String>> resultListenableFuture) {
try {
//这里我们可以获取到生产者消息是否提交成功
SendResult<Integer, String> integerStringSendResult = resultListenableFuture.get();
RecordMetadata recordMetadata = integerStringSendResult.getRecordMetadata();
// 打印消息被存储到哪个分区, 当前偏移量是多少
System.out.println("partition = " + recordMetadata.partition());
System.out.println("offset = " + recordMetadata.offset());
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.4 消费者监听器
package com.pyi.kafka.demo.listener;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
/**
* 定义专门处理消息方法
* @author pyi
* @date 2019-06-26
*/
@Slf4j
@Component
public class Listener {
/**
* KafkaListener注解 相当于 `KafkaMessageListenerContainer` 消费者的监听器, 方法实体部分相当于具体处理的消息内容
* @param consumerRecord
*/
@KafkaListener(id = "container_1", topics = {"topic1", "topic2"})
public void test1(ConsumerRecord<?, ?> consumerRecord) {
System.out.println(consumerRecord.toString());
}
}
五. 测试
2019-06-26 18:37:32.612 INFO 7384 --- [nio-8080-exec-3] c.p.k.demo.controller.HelloController : Topic = topic1, Message = demoData
partition = 0
offset = 16
ConsumerRecord(topic = topic1, partition = 0, offset = 16, CreateTime = 1561545452614, serialized key size = -1, serialized value size = 8, headers = RecordHeaders(headers = [], isReadOnly = false), key = null, value = demoData)