metaq杂记

MetaQ是一个消息中间件,使用Name Server提供路由信息。Producer与Consumer通过长连接与Name Server交互,Producer将消息发布到Master Broker,再同步到Slave Broker。消息存储在commitlog文件中,通过逻辑队列(consume queue)索引定位,支持顺序存储和消费。Consumer通过offset从consume queue获取消息,实现顺序消费。Name Server轻量无状态,不依赖ZooKeeper。MetaQ提供重试队列和死信队列处理消费失败的消息。
摘要由CSDN通过智能技术生成

Name Server:维护broker的地址列表,以及topic和topic对应的队列的地址列表。每个broker与每个Name Server之间使用长连接来保持心跳,并向其定时注册topic信息。可以从两个维度来理解Name Server的能力: 1)Name Server可以提供一个特定的topic对应的broker地址列表;2)Name Server可以提供一台broker上包含的所有topic列表。轻量级的名称服务。几乎无状态的节点,相互之间不会有数据同步。 主要提供topic路由信息的注册及查询。(MetaQ 1.x和MetaQ 2.x是依赖ZooKeeper的,由于ZooKeeper功能过重,RMetaQ 3.x去掉了对ZooKeeper依赖,采用自己的NameServer)

Producer和集群中某一个Name Server间采用长连接,Producer定期从Name Server中获取到Topic对应的broker地址列表,即Topic路由信息,并缓存在本地,然后选择一台合适的master broker发布消息。 注意producer发布消息是将消息发布到与对应的master broker上,再由master broker同步到slave broker上。

Consumer和集群中某一个Name Server间采用长连接,并定期从Name Server中获取到Topic路由信息,然后选择合适的broker拉取消息进行消费。

在 Metaq2.x 之前版本,队列也称为“分区”,两者描述的是一个概念。 但是按照 2.x 的实现,使用队列描 述更合适。

数据存储分为两级,物理队列+逻辑队列。
物理队列:一个broker只有一个物理队列,所有发到broker的数据都会顺序写入该队列,当一个文件被写满时(默认为1G),会新建文件继续写入。
逻辑队列:当consumer消费数据时,consumer先根据nameServer提供的路由信息定位到broker,再从broker的消费队列读取index,从而定位到物理队列的位置。一个topic有多个分区,每个分区对应一个消费队列,而消费队列由index组成。

commitlog消息文件:broker在接收到生产者发送来的消息后,是如何对其进行存储的呢?在MetaQ中,真正的消息本身实际上是存放在broker本地一个名为commitlog的文件中的,并且这个commitlog的写入是不区分Topic的,即不论什么Topic的消息,都会在接收到之后顺序写入commitlog文件中,commitlog的文件名就是起始字节位置 写满后,产生一个新的文件。

索引文件:读取的时候又是怎么从commitlog中找到消息的呢?的确,仅仅只存储消息本身是无法做到这个的(因为在仅有commitlog文件的前提下,消息的长度、类型等信息都是无法确定的),所以MetaQ还有索引文件(在一些文档中也称为Message Queue)。broker将消息存储到commitlog文件后,会将该消息在文件的物理位置(offset),消息大小,消息类型等信息封装成一个固定大小的数据结构,称为索引单元。其中,offset是java long型,有64位,从理论上讲,offset在100年内都不会发生溢出,所以可以认为message queue长度无限。从而简单地,可以把message queue理解为是一个长度无限的数组,offset就是下标。多个索引单元组成一个索引文件,和commitlog文件一样,文件名是起始字节位置,写满后,产生一个新的文件。

broker 将消息存储到文件后,会将该消息在文件的物理位置,消息大小,消息类型封装成一个固定大 小的数据结构,暂且称这个数据结构为索引单元吧,大小固定为 16k,消息在物理文件的位置称为 offset。
多个索引单元组成了一个索引文件,索引文件默认固定大小为 20M,和消息文件一样,文件名是 起始字节位置,写满后,产生一个新的文件。

metaq的消息存储由commit log和逻辑队列consume queue配合完成。 首先会将所有的消息分topic存在commit log文件中,commit log文件最大为1G,超过1G会生成新文件,文件以起始字节大小命名。consume queue逻辑队列相当于是commit log文件的索引,记录offset偏移量,size长度,消息的hashcode等信息。物理队列只有一个(也就是commit log文件),采用固定大小的文件顺序存储消息。逻辑队列有多个(每个对应一个topic),每个逻辑队列有多个分区(topicA_1分区,topicA_2分区,topicA_3分区),每个分区有多个索引单元。MetaQ中将每一个Topic分为了几个区,每个区对应了一个消费队列,这些消费队列就由各个索引文件组成。消费端在拉取消息时,只要知道自己是订阅的Topic从nameserver获取broker地址建立连接后,就能根据消费队列中的索引文件,去物理队列中获取订阅的消息。如下图所示,topicA在broker1 和 broker2分别有topicA_1分区, topicA_2分区;topicA_3 分区,topicA_4 分区。每个分区里存是的commit log文件的索引信息。消费信息时,需要通过索引信息,到commit log文件中获取真正的数据信息进行消费。仔细观察图metaq_arch_1/2/3.png

offset:这个概念其实放在这里讲略微有些早了,可以在了解完MetaQ 的消息生产模型之后再来了解。两个“offset”,刚开始还是有点迷惑的,梳理之后才理出了头绪,于是这里把这两种offset拿出来专门加以区分。

  1. 消息存储过程中的offset: 这个offset就是指索引单元中的offset,它标志着消息在commitlog文件中的物理位置。这个offset的存在也是MetaQ能正确在commitlog文件中定位消息的前提和保障。
  2. 消息消费时的offset: 这个offset是指当前消息被消费到的位置(接下来从哪个位置开始消费),这个offset是消费者顺序消费消息的依据和保障。这个offset被存储在消费者本地、数据库,还会定时更新到broker中。消费者每次拉取请求就需要offset这个参数,如果没有这个offset,MetaQ就不能实现顺序读了。MetaQ文件目录下有两个文件用于持久化消费进度,每次将offset写入consumerOffset.json文件,然后备份到 consumerOffset.json.bak文件中。在代码实现上,offset的持久化实际上是存储进一个以Topic和groupId的组合字符串为key,以ConcurrentMap为value的ConcurrentMap,而这个value上的ConcurrentMap又是以queueId为key,以offset为value的。

对于某一特定Topic而言,brokerId和分区号组合起来就是一个有序的分区列表, 如Topic “hello”对应的有序分区列表为{A-0,A-1,B-0,B-1,B-2},生产者生产消息实际上可以理解为是以topic下的分区为单位进行的,即生产者按照一定规则向“brokerId-分区号”组成的有序分区列表对应的分区发送消息。发送的规则可以定制,一般采用轮询的方式。
消息的存储就是借助之前介绍过的commitlog文件和索引文件来实现的。

消费者消费消息也是以topic下的分区为单位,分组内一个消费者对应消耗一个分区的消息。这样一来就出现以下两种情况:

  1. 一个Group中的消费者个数大于总的分区数目:在这种情况下,多出来的消费者空闲,不参与消费;
  2. 一个Group中的消费者个数小于总的分区数目:在这种情况下,有部分消费者需要承担额外的消费任务。

当Topic下的分区数足够大的时候,可以认为消费者负载是平均分配的。
于是,消息的拉取过程描述如下:
(1) 根据Topic和分区号找到对应的逻辑消费队列,记为A;
(2) 根据A和offset找到对应的待消费的索引位置,记为B;
(3) 从B开始,读取B所对应的commitlog文件中的消息放入消费者队列中,直到读取到的消息长度等于给定的maxSize。在这个过程中,offset同时后移更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值