首先创建一个名为kafka-basis的springboot项目,添加kafka的依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.kafkagroupId>
<artifactId>spring-kafkaartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
Kafka producer就是负责向Kafka写入数据的应用程序,在Kafka多种使用场景中,producer都是必要的组件。
server:
port: 8080
spring:
kafka:
bootstrap-servers: xxx:9092
# 生产者
producer:
acks: 1
retries: 0
batch-size: 16384
buffer-memory: 33554432
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
properties:
linger:
ms: 0
mq:
kafka:
topic: topic
partition: 3
replica: 1
其中常用的配置如下:
创建Topic并设置分区数和副本数
@Configuration
public class KafkaInitialConfiguration {
//名称
@Value("${mq.kafka.topic}")
private String topic;
//分区数
@Value("${mq.kafka.partition}")
private Integer partition;
//副本数
@Value("${mq.kafka.replica}")
private short replica;
@Bean
public NewTopic initialTopic() {
return new NewTopic(topic, partition, replica);
}
}
KafkaTemplate调用send时默认采用异步发送,源码如下:
protected ListenableFuture<SendResult<K, V>> doSend(final ProducerRecord<K, V> producerRecord) {
final Producer<K, V> producer = getTheProducer(producerRecord.topic());
this.logger.trace(() -> "Sending: " + KafkaUtils.format(producerRecord));
final SettableListenableFuture<SendResult<K, V>> future = new SettableListenableFuture<>();
Object sample = null;
if (this.micrometerEnabled && this.micrometerHolder == null) {
this.micrometerHolder = obtainMicrometerHolder();
}
if (this.micrometerHolder != null) {
sample = this.micrometerHolder.start();
}
Future<RecordMetadata> sendFuture =
producer.send(producerRecord, buildCallback(producerRecord, producer, future, sample));
// May be an immediate failure
if (sendFuture.isDone()) {
try {
sendFuture.get();
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new KafkaException("Interrupted", e);
}
catch (ExecutionException e) {
throw new KafkaException("Send failed", e.getCause()); // NOSONAR, stack trace
}
}
if (this.autoFlush) {
flush();
}
this.logger.trace(() -> "Sent: " + KafkaUtils.format(producerRecord));
return future;
}
private Callback buildCallback(final ProducerRecord<K, V> producerRecord, final Producer<K, V> producer,
final SettableListenableFuture<SendResult<K, V>> future, @Nullable Object sample) {
return (metadata, exception) -> {
try {
if (exception == null) {
if (sample != null) {
this.micrometerHolder.success(sample);
}
future.set(new SendResult<>(producerRecord, metadata));
if (KafkaTemplate.this.producerListener != null) {
KafkaTemplate.this.producerListener.onSuccess(producerRecord, metadata);
}
KafkaTemplate.this.logger.trace(() -> "Sent ok: " + KafkaUtils.format(producerRecord)
+ ", metadata: " + metadata);
}
else {
if (sample != null) {
this.micrometerHolder.failure(sample, exception.getClass().getSimpleName());
}
future.setException(new KafkaProducerException(producerRecord, "Failed to send", exception));
if (KafkaTemplate.this.producerListener != null) {
KafkaTemplate.this.producerListener.onError(producerRecord, metadata, exception);
}
KafkaTemplate.this.logger.debug(exception, () -> "Failed to send: "
+ KafkaUtils.format(producerRecord));
}
}
finally {
if (!KafkaTemplate.this.transactional) {
closeProducer(producer, false);
}
}
};
}
send方法会返回一个回调方法,如果我们不调用get方法,当消息发送成功时候,发送结果会被回调给注册过的listener进行通知,默认实现是LoggingProducerListener,我们也可以自定义实现。我们实现一个简单的发送示例:
@RestController
public class KafkaProducerController {
private final static Logger logger = LoggerFactory.getLogger(KafkaProducerController.class);
@Resource
private KafkaTemplate<String, Object> kafkaTemplate;
/**
* 默认异步发送,带有回调方法
*
* @param message
*/
@PostMapping("/sendMessage")
public void sendMessage(String message) {
kafkaTemplate.send("topic", message).addCallback(success -> logger.info("发送成功----内容:" + success.getProducerRecord().value() + ",topic:"
+ success.getRecordMetadata().topic() + ",partition:" + success.getRecordMetadata().partition()),
fail -> logger.info("发送失败----" + fail.getMessage()));
}
/**
* 同步发送:get方法会一直阻塞,直到结果返回
*
* @param message
* @throws Exception
*/
@PostMapping("/sendSyncMessage")
public void sendSyncMessage(String message) throws Exception {
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send("topic", message);
//获取发送结果,会一直等待,指导达到设置的超时时间
SendResult<String, Object> result = future.get(3, TimeUnit.SECONDS);
logger.info("发送成功----内容:" + result.getProducerRecord().value() + ",topic:"
+ result.getRecordMetadata().topic() + ",partition:" + result.getRecordMetadata().partition());
}
}
public class KafkaProducerListener<K, V> implements ProducerListener<K, V> {
private final static Logger logger = LoggerFactory.getLogger(KafkaProducerListener.class);
/**
* 成功处理
*
* @param producerRecord
* @param recordMetadata
*/
@Override
public void onSuccess(ProducerRecord<K, V> producerRecord, RecordMetadata recordMetadata) {
logger.info(producerRecord.value() + ",发送成功");
}
/**
* 失败处理
*
* @param producerRecord
* @param recordMetadata
* @param exception
*/
@Override
public void onError(ProducerRecord<K, V> producerRecord, RecordMetadata recordMetadata, Exception exception) {
logger.error(producerRecord.value() + ",发送失败,异常为:" + exception.getMessage());
}
}
@Configuration
public class KafkaConfiguration {
/**
* 配置producerListener
*
* @return
*/
@Bean
public ProducerListener<Object, Object> producerListener(){
return new KafkaProducerListener<>();
}
}
自定义分区示例如下:
spring:
kafka:
bootstrap-servers: xxx:9092
# 生产者
producer:
acks: 1
retries: 0
batch-size: 16384
buffer-memory: 33554432
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
properties:
linger:
ms: 0
#自定义分区器
partitioner:
class: com.fengfan.kafkbasis.config.MyKafkaPartitioner
public class MyKafkaPartitioner implements Partitioner {
@Override
public int partition(String s, Object o, byte[] bytes, Object o1, byte[] bytes1, Cluster cluster) {
//全部发送到1这个分区
return 1;
}
@Override
public void close() {
}
@Override
public void configure(Map<String, ?> map) {
}
}
Kafka默认提供了十几种序列化器,其中常用的serializer如下,反序列化也相似:
自定义序列化
spring:
kafka:
bootstrap-servers: xx:9092
# 生产者
producer:
acks: 1
retries: 0
batch-size: 16384
buffer-memory: 33554432
key-serializer: org.apache.kafka.common.serialization.StringSerializer
# value-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: com.fengfan.kafkbasis.config.MySerializer
properties:
linger:
ms: 0
#自定义分区器
partitioner:
class: com.fengfan.kafkbasis.config.MyKafkaPartitioner
/**
* 自定义序列化发送
*
* @param message
*/
@PostMapping("/sendSerializerMessage")
public void sendSerializercMessage(String message) {
KafkaMessageEntity<String> kafkaMessageEntity = new KafkaMessageEntity<>();
kafkaMessageEntity.setData(message);
kafkaMessageEntity.setId(UUID.randomUUID().toString());
kafkaTemplate.send("topic", kafkaMessageEntity);
}
Kafka消费者(consumer)是从Kafka读取数据的应用,若干个consumer订阅Kafka集群中的若干个topic并从Kafka接收属于这些topic的消息。
消费者使用一个消费者组名(即group.id)来标记自己,topic的每条消息都只会被发送到每个订阅它的消费者组的一个消费者实例上。其中含义如下:
Kafka是通过consumer group实现的对队列和发布/订阅模式的支持:
spring:
kafka:
bootstrap-servers: 192.168.159.135:9092
consumer:
group-id: defaul_group
enable-auto-commit: true
auto-commit-interval: 100
auto-offset-reset: latest
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
@KafkaListener(topics = "topic")
public void onMessage(ConsumerRecord<?, ?> record){
System.out.println("消费者消费,record:"+record.topic()+"-"+record.partition()+"-"+record.value());
}
客户端 consumer 接收消息特别简单,直接用 @KafkaListener 注解即可,并在监听中设置监听的 topic。属性如下:
consumer group rebalance 本质上是一组协议,它规定了一个 consumer group 是如何达成一致来分配订阅 topic 的所有分区的。假设某个组下有 20 consumer 实例,该组订阅了有着 100 个分区的 topic 正常情况下, Kafka 会为每个 consumer 平均分配 个分区。这个分配过程就被称为 rebalance,。对于每个组而言, Kafka 的某个broker 会被选举为组协调者( group coordinator) o。coordinator 负责对组的状态进行管理,它的主要职责就是当新成员到达时促成组内所有成员达成新的分区分配方案,即coordinator 负责对组执行 rebalance 操作。
rebalance 触发的条件有以下3个:
consumer group 在执行 rebalance 之前必须首先确定 coordinator 所在的 broker ,并创建与该
broker 相互通信的 Socket 连接。确定 coordinator 的算法与确定 offset 被提交到 _consumer_offsets 目标分区的算法是相同的。