Kafka是一个消息服务器中间件,至于谁出的、是否开源、更新历史等,自己去google,这篇文章的目的就是让读者搞清楚kafka与rabbitmq、activemq、openmq等在用法上的区别。要想把这些流程和所有异常情况下的处理都说清楚其实也挺费劲的,这里都是假设没有出现网络问题,一切都正常的情况下,kafka生产和消费的流程。
概念定义
1. Broker:就是kafka服务器的进程,强依赖于zookeeper,也就是kafka服务器一定是和zookeeper并存的。Kafka服务器集群就是n个broker和2k+1个zookeper的集群构建的(n>=1;k>=0,zookeeper数量为奇数这个问题自己去google找答案)
2. topic:在kafka中,队列和主题是一个概念,kafka都称之为topic
3. 生产者:负责发送消息的一方,称之为生产者。与其他消息服务中间件的生产者不同,Kafka的生产者可以批量发送消息,所以它的吞吐量很高。
4. 消费者:负责获取消息的一方,称之为消费者。Kafka的消费者比较特殊,跟其他的消息服务中间件消费者相比,kafka消费者是主动发送fetch请求获取消息,默认每次fetch消息的数量为2M字节,每100毫秒进行一次fetch发送,其他消息服务器中间件基本上采用消费者监听模式。
5. 消息:kafka中的消息由两部分组成,分别是Messagekey和MessageContent。Messagekey可以说是消息的标题,也可以当作路由键,MessageKey可以被设置为null,即便是null也具有路由键的功能;MessageContent就是消息的内容。
6. partition(分区):一个topic可以分成n个partition,每个partition是这个topic的持久化单元,消息根据messagekey的hash值,进入partition当中存储。在kafka集群中,一个topic的多个partition可以落在同一个broker,也可以分布在多个broker上。
7. replication(副本):每个partition都可以有n个副本。副本数量由kafka的broker节点的数量决定。副本数量<=kafka的broker数量。同一个partition的副本会分布在不同的broker上,并且不会重复。副本会被选出一个作为主副本,其他都是从副本,主副本会使用异步方式对生产者生产在主副本的消息向从副本进行同步。
好了,有了这些概念基本上就够了,下边开始讲解kafka的生产和消费。
我们以三个broker组成的kafka集群作为示例。三台kafka的服务器地址分别为:
server1,server2,server3
我们现在创建一个topic,topic的名称叫T;我们指定该topic为3个分区且双副本。三个分区分别为P1(P1’),P2(P2’),P3(P3’),其中括号内的就是副本的名称。Partition的分布情况大概是下图所示:
Topic的分区及副本分布图
Server1、server2、server3是通过socket两两连接在一起构成集群
消息生产过程
现在有一个生产者一次性批量发送9条消息。这9条消息的目的地都是是topic T,MessageKey值依次分别为key1、key2、key3……key9,消息内容MessageContent随意。
如果该生产者是第一次发消息,那么该生产者发送消息之前还需要下列步骤:
1. 从生产者配置的server地址列表中选出一台,发送一个MetaData协议,该协议询问Topic T的分区情况。
2. 接收MetaData协议的broker会告诉生产者Topic T所在的broker的地址,以及topic T的每个主副本的分布情况:topic的分区有3个,分别在server1、server2、server3,其中p1分区主副本在server1上、p2分区主副本在server2上、p3分区主副本在server3上。至于从副本,返回的MetaData响应不会告诉生产者从副本的情况。
3. 生产者分别于server1、server2、server3建立socket连接。由于0.8版本及以前的kafka没有安全认证与授权,所以没有认证过程。
一切准备就绪,开始发送消息。生产者将9条消息所组成的MessageSet数据结构进行解析,将其按照messageKey的值的hash拆分成三条Produce协议。
Key1、key4、key7消息将被发送到server1上的p1分区主副本
Key2、key5、key8消息将被发送到server2上的P2分区主副本
Key3、key6、key9消息将被发送到server3上的p3分区主副本
生产者使用3条produce协议分别发送给server1的p1分区、server2的p2分区、server3的p3分区。对于生产者消息可靠性,生产者自身可以定义三种策略。
1. 调用发送接口、写入socket发送缓冲区,立刻返回成功。
2. Broker接收到produce协议,还未写入partition分区文件,立刻返回成功
3. Broker接收到produce协议,将消息写入至少一个partition分区文件,然后返回成功。
这三种策略可靠性依次增高、吞吐量依次降低。
生产者发送完消息后,等待发送下一批消息。如果下一批消息中目的地有任何变更,生产者将断开与所有broker的连接,重新发送MetaData协议询问topic的信息。
多个生产者如果都向topic T生产消息,每个生产者都是遵从上边的生产流程。同一个生产者生产的同一批消息,在partition的存储是连续的。
副本同步
下边说一下副本同步,副本同步比较简单。在broker将消息写入partition主副本对应的分区文件以后,同步就开始异步的发生了。Server1会将p1中新增的三条消息key1、key4、key7三通过server1与server3的socket连接同步到server3上的p1’分区从副本中。
主从副本同步
单消费者消费消息
0.8版本的kafka的消费者客户端分为低级别和高级别两种api。低级别api需要自己处理消费者对消息的commit、offset信息的保存等。我们现在主要是以高级别api为例。高级别api集成的zk客户端,将消费者的消费信息(对消息的commit、offset信息)默认保存在zk上。
现在我们定义一个消费者,这个消费者订阅了topic T中的消息。
l 消费者消费消息的过程(0.8版本与0.9版本不同)
1. 消费者配置的地址为zk集群地址。Zk集群中保存在server1、server2、server3启动后注册在brokers节点中的地址信息,消费者通过查询brokers节点,获取到所有broker的地址信息。
2. 消费者同时需要从zk中获取到该消费者以前对topic T的消费情况。具体是查询一下消费了topic T中每个partition的offset。
3. 消费者获得了这两方面的信息,然后从server1、server2、server3这三个地址中选择一个,发送metadata协议(同生产者)。MetaData协议返回结果告诉消费者它需要消费的topic有三个partition、以及每个partition的主副本在哪个broker上。这一步基本上和生产者是完全一样的。
4. 假设消费者是第一次开始消费topic T中的消息,这时候消费者可以选择两种方式进行消费,从topic T的中的保存的最老的消息开始消费、或者从最新的一条消息开始消费。以下举例为消费者从没有消费过topic T中的消息,且决定从最老的一条消息开始消费。
5. 消费者开始与server1、server2、server3建立socket连接。
6. 消费者开始向server1、server2、server3发送fetch协议。Server1接收到的fetch协议为:我需要消费p1中的消息,从offset编号为0的消息消费,如果消息足够多,请给我返回2M字节的消息;server2收到的消息就是从p2中取消息;server3收到的fetch协议需要从p3中取消息。三条fetch协议的发送是基本上同时的。
7. Server1、server2、server3将各p1、p2、p3中的消息以fetch协议的response方式返回给消费者。当然了,返回的速度可能有的快、有的慢。如果消费者收到response的顺序为server1、server2、server3。那么消费者得到的消息顺序为:key1、key4、key7、key2、key5、key8、key3、key6、key9。
8. 消费者需要对消息进行确认。但这个确认不是给broker确认,而是消费者自己找了一个地方把消费的offset存起来。高级别版本的消费者api将把offset保存在zk中。Kafka消费者默认的采用了autocommit的方式,使用固定间隔6秒,向zk中记录某个partition的消费offset。也可以使用手动提交的方式调用commit(不推荐)。
特别注意:
1. Partition中的消息被消费了以后,还是会持久化在文件中,不会删除掉。消费者可以根据自己的策略重置、修改自己保存的offset,重新消费已经消费过的消息。
2. 消息的TTL是根据broker的配置决定的。一旦broker配置了删除消息日志文件策略。那么在该broker分布的而所有topic的partition(包括副本),都会按照这个TTL进行消息删除策略。
多消费者消费消息
我们以两个消费者都消费topic T为例。
这里我们分为两种情况,上图为第一种情况。这两个client设置的groupId(clientId)不一样。这两个消费者是两个完全独立的消费者,消费的行为完全一样。如果consumer1消费了9条消息,那么consumer2也会消费9条消息。至于顺序,就不一定一样了,主要是看response的响应速度。
第二种情况是两个消费者client设置的相同的groupId(clientId),这时候消费者的连接图可能(也有可能是其他情况)是这样的
Consumer1会消费掉p1、p2中的6条消息,consumer2会消费掉p3的3条消息。
相同的group的消费者的数量小于和等于订阅的topic的partition数量。如果第二种情况的consumer的数量为4个,那么其中至少有一个消费者不会消费任何消息,除非另外三个消费者中有最少一个消费者离线。