Loading

Kafka介绍


返回 我的技术栈(Technology Stack)



一、Kafka介绍

Kafka是最初由Linkedin公司开发,是一个分布式、支持分区的(partition)、多副本的 (replica),基于zookeeper协调的分布式消息系统,

它的最大的特性就是可以实时的处理大量数据以满足各种需求场景:比如基于hadoop的批处理系统、低延迟的实时系统、 Storm/Spark流式处理引擎,web/nginx日志、访问日志,消息服务等等,

用scala语言编写,Linkedin于 2010 年贡献给了Apache基金会并成为顶级开源项目。

1.Kafka的使用场景、

  • 日志收集:一个公司可以用Kafka收集各种服务的log,通过kafka以统一接口服务的方式 开放给各种consumer,例如hadoop、Hbase、Solr等。

  • 消息系统:解耦和生产者和消费者、缓存消息等。

  • 用户活动跟踪:Kafka经常被用来记录web用户或者app用户的各种活动,如浏览网⻚、 搜索、点击等活动,这些活动信息被各个服务器发布到kafka的topic中,然后订阅者通过 订阅这些topic来做实时的监控分析,或者装载到hadoop、数据仓库中做离线分析和挖掘。

  • 运营指标:Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产 各种操作的集中反馈,比如报警和报告。

2.Kafka基本概念

kafka是一个分布式的,分区的消息(官方称之为commit log)服务。它提供一个消息系统应该具备的功能,但是确有着独特的设计。

可以这样来说,Kafka借鉴了JMS规范的思想,但是确并没有完全遵循JMS规范。

首先,让我们来看一下基础的消息(Message)相关术语:

名称 解释
Broker 消息中间件处理节点,⼀个Kafka节点就是⼀个broker,⼀个或者多个Broker可以组成⼀个Kafka集群
Topic Kafka根据topic对消息进⾏归类,发布到Kafka集群的每条消息都需要指定⼀个topic
Producer 消息⽣产者,向Broker发送消息的客户端
Consumer 消息消费者,从Broker读取消息的客户端
ConsumerGroup 每个Consumer属于⼀个特定的Consumer Group,⼀条消息可以被多个不同的Consumer Group消费,但是⼀个Consumer Group中只能有⼀个Consumer能够消费该消息
Partition 物理上的概念,⼀个topic可以分为多个partition,每个partition内部消息是有序的

因此,从一个较高的层面上来看,producer通过网络发送消息到Kafka集群,然后consumer 来进行消费,如下图:

image

服务端(brokers)和客户端(producer、consumer)之间通信通过 TCP协议 来完成。

二、kafka基本使用

1.安装前的环境准备

  • 安装jdk

  • 安装zk

  • 官网下载kafka的压缩包:http://kafka.apache.org/downloads

  • 解压缩至如下路径

/usr/local/kafka/
  • 修改配置文件:/usr/local/kafka/kafka2.11-2.4/config/server.properties
#broker.id属性在kafka集群中必须要是唯一
broker.id= 0
#kafka部署的机器ip和提供服务的端口号
listeners=PLAINTEXT://192.168.65.60:9092
#kafka的消息存储文件
log.dir=/usr/local/data/kafka-logs
#kafka连接zookeeper的地址
zookeeper.connect= 192.168.65.60:2181

2.启动kafka服务器

进入到bin目录下。使用命令来启动

./kafka-server-start.sh -daemon../config/server.properties

验证是否启动成功:

进入到zk中的节点看id是 0 的broker有没有存在(上线)

ls /brokers/ids/

server.properties核心配置详解:

Property Default Description
broker.id 0 每个broker都可以⽤⼀个唯⼀的⾮负整数id进⾏标识;这个id可以作为broker的“名字”,你可以选择任意你喜欢的数字作为id,只要id是唯⼀的即可。
log.dirs /tmp/kafka-logs kafka存放数据的路径。这个路径并不是唯⼀的,可以是多个,路径之间只需要使⽤逗号分隔即可;每当创建新partition时,都会选择在包含最少partitions的路径下进⾏。
listeners PLAINTEXT://192.168.65.60:9092 server接受客户端连接的端⼝,ip配置kafka本机ip即可
zookeeper.connect localhost:2181 zooKeeper连接字符串的格式为:hostname:port,此处hostname和port分别是
log.retention.hours 168 每个⽇志⽂件删除之前保存的时间。默认数据保存时间对所有topic都⼀样。
num.partitions 1 创建topic的默认分区数
default.replication.factor 1 ⾃动创建topic的默认副本数量,建议设置为⼤于等于2
min.insync.replicas 1 当producer设置acks为-1时,min.insync.replicas指定replicas的最⼩数⽬(必须确认每⼀个repica的写数据都是成功的),如果这个数⽬没有达到,producer发送消息会产⽣异常
delete.topic.enable false 是否允许删除主题

3.创建主题topic

topic是什么概念?topic可以实现消息的分类,不同消费者订阅不同的topic。
image

执行以下命令创建名为“test”的topic,这个topic只有一个partition,并且备份因子也设置为1

./kafka-topics.sh --create --zookeeper 172.16.253.35:2181 --replication-factor 1 --partitions 1 --topic test

查看当前kafka内有哪些topic

./kafka-topics.sh --list --zookeeper 172.16.253.35:2181

4.发送消息

kafka自带了一个producer命令客户端,可以从本地文件中读取内容,或者我们也可以以命令行中直接输入内容,并将这些内容以消息的形式发送到kafka集群中。在默认情况下,每一个行会被当做成一个独立的消息。使用kafka的发送消息的客户端,指定发送到的kafka服务器地址和topic

./kafka-console-producer.sh --broker-list 172.16.253.38:9092 --topic test

image

5.消费消息

对于consumer,kafka同样也携带了一个命令行客户端,会将获取到内容在命令中进行输 出, 默认是消费最新的消息 。使用kafka的消费者消息的客户端,从指定kafka服务器的指定 topic中消费消息

方式一:从最后一条消息的偏移量+1开始消费

./kafka-console-consumer.sh --bootstrap-server 172.16.253.38:9092 --topic test

image

image
image
image

方式二:从头开始消费

./kafka-console-consumer.sh --bootstrap-server 172.16.253.38:9092 --from-beginning --topic test

image
image

几个注意点:

  • 消息会被存储
    image

  • 消息是顺序存储,通过offset偏移量来描述消息的有序性

  • 消息是有偏移量的

  • 消费时可以指明偏移量进行消费

三、Kafka中的关键细节

1.消息的顺序存储

消息的发送方会把消息发送到broker中,broker会存储消息,消息是按照发送的顺序进行存储。因此消费者在消费消息时可以指明主题中消息的偏移量。默认情况下,是从最后一个消息的下一个偏移量开始消费。

2. 单播消息的实现【组内单播,组间多播】

单播消息:一个消费组里 只会有一个消费者能消费到某一个topic中的消息。于是可以创建多个消费者,这些消费者在同一个消费组中。

./kafka-console-consumer.sh --bootstrap-server 10.31.167.10:9092 --consumer-property group.id=testGroup --topic test

image

3.多播消息的实现【组内单播,组间多播】

不同的消费组订阅同一个Topic,那么不同的消费组中只有一个消费者能收到消息。实际上也是多个消费组中的多个消费者收到了同一个消息

在一些业务场景中需要让一条消息被多个消费者消费,那么就可以使用多播模式。
kafka实现多播,只需要让不同的消费者处于不同的消费组即可。

./kafka-console-consumer.sh --bootstrap-server 10.31.167.10:9092 --consumer-property group.id=testGroup1 --topic test

./kafka-console-consumer.sh --bootstrap-server 10.31.167.10:9092 --consumer-property group.id=testGroup2 --topic test

image

image

4.查看消费组及信息

# 查看当前主题下有哪些消费组
./kafka-consumer-groups.sh --bootstrap-server 10.31.167.10:9092 --list

# 查看消费组中的具体信息:比如当前偏移量、最后一条消息的偏移量、堆积的消息数量
./kafka-consumer-groups.sh --bootstrap-server 172.16.253.38:9092 --describe --group testGroup

image

Currennt-offset: 当前消费组的已消费偏移量
Log-end-offset: 主题对应分区消息的结束偏移量(HW)
Lag: 当前消费组未消费的消息数

四、主题、分区的概念

1.主题Topic

主题Topic在Kafka中是一个逻辑概念,Kafka通过Topic将消息进行分类。不同的Topic会被订阅该Topic的消费者消费。

但是有一个问题,如果说这个Topic中的消息非常多,多到需要几个T来存储,而消息又是会被存储在log日志文件中,这就非常不方便。为了解决这个文件过大的问题,Kafka提出了Partition分区的概念。

2.partition分区

分区的作用:

  • 分区存储,解决统一存储文件过大问题。
  • 提高了读写的吞吐量:读和写可以同在在多个分区中进行

image

一个主题中的消息量是非常大的,因此可以通过分区的设置,来分布式存储这些消息。比如一个topic创建了 3 个分区。那么topic中的消息就会分别存放在这三个分区中。

为一个主题创建多个分区

./kafka-topics.sh --create --zookeeper localhost:2181 --partitions 2 --topic test2

可以通过这样的命令查看topic的分区信息

./kafka-topics.sh --describe --zookeeper localhost:2181 --topic test1

image

消息实际上是存在data/kafka-logs/test-0 和 test-1中的0000000.log文件中

细节:

消费者定期将自己消费分区的offset提交给kafka内部topic:__consumer_offsets,

image

提交过去的 时候,key是consumerGroupId+topic+分区号,value就是当前offset的值

kafka会定期(7天)清理topic里的消息,最后就保留最新的那条数据

因为__consumer_offsets可能会接收高并发的请求,kafka默认给其分配 50 个分区(可以通过offsets.topic.num.partitions设置),这样可以通过加机器的方式抗大并发。

通过如下公式可以选出consumer消费的offset要提交到__consumer_offsets的哪个分区
公式:hash(consumerGroupId) % __consumer_offsets主题的分区数

整个流程如下图所示:
image

image

image

image

image

image

五、Kafka集群及副本的概念

1.搭建kafka集群, 3 个broker

准备 3 个server.properties文件

每个文件中的这些内容要调整

  • server.properties
broker.id= 0
listeners=PLAINTEXT://192.168.65.60:
log.dir=/usr/local/data/kafka-logs
  • server1.properties
broker.id= 1
listeners=PLAINTEXT://192.168.65.60:
log.dir=/usr/local/data/kafka-logs-1
  • server2.properties
broker.id= 2
listeners=PLAINTEXT://192.168.65.60:
log.dir=/usr/local/data/kafka-logs-2

使用如下命令来启动 3 台服务器

./kafka-server-start.sh -daemon../config/server0.properties
./kafka-server-start.sh -daemon../config/server1.properties
./kafka-server-start.sh -daemon../config/server2.properties

搭建完后通过查看zk中的/brokers/ids 看是否启动成功

2.副本的概念

副本是对分区的备份。在集群中,不同的副本会被部署在不同的broker上。下面例子:创建 1个主题, 2 个分区、 3 个副本。

./kafka-topics.sh --create --zookeeper 172.16.253.35:2181 --replication-factor 3 --partitions 2 --topic my-replicated-topic

image

  • Replicas:当前副本存在哪些broker节点上

  • leader:副本里的概念
    每个partition都有一个broker作为leader。
    消息发送方要把消息发给哪个broker?就看副本的leader是在哪个broker上面。副本里的leader专⻔用来接收消息。
    接收到消息,其他follower通过poll的方式来同步数据。

  • follower:leader处理所有针对这个partition的读写请求,而follower被动复制leader,不提供读写(主要是为了保证多副本数据与消费的一致性),如果leader所在的broker挂掉,那么就会进行新leader的选举。

  • isr:可以同步和已经同步的节点会被存入到isr集合中。如果isr中的节点性能较差,会被剔除isr集合。leader选举也在isr中选择。

image

image

集群中有多个broker,创建主题时可以指明主题有多少个分区(把消息拆分到不同的分区中存储),可以为分区创建多个副本,不同副本存放在不同的broker里。

3.关于分区消费组消费者的细节

image
图中Kafka集群有两个broker,每个broker中有多个partition。

一个partition只能被一个消费组里的某一个消费者消费,从而保证消费顺序。

Kafka只在partition的范围内保证消息消费的局部顺序性,不能在同一个topic中的多个partition中保证总的消费顺序性。一个消费者可以消费多个partition。

消费组中消费者的数量不能比一个topic中的partition数量多,否则多出来的消费者消费不到消息。

六、Kafka的Java客户端-生产者

1.引入依赖

<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
    <version>2.4.1</version>
</dependency>

2.生产者发送消息的基本实现

#### //消息的发送方
public class MyProducer {

private final static String TOPIC_NAME = "my-replicated-topic";

public static void main(String[] args) throws ExecutionException,InterruptedException {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"10.31.167.10:9092,10.31.167.10:9093,10.31.167.10:9094");
//把发送的key从字符串序列化为字节数组
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
//把发送消息value从字符串序列化为字节数组
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());

Producer<String, String> producer = new KafkaProducer<String,String>(props);

Order order = new Order((long) i, i);
ProducerRecord<String, String> producerRecord = new ProducerRecord<String, String>(TOPIC_NAME, order.getOrderId().toString(), JSON.toJSONString(order));
RecordMetadata metadata = producer.send(producerRecord).get();
//=====阻塞=======
System.out.println("同步方式发送消息结果:" + "topic-" +metadata.topic() + "|partition-"+ metadata.partition() + "|offset-" +metadata.offset());

3.发送消息到指定分区上

ProducerRecord<String, String> producerRecord = new ProducerRecord<String, String>(TOPIC_NAME, 0 , order.getOrderId().toString(), JSON.toJSONString(order));

4.未指定分区,则会通过业务key的hash运算,算出消息往哪个分区上发

//未指定发送分区,具体发送的分区计算公式:hash(key)%partitionNum
ProducerRecord<String, String> producerRecord = new ProducerRecord<String, String>(TOPIC_NAME, order.getOrderId().toString(), JSON.toJSONString(order));

5.同步发送**

**生产者同步发消息,在收到kafka的ack告知发送成功之前一直处于阻塞状态

//等待消息发送成功的同步阻塞方法
RecordMetadata metadata = producer.send(producerRecord).get();
System.out.println("同步方式发送消息结果:" + "topic-" +metadata.topic() + "|partition-"+ metadata.partition() + "|offset-" +metadata.offset());

6.异步发消息

生产者发消息,发送完后不用等待broker给回复,直接执行下面的业务逻辑。可以提供callback,让broker异步的调用callback,告知生产者,消息发送的结果

//要发送 5 条消息
Order order = new Order((long) i, i);
//指定发送分区
ProducerRecord<String, String> producerRecord = new ProducerRecord<String, String>(TOPIC_NAME, 0 , order.getOrderId().toString(),JSON.toJSONString(order));
//异步回调方式发送消息
producer.send(producerRecord, new Callback() {
public void onCompletion(RecordMetadata metadata, Exception exception) {
if (exception != null) {
    System.err.println("发送消息失败:" +
    exception.getStackTrace());
}
if (metadata != null) {
System.out.println("异步方式发送消息结果:" + "topic-" +metadata.topic() + "|partition-"+ metadata.partition() + "|offset-" + metadata.offset());
         }
    }
});

7.关于生产者的ack参数配置

在同步发消息的场景下:生产者发动broker上后,ack会有 3 种不同的选择:
( 1 )acks=0: 表示producer不需要等待任何broker确认收到消息的回复,就可以继续发送下一条消息。性能最高,但是最容易丢消息。
( 2 )acks=1(默认): 至少要等待leader已经成功将数据写入本地log,但是不需要等待所有follower是否成功写入。就可以继续发送下一条消息。这种情况下,如果follower没有成功备份数据,而此时leader又挂掉,则消息会丢失。性能和安全是最均衡的。
( 3 )acks=-1或all: 需要等待 min.insync.replicas(默认为 1 ,推荐配置大于等于2) 这个参数配置的副本个数都成功写入日志,这种策略会保证只要有一个备份存活就不会丢失数据。这是最强的数据保证。一般除非是金融级别,或跟钱打交道的场景才会使用这种配置。这种方式最安全,但性能最差。

下面是关于ACK和重试(如果没有收到ACK,就开启重试)的配置

props.put(ProducerConfig.ACKS_CONFIG, "1");
/*
发送失败会重试,默认重试间隔100ms,重试能保证消息发送的可能性,但是也可能造成消息重复发送,比如网络抖动,所以需要在接收者那边做好消息接收的等幂性处理
*/
props.put(ProducerConfig.RETRIES_CONFIG,3); // 重试3次

props.put(ProducerConfig.RETRY_BACKOFF_MS_CONFIG,300); //每隔300ms

发送会默认会重试 3 次,每次间隔100ms
发送的消息会先进入到本地缓冲区(32mb),kakfa会跑一个线程,该线程去缓冲区中取16k的数据,发送到kafka,如果到 10 毫秒数据没取满16k,也会发送一次。
image
相关代码配置:
image

posted @ 2023-10-21 22:00  言非  阅读(103)  评论(0)    收藏  举报