Kafka
采用发布/订阅模型,以高吞吐量和低延迟为设计目标。
核心概念是日志(log)和分区(partition),它将消息持久化到磁盘,并允许以高效的方式进行批量读写。
设计目标是提供高吞吐量的数据流处理能力,适合处理海量数据和高并发场景。
Kafka 在需要处理大规模数据流、实时数据处理、日志收集和分析等场景中表现出色。它常用于大数据应用、日志处理、指标监控、流式处理等场景。
RabbitMQ
基于 AMQP(Advanced Message Queuing Protocol)协议的消息队列系统,它采用传统的消息队列模型,支持广泛的消息传递模式(如点对点、发布/订阅、请求/响应等)。
RabbitMQ 提供丰富的特性,如消息优先级、消息延迟、消息死信队列等,使得它更适合于一些需要灵活消息传递和处理的场景。
RabbitMQ 常用于任务分发、异步通信、事件驱动架构等场景。
RocketMQ
高吞吐、低延迟、保证消息顺序性
适用于可靠性要求比较高的金融互联网领域
ZooKeeper 是一个分布式协调服务,它提供了一个可靠的分布式系统的基础设施,用于协调和管理分布式应用程序的状态信息。
ZooKeeper 主要关注分布式系统的一致性和可靠性,提供了分布式锁、队列、通知机制等基础功能,并支持分布式协调和领导选举。
而同样作为注册中心的Nacos,是一个动态服务发现、配置管理和服务治理平台,旨在简化微服务架构下的服务注册、发现和配置管理。提供了服务注册与发现、动态配置管理、服务健康监测和流量管理等功能,以帮助构建弹性、可伸缩和可靠的微服务架构。因此更适用于构建和管理微服务架构,处理服务注册与发现、动态配置、服务健康监测等场景。
Kafka对Zookeeper强依赖,因此需要先运行zookeeper容器
docker pull zookeeper:3.4.14
docker run -d --name zookeeper -p 2181:2181 zookeeper:3.4.14
docker pull wurstmeister/kafka:2.12-2.3.1
docker run -d --name kafka \
--env KAFKA_ADVERTISED_HOST_NAME=192.168.133.128 \
--env KAFKA_ZO0KEEPER_CONNECT=192.168.133.128:2181 \
--env KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.133.128:9092\
--env KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 \
--env KAFKA_HEAP_OPTS="-Xmx256M -Xms256M"\
-p 9092:9092 \
wurstmeister/kafka:2.12-2.3.1
<dependency>
<groupId>org.apache.kafkagroupId>
<artifactId>kafka-clientsartifactId>
dependency>
//设置kafka基本信息
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFTG,"192.168.133.128:9092");//kafka链接地址
properties.put(ProducerConfig.KEY_SERIALITZER_CLASS_CONFTG,"org.apache.kafka.common.serialization.stringserializer");//消息key的序列化器
properties.put(ProducerConfig.VALUE_SERIAIZER_CLASS_CONFTG,"org.apache.kafka.common.serialization.stringSerializer");//消息value的序列化器
//创建生产者对象
KafkaProducer<String,String> producer = new KafkaProducer<String,String>(properties);
//发送消息
ProducerRecord<String,String> record = new ProducerRecord<String,String>("topic","key","value");
producer.send(record) ;
//关闭消息通道(必须关闭,否则消息发送不成功)
producer.close();
//连接信息
Properties properties = new Properties();
properties.put(ConsumerConfig.B00TSTRAP_SERVERS_CONFIG,"192.168.133.128:9092");
//指定消费者组
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"group1");
//反序列化的key和vaLlue
properties.put(ConsumerConfig.KEY_DESERIALIER_CLASS_CONFTG, "ong.apache.kafka.common.serialization.StringDeserializer");
properties.put(ConsumerConfig.VALUE_DESERLIALIZER_CLASS_CONFIG ,"ong.apache.kafka.common.serialization.StringDeserializer");
//创建消费者对象
KafkaConsumer<String,string> consumer = new KafkaConsumer<String,String>(properties);
//订阅主题
consumer.subscribe(Collections.singletonList("topic"));
while(true){
//获取消息(每秒拉取一次)
ConsumerRecords<String,String> consumerRecords = consumer.poll(Duration.ofMillis(1000));
}
每一个分区都是一个顺序的、不可变的消息队列,并且可以持续的添加。分区中的消息都被分了一个序列号,称之为偏移量(offset),在每个分区中此偏移量都是唯一的。
分区策略 | 说明 |
---|---|
轮询策略 | 按顺序轮流将每条数据分配到每个分区中 |
随机策略 | 每次都随机地将消息分配到每个分区 |
按键保存策略 | 生产者发送数据的时候,可以指定一个key,计算这个key的hashCode值,按照hashCode的值对不同消息进行存储 |
Broker
Kafka的服务器端由被称为Broker的服务进程构成,即一个Kafka集群由多个Broker组成
这样如果集群中某一台机器宕机,其他机器上的Broker也依然能够对外提供服务。这其实就是Kafka 提供高可用的手段之一
Kafka 中消息的备份又叫做副本(Replica)Kafka定义了两类副本:
领导者副本(Leader Replica)>追随者副本(Follower Replica)
直接使用KafkaProducer的send()
方法,返回的是Future对象,再调用get()方法就知道是否发送成功
调用KafkaProducer的send(ProducerRecord record, new Callback())
方法
producer.send(record,new Callback() {
@override
public void onCompletion(RecordMetadata recordMetadata,Exception e) {
if(e!=null){
e.printstackTrace();
}
system.out.println(recordMetadata.offset());
}
});
指定回调函数Callback(),服务器在返回时会调用Callback()方法,可用于异常处理等
消费者组(Consumer Group)︰指的就是由一个或多个消费者组成的群体
一个发布在Topic上消息被分发给此消费者组中的一个消费者
所有的消费者都在一个组中,那么这就变成了queue模型
所有的消费者都在不同的组中,那么就完全变成了发布-订阅模型
发送顺序和接收顺序一致
topic分区中消息只能由消费者组中的唯一一个消费者处理,所以消息肯定是按照先后顺序进行处理的。但是它也仅仅是保证Topic的一个分区顺序处理,不能保证跨分区的消息先后处理顺序。所以,如果你想要顺序的处理Topic的所有消息,那就只提供一个分区。
kafka不会像其他JMS队列那样需要得到消费者的确认,消费者可以使用kafka来追踪消息在分区的位置(偏移量)。消费者会往一个叫做_consumer_offset的特殊主题发送消息,消息里包含了每个分区的偏移量。如果消费者发生崩溃或有新的消费者加入群组,就会触发再均衡
//手动提交偏移量
properties.put(consumerConfig.ENABLE_AUTO_COMNMNIT_CONFIG,false);
ConsumerRecords<String,String> consumerRecords = consumer.poll(Duration.ofMillis(1000));
//同步和异步组合提交
try {
while(true){
ConsumerRecords<String,String> records = consumer.poll(Duration.ofMillis(1000));
for (ConsumerRecord<String,string> record : records) {
system.out.println(record.value());
system.out.println(record.key());
}
//先使用异步提交
consumer.commitAsync();
}
}catch (Exception e){
e.printstackTrace();
system.out.println("记录错误信息: "+e);
}finally {
try {
//异步提交失败再同步提交
consumer.commitsync();
}finally {
consumer.close();
}
}
<dependency>
<groupId>org.springframework.kafkagroupId>
<artifactId>spring-kafkaartifactId>
<exclusions>
<exclusion>
<groupid>org.apache.kafkagroupId>
<artifactId>kafka-clientsartifactid>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.apache.kafkagroupId>
<artifactId>kafka-clientsartifactId>
dependency>
server:
port: 9991
spring:
application:
name: kafka-demo
kafka:
bootstrap-servers: 192.168.133.128:9092 #kafka地址
producer:
retries: 10 #重试次数
key-serializer: org.apache.kafka.common.serialization.stringSerializer
value-serializer: org.apache.kafka.common.serialization.stringSerializer
consumer:
group-id: test-hello-group #组名称
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.stringDeserializer
@Autowired
private KafkaTemplate<String,string> kafkaTemplate;
@GetMapping("/hello")
public String hello(){
//第一个参数: topics
//第二个参数:消息内容
kafkaTemplate.send("kafka-hello","Hello");
return "ok";
}
@Component
public class HelloListener {
@KafkaListener(topics = "kafka-hello"})
public void onMessage(String message){
if(!Stringutils.isEmpty(message)){
system.out.println( message);
}
}
}