一、什么是kafka?
Kafka是一种分布式,基于发布/订阅的消息系统。
高吞吐量:每秒可处理几十万条记录
分布式:支持热扩展
持久化:消息持久化到磁盘
容错:副本容错
高并发:客户端同时高并发读写
使用背景:
峰值处理能力
统一接口服务
解耦
消息系统介绍:
一个消息系统负责将数据从一个应用传递到另外一个应用,应用只需关注于数据,无需关注数据在两个或多个应用间是如何传递的。
分布式消息传递基于可靠的消息队列,在客户端应用和消息系统之间异步传递消息。
有两种主要的消息传递模式:点对点传递模式、发布-订阅模式。
大部分的消息系统选用发布-订阅模式。Kafka就是一种发布-订阅模式。
点对点模式:
在点对点消息系统中,消息持久化到一个队列中。此时,将有一个或多个消费者消费队列中的数据。
但是一条消息只能被消费一次。当一个消费者消费了队列中的某条数据之后,该条数据则从消息队列中删除。
该模式即使有多个消费者同时消费数据,也能保证数据处理的顺序。
发布-订阅模式:
在发布-订阅消息系统中,消息被持久化到一个topic中。与点对点消息系统不同的是,
消费者可以订阅一个或多个topic,消费者可以消费该topic中所有的数据,同一条数据可以被多个消费者消费,
数据被消费后不会立马删除。在发布-订阅消息系统中,消息的生产者称为发布者,消费者称为订阅者.
发布者发送到topic的消息,只有订阅了topic的订阅者才会收到消息。
二、kafka-基本概念
生产者(Producer):负责发布消息到Kafka broker
消费者(Consumer):从消息队列中请求消息的客户端应用程序
代理(Borker): kafka集群包含一个或多个服务器,这种服务器被称为broker,准确来讲是一个kafka的服务进程。
生产者推送消息到broker上,消费者可以订阅一个或多个主题(topic),并从Broker拉数据,从而消费这些已发布的消息。
Topic
在实际业务中,通常一个业务对应一个topic
kafka使用topic来组织消息
一个topic消息可以包含多个partition,分布在不同的broker上
一个partition可以指定多个副本
生产消息、订阅消息都需要指定topic
Partition(分区)
一个topic按照多个分区组织消息,每个分区都是一个顺序的,不可变的消息队列,且持续在队列末尾添加,而不是随机读写。
producer生产的消息按照一定的算法分配到不同的分区中,添加到分区末尾。所有的partition当中的数据全部合并起来,就是一个topic当中的所有的数据。
一个broker服务下,可以创建多个分区,broker数与分区数没有关系;在kafka中,每一个分区会有一个编号:编号从0开始。
每一个分区内的数据是有序的,但全局的数据不能保证是有序的。(有序是指生产什么样顺序,消费时也是什么样的顺序)
增加partition数量,可以提升读写并发
一个partition对应的物理文件:log文件和index文件,每个log文件又被称为segment,索引文件分为offset索引文件和时间戳索引文件
一个partition可以指定多个副本,但是只有一个副本是leader,partition的读写只能通过leader。
Message(消息)
一个消息对应kafka的一个offset
消息只会追加到segment上面,不能修改删除
segment会定期删除(配置项log.retention.{ms,minutes,hours}和log.retention.bytes)
segment默认配置保留时间为7天
Message物理结构
8 bytes offset(表示消息在partition中第几条)
4 bytes message size(消息的大小)
1 byte magic size(kafka程序服务协议号)
4 bytes crc(crc32校验)
其他(压缩、编码,key等信息)
payload(实际消息的数据)
segment/index文件(log文件)命名规范:文件中第一条信息的offset -1
index 文件包括序号和地址,序号就是message在日志文件中的相对偏移量(即第几条)
地址是指文件中的偏移字节。
OffsetIndex是稀疏索引,不会存储所有的消息相对offset和position
消息检索过程:
以这个partition目录下面,00000000001560140916为例
定位offset 为1560140921的message
1. 定位到具体的segment日志文件
由于log日志文件的文件名是这个文件中第一条消息的offset-1. 因此可以根据offset定位到这个消息所在日志文件:00000000001560140916.log
2. 计算查找的offset在日志文件的相对偏移量
segment文件中第一条消息的offset = 1560140917
计算message相对偏移量:需要定位的offset - segment文件中第一条消息的offset + 1 = 1560140921 - 1560140917 + 1 = 5
查找index索引文件, 可以定位到该消息在日志文件中的偏移字节为456. 综上, 直接读取文件夹00000000001560140916.log中偏移456字节的数据即可。
1560140922 -1560140917 + 1 = 6
如果查找的offset在日志文件的相对偏移量在index索引文件不存在, 可根据其在index索引文件最接近的上限
偏移量, 往下顺序查找
Offset(偏移量)
Offset是一个有序的序列
一个消息对应kafka的一个offset
offset的最大长度为8字节
Consumer Group(消费组)
消费者组由一个或者多个消费者组成,同一个组中的消费者对于同一条消息只消费一次。
每个消费者都属于某个消费者组,如果不指定,那么所有的消费者都属于默认的组。
每个消费者组都有一个ID,即group ID。组内的所有消费者协调在一起来消费一个订阅主题(topic)的所有分区(partition)。
当然,每个分区只能由同一个消费组内的一个消费者(consumer)来消费,可以由不同的消费组来消费。
某一个主题下的分区数,对于消费者来说,应该小于等于该主题下的分区数。
partition replicas(分区副本)
副本数(replication-factor): 控制消息保存在几个broker(服务器)上,一般情况下副本数等于broker的个数。
一个broker服务下,不可以创建多个副本因子。创建主题时,副本因子应该小于等于可用的broker数。
副本因子操作以分区为单位的。每个分区都有各自的主副本和从副本;
主副本叫做leader,从副本叫做 follower。follower通过拉的方式从leader同步数据。
消费者和生产者都是从leader读写数据,不与follower交互。
消费模型
High Level Consumer API
不需要自己管理offset
默认实现最少一次消息传递语义(At least once)
consumer数量大于partition数量,浪费
consumer数量小于partition数量,会导致一个consumer对应多个partition
最好partition的数量是consumer数目的整数倍
Low Level Consumer API(Simple Consumer API)
需要自己管理offset
可以实现各种消息传递语义
生产上常用
三、运行架构
1.如何消费消息?
producer选择一个topic,生产消息,消息会通过分配策略append到某个partition末尾。
consumer选择一个topic,通过id指定从哪个位置开始消费消息。消费完成之后保留id,下次可以从这个位置开始继续消费,也可以从其他任意位置开始消费。
上面的id在kafka中称为offset,这种组织和处理策略提供了如下好处:
1.1 消费者可以根据需求,灵活指定offset消费
1.2 保证了消息不变性,为并发消费提供了线程安全的保证。每个consumer都保留自己的offset,互相之间不干扰,不存在线程安全问题。
1.3 消息访问的并行高效性。每个topic中的消息被组织成多个partition,partition均匀分配到集群server中。生产、消费消息的时候,
会被路由到指定partition,减少竞争,增加了程序的并行能力。
1.4 增加消息系统的可伸缩性。每个topic中保留的消息可能非常庞大,通过partition将消息切分成多个子消息,并通过负责均衡策略将partition分配到不同server。
这样当机器负载满的时候,通过扩容可以将消息重新均匀分配。
1.5 保证消息可靠性。消息消费完成之后不会删除,可以通过重置offset重新消费,保证了消息不会丢失。
1.6 灵活的持久化策略。可以通过指定时间段(如最近一天)来保存消息,节省broker存储空间。
1.7 备份高可用性。消息以partition为单位分配到多个server,并以partition为单位进行备份。
备份策略为:1个leader和N个followers,leader接受读写请求,followers被动复制leader。leader和followers
会在集群中打散,保证partition高可用。
2.为什么说是分布式和冗余备份的?
分区被分布到集群中的各个服务器中,每个服务器处理它所拥有的分区。根据配置,每个分区还可以复制到其他服务器作为备份容错。
每个分区拥有一个leader,有一个或者多个follower(冗余备份的)。一个broker可以是一个分区的leader,同时也可以是别的分区的follwer,
避免了所有的请求只让一个或者几个服务器处理,负载均衡。
某个broker如果是一个分区的leader,那么它处理这个分区上的所有读写请求,而follwer分区被动的复制数据。
如果leader宕机,则follwer就可以被推举为leader。
3.为什么说是持久性的:
kafka使用文件存储消息,并且会保存所有消息直到它过期,无论是否消费。
四、kafka安装及使用
kafka_2.11-2.1.0.tgz
zookeeper-3.4.5.tar.gz
1.kafka依赖于zookeeper,先安装zookeeper
#1.1 解压文件
tar -zxvf zookeeper-3.4.5.tar.gz
#1.2 移动文件到合适的位置
mv zookeeper-3.4.5 /usr/local/zk
#1.3 修改配置文件
cd conf
cp zoo_sample.cfg zoo.cfg
vim zoo.cfg
#dataDir是存放snapshot,zookeeper的datatree在内存中某一时刻的影像
dataDir=/usr/local/zk/data
:wq
#启动zookeeper
cd bin
./zkServer.sh start
2.安装jdk8
#解压文件
tar -zxvf jdk-8ul8l-linux-x64.gz
#移动文件
mv jdk1.8.0_181 /usr/local/jdk
#配置环境变量
vim /ect/profile
export JAVA_HOME=/usr/local/jdk
export ZK_HOME=/usr/local/zk
export PATH=.:$PATH:$JAVA_HOME/bin:$ZK_HOME/bin
:wq
#生效配置文件
source /etc/profile
#查看当前服务器进程
jps
#查看zookeeper的状态
zkServer.sh status
3.安装kafka
#解压文件
tar -zxvf kafka_2.11-2.0.1.tgz
#移动文件
mv kafka_2.11-2.1.0 /usr/local/kafka
#修改配置文件
cd /usr/local/kafka
cd config
vim server.properties
#修改日志存放目录
log.dirs=/usr/local/kafka/data/kafka-logs
#zookeeper的链接地址(可默认不变)
zookeeper.connect=localhost:2181
4.修改主机域名
vim /etc/hostname
master
:wq
5.修改host域名映射(hosts文件)
su root
vim /etc/hosts
192.168.159.131 master
6.启动kafka
./kafka-server-start.sh ../config/server.properties
#查看kafka是否正常启动
jps
7.关闭防火墙
systemctl stop firewalld.service
8、测试kafka
1.创建TOPIC test1, 副本数1 分区数1
./kafka-topics.sh --zookeeper 127.0.0.1:2181 --topic test1 --replication-factor 1 --partitions 1 --create
2.生产消息
./kafka-console-producer.sh --broker-list 127.0.0.1:9092 --topic test1
2.消费消息
./kafka-console-consumer.sh -bootstrap-server 127.0.0.1:9092 --topic test1
9、SpringBoot 整合kafka
整合时遇到错误:
org.apache.kafka.common.errors.TimeoutException: Expiring 1 record(s) for test1-0: 30002 ms has passed since batch creation plus linger time
解决方案:(配置hosts)
五、kafka发送消息示例代码
public static void main(String[] args) {
String mytopic = "mytest1";
Properties props = new Properties();
props.put("serializer.class","kafka.serializer.StringEncoder");
props.put("metadata.broker.list","192.168.50.104:9092");
props.put("request.required.acks","1");
props.put("partitioner.class","kafka.producer.DefaultPartitioner");
Producer<String,String> producer = new Producer<String,String>(new ProducerConfig(props));
for(int index=0; index <88; index++){
producer.send(new KeyedMessage<String,String>(mytopic,index+"", UUID.randomUUID()));
}
}
request.required.acks:用于校验broker是否正确接收到kafka的消息.
参数值共有三个0,1,-1,为零代表不等待broker返回确认消息,无阻塞
1.只需要partition的leader保存成功即返回生产者,无需等待副本是否保存成功
-1,需要partition的leader以及follower副本均保存成功才返回消息