我是一条消息,从我被生产者发布到topic的时候,我就清楚自己的使命:被消费者获取消费。但我一直很纳闷,把我直接推送给消费者不就行了,为什么一定要先推送到类似队列的topic中呢,消息队列的作用到底是什么呢?
在消息队列出现以前,服务之间的矛盾由来已久,服务A调用服务B,经常要等待服务B处理完后续流程再返回结果,因此服务A常常抱怨服务B接收处理能力太慢,服务B也常常把某些责任推卸给服务A,于是,消息队列出现了,让服务A只管往队列里面放消息,后续的处理流程都不用管,而服务B负责往队列里面取消息,通过巧妙的办法,抛开了等待的过程,明确了两者的职责,从此,服务A和服务B各自专心于自己的事物,也不相互抱怨推诿责任了,用砖家的话说,就是实现了解耦和异步通信。kafka就是消息系统中的代表性成员。
我一开始以为kafka不过是一条细长的管道,我们这些消息一个个往里面放,先进者先出,等我真正进入生产环境的消息系统之后,才发现原来是自己道行不够,真实情况往往比我们想象中的要复杂。那是一个非常宏伟的建筑:
图片来源:极客学院
这幅图包含了消息系统使用中的各个角色,这里重点讲解下消费者,kafka使用Consumer high level API时,同一Topic的一条消息只能被同一个Consumer Group内的一个Consumer消费,但多个Consumer Group可同时消费这一消息。
这可以实现广播和单播模式,如果所有的consumer都具有相同的group,我们会被其中一个cosumer fetch(单播);如果所有的consumer都有不同的group,那这就是"发布-订阅",我们将会被广播给所有的consumer。
上图中,broker持有topic,一个topc切分成多个partitions,这些partitions分布在多个不同的broker(kafka服务器)中,这样避免了消息文件达到单机限制,同时也提高了并发消息的能力,消息被路由到哪个partition上,由producer客户端决定,比如可以采用"key-hash"等方法。一个partition又有多个segments,当segment文件尺寸达到一定阀值时,将会创建一个新的文件。我们就藏在segment中,但kafka总能通过offset找到我们,经典方法是二分查找法。读到这里,您肯定头晕了,下面是术语解释!
topic:被发布消息的一个类别,逻辑上可以被认为是一个queue,一个topic可以有一个或多个消费者订阅。
图片来自官网
partition:物理上把Topic分成一个或多个Partition,每个Partition在物理上对应一个文件夹,该文件夹下存储这个Partition的所有消息和索引文件。每个Partition都是一个有序的、不可变的记录序列,它不断被追加到一个结构化的commit日志中。Partition中的记录被分配到一个顺序的id号,称为offset(偏移量),它惟一地标识Partition中的每个记录。
log:比如topic名称为"my_topic",它有2个partitions,那么日志将会保存在my_topic_0和my_topic_1两个目录中;日志文件中保存了一序列"log entries"(日志条目),每个log entry格式为"4个字节的数字N表示消息的长度" + "N个字节的消息内容";每个日志都有一个offset来唯一的标记一条消息,offset的值为8个字节的数字,表示此消息在此partition中所处的起始位置..每个partition在物理存储层面,有多个log file组成(称为segment).segmentfile的命名为"最小offset".kafka.例如"00000000000.kafka";其中"最小offset"表示此segment中起始消息的offset。
放大镜下的log:
图片来自官网
二分查找:首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
生产者把我们推到消息队列后,剩下的就是等待kafka消费者fetch,这是kafka的一个特点,而在JMS实现中,一般是push模式,由broker主动推送给消费者,可见broker的任务是相当的轻的。
为了防止我们随时可能挂了,kafka还精心设计了备份机制,即将partition克隆复制几份,放到其他broker上,这些partitions中有leader,负责日常事务,其他follower只是按照leader节奏,同步信息即可,一旦leader挂了,就要选择一个follower来顶上。
除了备份机制,还有消息传送机制,可选的有以下三种:
1、at most once: 最多一次,发送一次,无论成败,将不会重发。
读取消息,然后在log中保存它的位置,最后处理消息。而消息被处理之前,消费者进程可能会崩溃。在这种情况下,处理的过程将从保存的位置开始,即使在该位置之前的一些消息还没有被处理。这就是“at most once”语义。
2、 at least once: 消息至少发送一次,如果消息未能接受成功,可能会重发,直到接收成功。
读取消息,处理消息,最后保存它的位置。在这种情况下,消费者进程可能在处理消息后崩溃,此时其位置还没有保存。在这种情况下,新进程会接收到已经被处理的几条消息。这就是“at least once”。在许多情况下,消息有一个主键,因此更新是幂等(接收相同的消息两次,只是用一个副本覆盖一条记录)。
3、exactly once: 消息只会发送一次。
元数据存储—zookeeper
上面建筑图右边还有一个叫zookeeper的家伙,Kafka通过zookeeper来存储管理元数据,zookeeper的工作包括但不限于:
1、选择controller。controller是其中的一个broker,负责维护所有partitions的leader/follower关系。当一个节点关闭时,controller通知其他副本成为partition leader,以替换原节点的partition leader。zookeeper选择controller,确保只有一个controller,如果它崩溃了,zookeeper会选择一个新的。
2、管理集群成员—哪些broker是alive状态的,是集群的一部分?这也是通过zookeeper来管理的。
3、topic配置—哪些topic是存在的,每个有多少partitions,副本在哪里,其中哪个是优先选择的leader,每个topic都配置了什么。
4、存在哪些consumer group,他们有哪些成员,以及每个group从每个partiton获得的最新offset。在kafka中,一个partition中的消息只会被group中的一个consumer消费。
kafka初次漫游到此结束,可能有些地方看得不是很全很细,下次继续。
java达人
ID:drjava
(扫码或长按识别)
领取专属 10元无门槛券
私享最新 技术干货