Kafka 分布式消息系统

文章目录


消息中间件对比


Kafka概述

Kafka 是一个分布式流媒体平台,类似于消息队列或企业消息传递系统。kafka官网:http://kafka.apache.org/

  • Producers:消息的生产者
  • Consumers:消息的消费者
  • Kafka Cluster:Kafka 集群
  • Connectors:连接器
  • Stream Processors:Stream 流处理
  • producer:发布消息的对象称之为主题生产者(Kafka topic producer)
  • topic:Kafka将消息分门别类,每一类的消息称之为一个主题(Topic)
  • consumer:订阅消息并处理发布的消息的对象称之为主题消费者(consumers)
  • broker:已发布的消息保存在一组服务器中,称之为Kafka集群。集群中的每一个服务器都是一个代理(Broker)。 消费者可以订阅一个或多个主题(topic),并从Broker拉数据,从而消费这些已发布的消息。

kafka安装和配置

Kafka 对于 zookeeper 是强依赖,保存 kafka 相关的节点数据,所以安装 Kafka 之前必须先安装 zookeeper

Docker安装zookeeper

下载镜像

shell 复制代码
docker pull zookeeper:3.4.14

arm linux

shell 复制代码
docker pull arm64v8/zookeeper:3.4.14

创建容器

shell 复制代码
docker run -d --name zookeeper -p 2181:2181 zookeeper:3.4.14

Docker安装kafka

下载镜像

shell 复制代码
docker pull wurstmeister/kafka:2.12-2.3.1

创建容器

shell 复制代码
docker run -d --name kafka \
--env KAFKA_ADVERTISED_HOST_NAME=10.211.55.6 \
--env KAFKA_ZOOKEEPER_CONNECT=10.211.55.6:2181 \
--env KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://10.211.55.6:9092 \
--env KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 \
--env KAFKA_HEAP_OPTS="-Xmx256M -Xms256M" \
--net=host wurstmeister/kafka:2.12-2.3.1

--net=host,直接使用容器宿主机的网络命名空间, 即没有独立的网络环境。它使用宿主机的ip和端口

docker ps 查看是否启动成功

kafka入门

  • 生产者发送消息,多个消费者只能有一个消费者接收到消息
  • 生产者发送消息,多个消费者都可以接收到消息

生产者发送消息

1:导入kafka客户端依赖

xml 复制代码
<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
</dependency>

2:编写消息生产者类ProducerQuickstart

①.设置kafka的配置信息

java 复制代码
// 1. kafka 连接配置信息
Properties prop = new Properties();
// kafka 连接地址
prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "10.211.55.6:9092");
// key 和 value 的序列化
prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");

②.创建生产者对象

java 复制代码
// 2. 创建 kafka 生产者对象
KafkaProducer<String, String> producer = new KafkaProducer<String, String>(prop);

③.发送消息

java 复制代码
// 3. 发送消息
ProducerRecord<String, String> kvProducerRecord = new ProducerRecord<String, String>("topic-first", "key-001", "hello kafka");
producer.send(kvProducerRecord);

④.关闭消息通道

java 复制代码
// 4. 关闭消息通道   必须关闭,否则消息发送不成功
producer.close();

生产者

java 复制代码
/**
 * 生产者
 */
public class ProducerQuickStart {

    public static void main(String[] args) {

        // 1. kafka 连接配置信息
        Properties prop = new Properties();
        // kafka 连接地址
        prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "10.211.55.6:9092");
        // key 和 value 的序列化
        prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");

        // 2. 创建 kafka 生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<String, String>(prop);

        // 3. 发送消息
        ProducerRecord<String, String> kvProducerRecord = new ProducerRecord<String, String>("topic-first", "key-001", "hello kafka");
        producer.send(kvProducerRecord);

        // 4. 关闭消息通道   必须关闭,否则消息发送不成功
        producer.close();
    }
}

消费者接收消息

创建ConsumerQuickStart消费者类

①:设置kafka的配置信息

java 复制代码
// 1. kafka的配置信息
Properties prop = new Properties();
// kafka 连接地址
prop.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "10.211.55.6:9092");
// key 和 value 的反序列化器
prop.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
prop.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");

// 设置消费者组
prop.put(ConsumerConfig.GROUP_ID_CONFIG, "group1");

②:创建消费者对象

java 复制代码
// 2. 创建消费者对象
KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(prop);

③:订阅主题

java 复制代码
// 3. 订阅消息
consumer.subscribe(Collections.singletonList("topic-first"));

④:获取消息

java 复制代码
// 4. 拉取消息
while (true){
    ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofMillis(1000));
    for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
        System.out.println(consumerRecord.key());
        System.out.println(consumerRecord.value());
    }
}

消费者

java 复制代码
public class ConsumerQuickStart {

    public static void main(String[] args) {

        // 1. kafka的配置信息
        Properties prop = new Properties();
        // kafka 连接地址
        prop.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "10.211.55.6:9092");
        // key 和 value 的反序列化器
        prop.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        prop.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");

        // 设置消费者组
        prop.put(ConsumerConfig.GROUP_ID_CONFIG, "group1");

        // 2. 创建消费者对象
        KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(prop);

        // 3. 订阅消息
        consumer.subscribe(Collections.singletonList("topic-first"));

        // 4. 拉取消息
        while (true){
            ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofMillis(1000));
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord.key());
                System.out.println(consumerRecord.value());
            }
        }
    }
}

生产者发送消息,多个消费者订阅同一个主题,只能有一个消费者收到消息(一对一)

两个消费者在同一个组

生产者发送消息,多个消费者订阅同一个主题,所有消费者都能收到消息(一对多)


两个消费者在不同的组


分区机制

Kafka 中的分区机制指的是将每个主题划分成多个分区(Partition)

可以处理更多的消息,不受单台服务器的限制,可以不受限的处理更多的数据

可以将分区看作存储 Topic 的文件夹,当我们发送消息的时候,可以指定不同的分区,也就是让 Topic 存储到不同的文件夹下(分区),并且也可以是不同的机器上。

topic剖析

每一个分区都是一个顺序的、不可变的消息队列, 并且可以持续的添加。分区中的消息都被分了一个序列号,称之为偏移量(offset),在每个分区中此偏移量都是唯一的。

分区策略

Kafka高可用设计

集群

  • Kafka 的服务器端由被称为 Broker 的服务进程构成,即一个 Kafka 集群由多个 Broker 组成
  • 这样如果集群中某一台机器宕机,其他机器上的 Broker 也依然能够对外提供服务。这其实就是 Kafka 提供高可用的手段之一

备份机制(Replication)

Kafka 中消息的备份又叫做 副本(Replica)

Kafka 定义了两类副本:

  • 领导者副本(Leader Replica)
  • 追随者副本(Follower Replica)

备份机制(Replication)-同步方式

ISR(in-sync replica)需要同步复制保存的 follower

如果leader失效后,需要选出新的leader,选举的原则如下:

  • 第一:选举时优先从ISR中选定,因为这个列表中follower的数据是与leader同步的
  • 第二:如果ISR列表中的follower都不行了,就只能从其他follower中选取

极端情况,就是所有副本都失效了,这时有两种方案

  • 第一:等待ISR中的一个活过来,选为Leader,数据可靠,但活过来的时间不确定
  • 第二:选择第一个活过来的Replication,不一定是ISR中的,选为leader,以最快速度恢复可用性,但数据不一定完整

kafka生产者详解

同步发送

使用 send() 方法发送,它会返回一个Future对象,调用get()方法进行等待,就可以知道消息是否发送成功

java 复制代码
// 发送消息
RecordMetadata recordMetadata = producer.send(kvProducerRecord).get();
// 获取偏移量
System.out.println(recordMetadata.offset());

异步发送

调用 send() 方法,并指定一个回调函数,服务器在返回响应时调用函数

java 复制代码
// 异步发送消息
producer.send(kvProducerRecord, new Callback() {
    @Override
    public void onCompletion(RecordMetadata recordMetadata, Exception e) {
        if (e != null){
            System.out.println("记录异常信息到日志表中");
        }
        System.out.println(recordMetadata.offset());
    }
});

参数详解(ack)

参数详解(retries)

生产者从服务器收到的错误有可能是临时性错误,在这种情况下,retries 参数的值决定了生产者可以重发消息的次数,如果达到这个次数,生产者会放弃重试返回错误,默认情况下,生产者会在每次重试之间等待100ms

参数详解-消息压缩

默认情况下, 消息发送时不会被压缩。

使用压缩可以降低网络传输开销和存储开销,而这往往是向 Kafka 发送消息的瓶颈所在。

kafka消费者详解

消费者组

  • 消费者组(Consumer Group) :指的就是由一个或多个消费者组成的群体
  • 一个发布在Topic上消息被分发给此消费者组中的一个消费者
    • 所有的消费者都在一个组中,那么这就变成了queue模型
    • 所有的消费者都在不同的组中,那么就完全变成了发布-订阅模型

消息有序性

应用场景:

  • 即时消息中的单对单聊天和群聊,保证发送方消息发送顺序与接收方的顺序一致
  • 充值转账两个渠道在同一个时间进行余额变更,短信通知必须要有顺序
  • ......

kafka集群托管4个分区(P0-P3),2个消费者组,消费组A有2个消费者,消费组B有4个

topic分区中消息只能由消费者组中的唯一一个消费者处理,所以消息肯定是按照先后顺序进行处理的。但是它也仅仅是保证Topic的一个分区顺序处理,不能保证跨分区的消息先后处理顺序。 所以,如果你想要顺序的处理Topic的所有消息,那就只提供一个分区。

提交和偏移量

kafka不会像其他JMS队列那样需要得到消费者的确认,消费者可以使用kafka来追踪消息在分区的位置(偏移量)

消费者会往一个叫做 _consumer_offset 的特殊主题发送消息,消息里包含了每个分区的偏移量。如果消费者发生崩溃或有新的消费者加入群组,就会触发再均衡

偏移量

如果提交偏移量小于客户端处理的最后一个消息的偏移量,那么处于两个偏移量之间的消息就会被重复处理。

如果提交的偏移量大于客户端的最后一个消息的偏移量,那么处于两个偏移量之间的消息将会丢失。

偏移量提交方式

提交偏移量的方式有两种,分别是自动提交偏移量和手动提交

  • 自动提交偏移量

enable.auto.commit 被设置为 true,提交方式就是让消费者自动提交偏移量,每隔5秒消费者会自动把从 poll() 方法接收的最大偏移量提交上去

  • 手动提交

enable.auto.commit 被设置为 false 可以有以下三种提交方式

  • 提交当前偏移量(同步提交)
  • 异步提交
  • 同步和异步组合提交

提交当前偏移量(同步提交)

异步提交

同步和异步组合提交

SpringBoot集成kafka收发消息

  1. 导入 spring-kafka 依赖信息
xml 复制代码
<dependency>
   <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--kafka-->
<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
</dependency>
  1. resources 下创建文件 application.yml
yaml 复制代码
server:
  port: 9991
spring:
  application:
    name: kafka-demo
  kafka:
    bootstrap-servers: 10.211.55.6:9092
    producer:
      retries: 10
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
    consumer:
      group-id: ${spring.application.name}-test
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
  1. 消息生产者
java 复制代码
@RestController
public class HelloController {

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    @GetMapping("/hello")
    public String hello(){
        kafkaTemplate.send("topic-key", "hello mx");
        return "ok";
    }
}
  1. 消息消费者
java 复制代码
@Component
public class HelloListener {

    @KafkaListener(topics = "topic-key")
    public void onMessage(String message){
        if (!StringUtils.isEmpty(message)){
            System.out.println(message);
        }
    }
}

传递消息为对象

目前springboot整合后的kafka,因为序列化器是StringSerializer,这个时候如果需要传递对象可以有两种方式

  • 方式一:可以自定义序列化器,对象类型众多,这种方式通用性不强
  • 方式二:可以把要传递的对象进行转json字符串,接收消息后再转为对象即可

发送消息

接收消息

相关推荐
AAA小肥杨6 小时前
基于k8s的Python的分布式深度学习训练平台搭建简单实践
人工智能·分布式·python·ai·kubernetes·gpu
爬山算法9 小时前
Redis(73)如何处理Redis分布式锁的死锁问题?
数据库·redis·分布式
yumgpkpm11 小时前
华为鲲鹏 Aarch64 环境下多 Oracle 、mysql数据库汇聚到Cloudera CDP7.3操作指南
大数据·数据库·mysql·华为·oracle·kafka·cloudera
祈祷苍天赐我java之术11 小时前
Redis 数据类型与使用场景
java·开发语言·前端·redis·分布式·spring·bootstrap
猫林老师14 小时前
HarmonyOS线程模型与性能优化实战
数据库·分布式·harmonyos
阿里云云原生15 小时前
AI 时代的数据通道:云消息队列 Kafka 的演进与实践
云原生·kafka
在未来等你17 小时前
Elasticsearch面试精讲 Day 26:集群部署与配置最佳实践
大数据·分布式·elasticsearch·搜索引擎·面试
勤源科技18 小时前
分布式链路追踪中的上下文传播与一致性维护技术
分布式
互联网工匠18 小时前
分布式操作的一致性方案
分布式·架构
熊猫钓鱼>_>18 小时前
【案例实战】鸿蒙分布式智能办公应用的架构设计与性能优化
分布式·华为·harmonyos