Kafka初探
参考资料
资料名称 | 来源地址 |
---|---|
Kafka官方文档 | http://kafka.apache.org/intro |
《Kafka技术内幕》 | 图书 |
《Kafka、RabbitMQ、RocketMQ等消息中间件的对比 —— 消息发送性能和优势》 | https://yq.aliyun.com/articles/475265?spm=a2c4e.11153940.0.0.26dc794fkRn404 |
1. Kafka介绍
Kafka是一种高吞吐量的分布式发布订阅消息系统,也是一个流式数据处理平台,具有高性能、持久化、多副本备份、横向扩展能力。Kafka最初由LinkedIn公司开发的,之后成为Apache项目的一部分。具备下面三个特点:
- 类似消息系统,提供事件流的发布和订阅,即具备数据注入功能
- 存储时间流数据的节点具有故障容错的特点,即具备数据存储功能
- 能够对实时的事件流进行流式的处理和分析,即具备流处理功能
1.1 基础架构和术语
Producer:发布消息的对象称为生产者
Consumer:订阅消息并处理发布的消息的种子的对象称为消费者
Consumer Group:我们可以将多个消费者组成一个消费者组,在kafka的设计中同一个分区的数据只能被消费者组中的某一个消费者消费。同一个消费者组的消费者可以消费同一个topic的不同分区的数据,这也是为了提高kafka的吞吐量
Kafka cluster :已发布的消息保存在一组服务器中,称之为Kafka集群
Broker:Kafka集群中的每一个服务器都是一个代理(Broker). 消费者可以订阅一个或多个主题(topic),并从Broker拉数据,从而消费这些已发布的消息。
Topic:消息的主题,可以理解为消息的分类,kafka的数据就保存在topic。在每个broker上都可以创建多个topic
Partition:Topic的分区,每个topic可以有多个分区,分区的作用是做负载,提高kafka的吞吐量。同一个topic在不同的分区数据是不重复的,partition的表现形式就是一个一个的文件夹
Replication:每一个分区都有多个副本,副本的作用是做备胎。当主分区(Leader)故障的时候会选择一个备胎(Follower)上位,成为Leader。在kafka中默认副本的最大数量是10个,且副本的数量不能大于Broker的数量,follower和leader绝对是在不同的机器,同一机器对同一个分区也只可能存放一个副本(包括自己)。
Zookeeper:kafka集群依赖zookeeper来保存集群的的元信息,来保证系统的可用性
AR ( Assigned Replicas):分区中的所有副本合集
ISR(In-Sync Replicas):所有与leader 副本保持一定程度同步的副本(包括leader 副本在内〕组成 , ISR 集合是AR 集合中的一个子集
OSR ( Out-of-Sync Replicas ):与leader 副本同步滞后过多的副本
ISR = leader + 没有落后太多的副本
AR = OSR+ ISR;
2. Kafka三大角色
-
消息系统
Kafka和传统的消息系统(也称作消息中间件)都具备系统解耦、冗余存储、流量削峰、缓冲、异步通信、扩展性、可恢复性等功能。与此同时, Kafka 还提供了大多数消息系统难以实现的消息顺序性保障及回溯消费的功能。
-
存储系统
Kafka 把消息持久化到磁盘,相比于其他基于内存存储的系统而言,有效地降低了数据丢失的风险。也正是得益于Kafka 的消息持久化功能和多副本机制,我们可以把Kafka 作为长期的数据存储系统来使用,只需要把对应的数据保留策略设置为“永久”或启用主题的日志压缩功能即可。
-
流式处理平台
Kafka 不仅为每个流行的流式处理框架提供了可靠的数据来源,还提供了一个完整的流式处理类库,比如窗口、连接、变换和聚合等各类操作。
3. 消息系统
3.1 消费模式
-
点对点模式
点对点模式通常是基于拉取或者轮询的消息传送模型,这个模型的特点是发送到队列的消息被一个且只有一个消费者进行处理。生产者将消息放入消息队列后,由消费者主动的去拉取消息进行消费。点对点模型的的优点是消费者拉取消息的频率可以由自己控制。但是消息队列是否有消息需要消费,在消费者端无法感知,所以在消费者端需要额外的线程去监控。
-
发布订阅模式
在该模式下生产者将消息发送到消息队列后,队列会将消息推送给订阅过该类消息的消费者。由于消费者被动接收推送,所以无需轮询队列是否有待消费的消息。但是consumer1、consumer2、consumer3由于机器性能不一样,所以处理消息的能力也会不一样,但消息队列却无法感知消费者消费的速度,所以推送的速度成了发布订阅模模式的一个问题,假设三个消费者处理速度分别是8M/s、5M/s、2M/s,如果队列推送的速度为5M/s,则consumer3无法承受,如果队列推送的速度为2M/s,则consumer1、consumer2会出现资源的极大浪费。
Kafka同时支持这两种模式:
- 如果所有的消费者都隶属于同一个消费组,那么所有的消息都会被均衡地投递给每一个消费者,即每条消息只会被一个消费者处理,这就相当于点对点模式的应用。
- 如果所有的消费者都隶属于不同的消费组,那么所有的消息都会被广播给所有的消费者,即每条消息会被所有的消费者处理,这就相当于发布/订阅模式的应用。
3.3 消费模型
消息由生产者发布到Kafka集群后,会被消费者消费。
这里介绍下两种消费模型:推送(push)模型和拉取(pull)模型。
3.3.1 推送模型
基于推送模型的消息系统,由消息代理将消息推送给消费者。消费者的消费状态由代理服务器记录,消息代理服务器将消息发送给消费者后,将这这条消息标记为已消费,但这种方式无法很好报这个消息的处理语义。比如消息发出去后,当消费进程挂掉或者由于网络原因没有收到这条消息时,就有可能造成消息丢失问题(因为消息代理已经把这条消息标记为已消费了,但实际上这条消息并没有被实际处理)。如果要保证消息的处理语义,消息代理发送完消息后,要设置状态为已发送,只有收到消费者的确认请求后才更新为已消费,这就需要消息代理中记录所有消息的消费状态,这种做法也是不可取的。
3.3.2 拉取模型
Kafka采用拉取模型。拉取模型中由消费者自己记录消费状态,每个消费者互相独立地顺序读取每个分区的消息,消费进度由消费者自由控制,可以重新处理之前消费过的消息或从当前时刻开始消费。
3.2 分区模型
在Kafka中的每一条消息都有一个topic(主题)。一般来说在我们应用中产生不同类型的数据,都可以设置不同的主题。一个主题一般会有多个消息的订阅者,当生产者发布消息到某个主题时,订阅了这个主题的消费者都可以接收到生产者写入的新消息。
Kafka的消息通过主题进行分类,主题类似于关系型数据库中的表或文件系统的中的文件夹。一个主题可以被分为多个分区,然后每个分区的消息以先进先出的顺序读取。
分区的目的:
- 方便扩展:因为一个主题可以有多个分区,所以可以通过扩展机器增加分区去轻松应对日益增长的数据量。
- 提高并发:以分区为读写单位,可以多个消费者同时消费数据,提高了消息的处理效率。
如上图,每个分区的每条消息都有一个称为offset的顺序ID号,该ID唯一地标识分区中的每个记录。当生产者产生新消息发布到broker时,每条消息追加到分区中,顺序写入磁盘,保证分区内消息顺序。
分区消息的读取由消费者控制,通常每个消费者都会保存当前消费的消息的偏移量,在每次消费时将这个偏移量不断增加,所以消费者可以自由控制消息读取的位置,重新处理以前的旧数据或跳到最近的记录开始消费,并且不会对其他消费者产生影响。
分区中各偏移量位置:
HW(High Watermark):它标识了一个特定的消息偏移量( offset ),消费者只能拉取到这个offset 之前的消息
LEO(Log End Offset):它标识当前日志文件中下一条待写入消息的offset,LEO 的大小相当于当前日志分区中最后一条消息的offset值加1 。分区ISR 集合中的每个副本都会维护自身的LEO
LSO(Log Start Offset):它标识当前日志文件起始的offset
上图表示一个分区的日志文件,当前有8条消息,第一条消息的offset(LogStartOffset)为0,最后一条消息offset为7,用虚线表示的offset为8的消息代表下一条待写入的消息。
3.3 多副本架构
Kafka为分区提供了多副本机制,通过副本机制来保证容灾能力。副本的职责就是同步数据:从leader副本中同步消息,其他副本叫follower,之间是一主多从的关系。leader负责处理消息的读和写请求,follower负责同步leader上的消息数据。副本处于不同的Broker中,当leader挂掉,从ISR的follower副本中重新选举新的副本作为leader。Kafka 通过多副本机制实现了故障的自动转移,当Kafka 集群中某个broker 失效时仍然能保证服务可用。
基于多副本架构,下面分析一下ISR集合和HW、LEO关系:
某分区有三个副本,一个leader和两个follower,HW和LEO都是3,此时生产者发送消息3和消息4后存入leader副本:
如上图,此时消息3、消息4写入leader副本,follower1从leader同步了消息3,HW取最小值仍为3,leader的LEO变为5,follower1的LEO为4,follower2的LEO为3。当前消费者可以消费0-2之间的消息。
当follower2也同步了消息3,此时HW取最小值4,follower的LEO变为4。消费者可以消费0-3之间的消息。
最后follow1和follow2都同步了消息4,LEO和HW都变为5
3.4 消息分区发送机制
Kafka的消息由键值对和时间戳组成。
生产者负责将消息发布到对应的topic,具体是负责将哪个消息发布到主题中的哪个分区,这里生产者可能对请求进行负载,将消息分发到不同的分区:
-
分区在写入的时候可以指定需要写入的分区,如果有指定,则写入对应的分区
-
如果没有指定分区,但是设置了数据的key,则会根据key的值hash出一个分区
-
如果既没指定分区,又没有设置key,则会轮询选出一个分区
3.5 消息保留机制
Kafka集群使用可配置的保留期限持久地保留所有已发布的记录(无论是否已使用它们)。例如,如果将保留策略设置为两天,则在发布记录后的两天内,该记录可供使用,之后将被丢弃以释放空间。Kafka的性能相对于数据大小实际上是恒定的,因此长时间存储数据不是问题。
3.6 消息批量写入机制
为了提高消息写入效率,消息被分批次写入Kafka中。批次就是一组消息,这些消息属于同一topic下的同一分区。这样减少了网络开销,但是这需要在时间延迟和吞吐量之间作出平衡。批次的数据会被压缩,这样提升了数据的传输和存储能力,但同样做了更多的计算。
3.7 消息顺序性保证
传统消息系统在服务端保持消息的顺序,如果有多个消费者消费同一个消息队列,服务端会以消息存储的顺序依次发送给消费者。但由于消息是异步发送给消费者的,所以消息到达消费者的顺序可能是无序的,这就意味着并行消费时传统消息队列无法很好保证消息被顺序处理。虽然可以设置一个专用的消费者只消费一个队列,以此来解决消息顺序问题,但是这就使消费无法并行处理。
Kafka比传统消息系统有更强的消息顺序性保证,它使用主题分区作为消息处理的并行单元。Kafka以分区作为最小的粒度,将每个分区分配给消费组中不同的而且是唯一的消费者,并确保一个分区只属于一个消费者,即这个消费者就是这个分区下唯一读取线程。那么,只要分区的消息是有序的,消费者处理的消息顺序就有保证。每个主题有多个分区,不同消费者处理不同分区,所以Kafka不仅保证了消息的有序性,也做到了消费者的负载均衡。不过这个消息消费的顺序仅对于分区而言,如果要保证整个主题的消息消费顺序,那么这个主题只能有一个分区并被一个消费者消费才能保证主题内消息的消费顺序。
3.8 producer向kafka写入消息的机制
可以通过 request.required.acks参数来设置数据可靠性的级别 :
-
0:producer无需等待来自broker的确认而继续发送下一批消息。这种情况下数据传输效率最高,但是数据可靠性确是最低的。
-
1:这是默认值, producer发送消息,在leader已成功收到数据并得到确认后发送下一条消息。如果leader宕机了,则会丢失数据,因为可能消息还没有同步到其他follower。
-
-1: producer需要等待ISR中的所有follower都确认接收到数据后才算一次发送完成,可靠性最高。但是这样也不能保证数据不丢失,比如只有分区只有一个leader副本的时候,就变成了acks=1情况
4. 存储系统
4.1 日志文件相关结构
每个主题都有一个或多个分区,分区在服务器上表现形式就是一个个的文件夹,每个分区文件夹下会有多组segment文件,每组segment文件又包含.index文件、log文件、timeindex文件,其中log文件就是存储消息的地方,index和timeindex文件为索引文件,用于检索消息。
每个log文件大小是一样的,但是存储的消息数量不一定相等,因为消息大小可能不一样。文件的命名由segment最小的offset来命名,如segment1的log文件存储offset为0~368795的消息。
4.2 日志索引机制
kafka利用分段+索引的方式来解决查找效率的问题。由于kafka消息数据太大,如果全部建立索引,即占了空间又增加了耗时,所以kafka选择了稀疏索引的方式,这样的话索引可以直接进入内存,加快偏查询速度。
根据消息的offset368801来查找消息的过程如下:
1.根据二分法查找offset为368801的消息所在segment,找到为segment2
2.找到segment2中的index文件,文件中记录了消息的稀疏索引,存储相对offset及对应消息的物理偏移关系。这里查找的消息368801的相对offset为368801-368796=5,因为5在index中不存在,所以需要找到距离5最近的索引4。
3.根据索引4,确定相对offset为4的消息物理偏移为388,然后在log文件中根据偏移388找到message368800,一直往下扫描直到查找到offset368801的消息。
4.3 磁盘读取优化机制
数据从文件发送到socket网络连接中的常规传输路径:
- 操作系统从磁盘读取数据到内核空间里的页面缓存
- 应用程序将数据从内核空间读入用户空间的缓冲区
- 应用程序将读到的数据写回内核空间并放入socket缓冲区
- 操作系统将数据从socket缓冲区复制到网卡接口,此时数据才能通过网络发送出去
这个过程包含了4次copy和两个系统上下文切换,性能比较低效。
结合Kafka的消息有多个订阅者的使用场景,生产者发布的消息一般会被不同的消费者消费多次。Kafka使用“零拷贝”技术只需将磁盘文件的数据复制到页面缓存中一次,然后将数据从页面缓存直接发送到网络中(发送给不同使用者时,都可以重复使用同一个页面缓存),避免了重复的复制操作。
5. API
- Producer API 允许应用程序发送数据流到kafka集群中的topic。
- Consumer API 允许应用程序从kafka集群的topic中读取数据流。
- Streams API 允许从输入topic转换数据流到输出topic。
- Connect API 通过实现连接器(connector),不断地从一些源系统或应用程序中拉取数据到kafka,或从kafka提交数据到宿系统(sink system)或应用程序。
6. Kafka使用示例(windows环境)
6.1 Kafka安装配置
1.安装JDK
Kafka2.0.0版本开始不支持JDK7及以下版本,这里演示使用JDK1.8,JDK安装过程略过。
2.安装Zookeeper
Zookeeper是安装Kafka集群的必要组件,Kafka通过Zookeeper来实施对元数据信息的管理,包括集群、broker、主题、分区等内容。Kafka的安装包中已经有zookeeper,这里不再去另外下载安装。
3.下载Kafka安装包并解压:
https://www.apache.org/dyn/closer.cgi?path=/kafka/2.3.0/kafka_2.12-2.3.0.tgz
4.进入Kafka目录,查看config目录下的server.properties配置文件,关注以下几个配置:
# broker的编号,如果集群中有多个broker,则每个broker的编号需要设置的不同
broker.id=0
# broker对外提供的服务入口地址
listeners=PLAINTEXT://172.31.80.151:9092
# 存放消息文件的目录
log.dirs=/tmp/kafka-logs
# Kafka所需的zookeeper集群地址
zookeeper.connect=localhost:2181
6.2 单机模式
1.进入Kafka安装目录,启动Zookeeper
.\bin\windows\zookeeper-server-start.bat .\config\zookeeper.properties
2.启动Kafka server
.\bin\windows\kafka-server-start.bat .\config\server.properties
3.创建topic “tplink”
.\bin\windows\kafka-topics.bat --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic tplink
4.查看是否创建成功
.\bin\windows\kafka-topics.bat --list --bootstrap-server localhost:9092
5.启动生产者发送信息
.\bin\windows\kafka-console-producer.bat --broker-list localhost:9092 --topic tplink
6.启动消费者消费消息
.\bin\windows\kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic tplink --from-beginning
可以看到生产者发送的消息成功被消费者消费
6.3 分布式模式
分布式模式的Kafka集群拥有多个broker,增加两个broker:
1.复制config/server.properties文件为config/server-1.properties和config/server-2.properties
2.分别修改server-1.properties和server-2.properties
config/server-1.properties:
broker.id=1
listeners=PLAINTEXT://localhost:9093
log.dirs=/tmp/kafka-logs-1
config/server-2.properties:
broker.id=2
listeners=PLAINTEXT://localhost:9094
log.dirs=/tmp/kafka-logs-2
3.启动broker1和broker2:
.\bin\windows\kafka-server-start.bat .\config\server-1.properties
.\bin\windows\kafka-server-start.bat .\config\server-2.properties
4.创建topic,分区数量为1,分区副本数量为3
.\bin\windows\kafka-topics.bat --create --bootstrap-server 172.31.80.151:9092 --replication-factor 3 --partitions 1 --topic tplink-replicated-topic
5.查看topic
.\bin\windows\kafka-topics.bat --describe --bootstrap-server localhost:9092 --topic tplink-replicated-topic
解释:
返回的第一个行显示所有partitions的一个总结,以下每一行给出一个partition中的信息,如果我们只有一个partition,则只显示一行。
-
Partition:0表示当前分区为0
-
Leader:0表示当前leader为broker.id为0的kafka实例
-
Replicas :显示给定partiton所有副本所存储节点的节点列表,不管该节点是否是leader或者是否存活
-
Isr: 副本都已同步的的节点集合,这个集合中的所有节点都是存活状态,并且跟leader同步
6.创建生产者往集群发送消息
.\bin\windows\kafka-console-producer.bat --broker-list localhost:9092 --topic tplink-replicated-topic
7.创建消费者消费消息
.\bin\windows\kafka-console-consumer.bat --bootstrap-server localhost:9092 --from-beginning -topic tplink-replicated-topic
8.杀死分区的leader进程,查看topic状态
从第5步知道leader是1,查找broker id 为1的实例进程:
wmic process where "caption = 'java.exe' and commandline like '%server.properties%'" get processid
杀死进程:
taskkill /pid 36488 /f
查看topic状态:
.\bin\windows\kafka-topics.bat --describe --zookeeper localhost:2181 --topic tplink-replicated-topic
此时leader挂掉后,重新选举leader变为broker1,ISR剩下broker1和2
6.4 docker镜像使用
1.安装 Docker Desktop on Windows
https://docs.docker.com/docker-for-windows/install/
2.使用docker hub上的wurstmeister/kafka镜像
https://hub.docker.com/r/wurstmeister/kafka
dock compose是 docker 提供的一个命令行工具,用来定义和运行由多个容器组成的应用。使用 compose,我们可以通过 YAML 文件声明式的定义应用程序的各个服务,并由单个命令完成应用的创建和启动。
编写docker-compose.yml文件,进行相关配置如下:
version: '2'
services:
zookeeper:
image: wurstmeister/zookeeper
ports:
- "2181:2181"
kafka:
image: wurstmeister/kafka
ports:
- "9092-9095:9092"
environment:
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://:9092
KAFKA_ADVERTISED_HOST_NAME: 172.31.80.151
KAFKA_LISTENERS: PLAINTEXT://:9092
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
volumes:
# 使容器内可以执行docker ps、docker port等命令
- /var/run/docker.sock:/var/run/docker.sock
- 创建和启动docker容器
docker-compose up -d
- 扩展kafka节点为3个
docker-compose scale kafka=3
- 查看docker运行容器
docker ps
- 创建topic
docker exec kafka_kafka_1 kafka-topics.sh --create --topic tplink --partitions 3 --zookeeper zookeeper:2181 --replication-factor 3
- 查看当前topic列表
docker exec kafka_kafka_1 kafka-topics.sh --list --zookeeper zookeeper:2181
- 查看tplink topic的情况
docker exec kafka_kafka_1 kafka-topics.sh --describe --topic tplink --zookeeper zookeeper:2181
7. Kafka与其他消息队列比较
与RabbitMQ、RocketMQ比较:
-
Kafka是LinkedIn开源的分布式发布-订阅消息系统,目前归属于Apache定级项目。Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输。0.8版本开始支持复制,不支持事务,对消息的重复、丢失、错误没有严格要求,适合产生大量数据的互联网服务的数据收集业务。
-
RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。AMQP协议更多用在企业系统内,对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。
-
RocketMQ是阿里开源的消息中间件,它是纯Java开发,具有高吞吐量、高可用性、适合大规模分布式系统应用的特点。RocketMQ思路起源于Kafka,但并不是Kafka的一个Copy,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算、消息推送、日志流式处理、binglog分发等场景。