一、生产者
生成者是指消息的生成者,即将消息发送到指定的Topic中的生产者。生产者可以通过特定的分区函数决定消息路由到Topic的某个分区。
1、设计原则
生产者就是将消息发送到指定的Topic中。生产者本质上就是指定具体的Topic,然后向目的端Broker Server发送ProducerRequest请求,并且通过分区函数可以指定具体的消息至特定的分区。生产者内部会动态维护与Topic相关的Broker Server 的Socket链接。客户端无需手动维护。
特点:客户端消息发送时只需要指定Topic和消息,不需要指定目的端Broker Server。
2、发送模式
两种模式:同步模式、异步模式
在同步模式下发送消息,消息立刻发送到指定的Broker Server;
在异步模式下发送消息,消息并不是立刻发送到指定的Broker Server,而是缓存在客户端消息队列缓冲区,并且等待消息队列缓冲区是否达到一定数量或者间隔一定的时间之后才把消息发送出去。
2.1 同步模式
配置参数:producer.type = sync
生产者接收到多少信息,则立刻发送多少信息,并不会在客户端缓存消息。
需更新Topic元数据信息的场景:
A、发送前的定时更新机制
B、发送过程中针对消息进行分组,如果发现该消息的Topic元数据信息不存在,则需要更新
C、发送失败时的更新机制。当消息发送失败的时候,会进入重试流程,首先更新Topic的元数据信息,然后再次发送,如果发送成功,则直接退出。
同步模式实现过程图如下:
2.2 异步模式
配置参数:producer.type = async
生产者会缓存一定数量的消息或者达到一定的超时时间才会把接收到的消息发送出去。
异步模式实现过程图如下:
二、消费者
消息消费者是指获取消息的用户,kafka提供了两种不同的方式来获取消息:简单消费者和高级消费者。
简单消费者:获取消息时,用户需要知道待消费的消息位于哪个Topic的哪个分区,并且该目的的分区的leader Replica位于哪个Broker Server上;
高级消费者:获取消息时,用户不需要知道以上全部信息,用户只需要指定待消费的消息属于哪个Topic即可。
1、简单消费者
特点:消息可以重复被消费,即一个消息可以被读取多次,只要其其FetchRequest里面的参数相同即可。
实现流程:
2、高级消费者
高级消费者屏蔽了使用简单消费者所需要做的许多额外工作,以Consumer Group(消费者)的形式来管理消息的消费,以Stream(流)的形式来提供具体消息的读取。客户端在消费者的时候只需要知道消息所属的Topic即可,而不需要知道该Topic的具体元数据信息。
特点:
客户端以某个Consumer Group 消费消息时,消费过的消息无法再次消费,除非更换另外一个Consumer Group来消费消息。
实现流程:
三、基于springboot的代码实现
1、引入kafka的maven依赖,注意springboot的kafka的版本兼容问题。此处选择springboot 1.5.9.RELEASE
<!--kafka-->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
2、properties配置文件
server.port=8666
#============== kafka ===================
# 指定kafka 代理地址,可以多个
spring.kafka.bootstrap-servers=192.168.2.119:9092
#=============== provider =======================
spring.kafka.producer.retries=0
# 每次批量发送消息的数量
spring.kafka.producer.batch-size=16384
spring.kafka.producer.buffer-memory=33554432
# 指定消息key和消息体的编解码方式
spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
#=============== consumer =======================
# 指定默认消费者group id
spring.kafka.consumer.group-id=test-consumer-group
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.enable-auto-commit=true
spring.kafka.consumer.auto-commit-interval=100
# 指定消息key和消息体的编解码方式
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
3、消息VO代码
package com.krycai.springbootkafka.vo;
import lombok.Data;
import java.util.Date;
@Data
public class MessageVo {
private Long id;
private String msg;
private Date sendTime;
}
4、生成者代码,此处需要注意send()的方法,krycai是生成者消费的标志,为消费者消费时根据krycai进行消费。
package com.krycai.springbootkafka.provider;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.krycai.springbootkafka.vo.MessageVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class KafkaProvider {
@Autowired
private KafkaTemplate<String,String> kafkaTemplate;
private Gson gson =new GsonBuilder().create();
//发送消息方法
public void send(MessageVo message) {
log.info("+++++++++++++++++++++ message = {}", gson.toJson(message));
kafkaTemplate.send("krycai", gson.toJson(message));
}
}
5、消费者,此处博主单独定为业务具体的实现扩展类代码
package com.krycai.springbootkafka.consumer;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@Slf4j
public class KafkaConsumer {
public void listen(ConsumerRecord<?, ?> record) {
Optional<?> kafkaMessage = Optional.ofNullable(record.value());
if (kafkaMessage.isPresent()) {
Object message = kafkaMessage.get();
log.info("----------------- record =" + record);
log.info("------------------ message =" + message);
}
}
}
6、controller类的实现方式。此处重点是@KafkaListener(topics = {"krycai"}),KafkaListener监听未消费的信息。生成者存在的未读消费的信息,都会被KafkaListener监听到。
package com.krycai.springbootkafka.contronller;
import com.krycai.springbootkafka.consumer.KafkaConsumer;
import com.krycai.springbootkafka.provider.KafkaProvider;
import com.krycai.springbootkafka.vo.MessageVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Date;
import java.util.UUID;
@Controller
@Slf4j
@RequestMapping("kafka")
public class KafkaContronller {
@Autowired
private KafkaProvider kafkaProvider;
@Autowired
private KafkaConsumer kafkaConsumer;
/**
* 发送信息
*/
@GetMapping("sendMessage")
public void sendMessage(){
for (int i = 0;i<5;i++){
MessageVo message = new MessageVo();
message.setId(System.currentTimeMillis());
message.setMsg(UUID.randomUUID().toString());
message.setSendTime(new Date());
kafkaProvider.send(message);
}
}
@PostMapping("pullMessage")
@KafkaListener(topics = {"krycai"})
public void pullMessage(ConsumerRecord<?, ?> record){
log.info("===============================");
kafkaConsumer.listen(record);
}
}
7、启动类
package com.krycai.springbootkafka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.kafka.annotation.EnableKafka;
@SpringBootApplication
@ComponentScan(value = {"com.krycai.springbootkafka"})
public class KafkaApp {
public static void main(String[] args) {
SpringApplication.run(KafkaApp.class, args);
}
}