介绍
用途
- 流处理:Kafka 非常适合用于构建实时数据管道,支持高吞吐量的数据摄入。
- 日志聚合:可以作为日志收集系统,集中管理来自多个来源的日志。
- 事件源和 CQRS:适用于事件驱动架构中,作为事件存储和分发机制。
优缺点
- 优点
- 高吞吐量:能够处理大量的消息而不影响性能。
- 持久性和容错性:通过分区和副本机制提供强大的数据冗余。
- 水平扩展性:容易添加更多的 broker 来扩展集群。
- 内置流处理:提供了 Kafka Streams API 支持流式数据处理。
- 缺点
- 复杂度较高:相比一些更简单的消息队列,Kafka 的配置和运维可能更加复杂。
- 依赖 Zookeeper:传统模式下需要额外部署 Zookeeper(不过新版本引入了 KRaft 模式减少对 Zookeeper 的依赖)。
特性/产品 | Apache Kafka | RabbitMQ | ActiveMQ | Amazon SQS |
---|---|---|---|---|
用途 | - 流处理 - 日志聚合 - 事件源和 CQRS | - 工作队列 - 发布/订阅模式 - 路由和主题交换 | - 企业级集成 - 事务支持 | - 解耦服务 - 缓冲和流量削峰 |
优点 | - 高吞吐量 - 持久性和容错性 - 水平扩展性 - 内置流处理 | - 易用性 - 多协议支持 - 丰富的插件生态 | - 成熟稳定 - 全面的功能集 - 多语言客户端库 | - 完全托管服务 - 全球可用性 - 按需付费 |
缺点 | - 复杂度较高 - 依赖 Zookeeper | - 性能限制 - 单点故障风险 | - 性能瓶颈 - 资源消耗大 | - 网络延迟 - 有限的定制选项 |
springboot 项目导入依赖
<!--kafka-->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
在application.yml 添加配置
spring:
kafka:
bootstrap-servers: 192.168.2.104:9092,192.168.2.104:9093,192.168.2.104:9094
producer:
# 消息重发的次数
#retries: 0
#一个批次内存大小
batch-size: 16384
# 设置生产者内存缓冲区的大小。
buffer-memory: 33554432
# 键的序列化方式
key-serializer: org.apache.kafka.common.serialization.StringSerializer
# 值的序列化方式
value-serializer: org.apache.kafka.common.serialization.StringSerializer
#配置事务必须为 all 或者 -1
acks: all
#事务id
transaction-id-prefix: demo-tran-
consumer:
# 自动提交的时间间隔 需要符合特定的格式,如1S,1M,2H,5D
auto-commit-interval: 1S
# 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
auto-offset-reset: earliest
# 是否自动提交偏移量
enable-auto-commit: false
# 键的反序列化方式
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
# 值的反序列化方式
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
listener:
#手工ack,调用ack后立刻提交offset
ack-mode: manual_immediate
#容器运行的线程数
concurrency: 6
#避免出现主题未创建报错
missing-topics-fatal: false
发送消息
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaOperations;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.transaction.annotation.Transactional;
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;
@Slf4j
@RestController
@RequestMapping("/api/v1")
public class UserController {
private static final String TOPIC_NAME = "user.demo.topic";
@Autowired
private KafkaTemplate<String,Object> kafkaTemplate;
@GetMapping("/register/{num}")
public void sendMessage(@PathVariable("num") String num){
kafkaTemplate.send(TOPIC_NAME,String.format("消息内容:%s",num)).addCallback(succsess->{
String topic = succsess.getRecordMetadata().topic();
long offset = succsess.getRecordMetadata().offset();
int partition = succsess.getRecordMetadata().partition();
log.info("发送成功:topic->{},partition->{} , offset->{}",topic,partition,offset);
},failure->{
log.info("发送失败:{}",failure.getMessage());
});
}
/**
* 注解方式的事务
* @param i
*/
@GetMapping("/register/tran1")
@Transactional(rollbackFor = RuntimeException.class)
public void sendMessage2(int num){
kafkaTemplate.send(TOPIC_NAME, "这个是事务里面的消息:1 i="+num);
if (num == 0) {
throw new RuntimeException("fail");
}
kafkaTemplate.send(TOPIC_NAME, "这个是事务里面的消息:2 i="+num);
}
/**
* 声明式事务支持
* @param num
*/
@GetMapping("/register/tran2")
public void sendMessage3(int num){
kafkaTemplate.executeInTransaction(new KafkaOperations.OperationsCallback() {
@Override
public Object doInOperations(KafkaOperations kafkaOperations) {
kafkaOperations.send(TOPIC_NAME,"这个是事务里面的消息:1 i="+num);
if(num==0)
{
throw new RuntimeException("input is error");
}
kafkaOperations.send(TOPIC_NAME,"这个是事务里面的消息:2 i="+num);
return true;
}
});
}
}
配置监听
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.kafka.support.KafkaHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MQListener {
@KafkaListener(topics = {"user.demo.topic"},groupId = "demo-g1")
public void onMessage(ConsumerRecord<?, ?> record, Acknowledgment ack, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic){
try {
log.info("主题:topic:{},partition:{}",record.topic(),record.partition());
log.info("消费消息内容:{}",record.value());
} finally {
ack.acknowledge();
}
}
}