一、简介
RocketMQ是一款阿里巴巴开源的消息中间件,在2017年9月份成为Apache的顶级项目,是国内首个互联网中间件在 Apache 上的顶级项目。
RocketMQ的起源受到另一款消息中间件Kafka的启发。最初,淘宝内部的交易系统使用了淘宝自主研发的Notify消息中间件,使用Mysql作为消息存储媒介,可完全水平扩容。
为了进一步降低成本和提升写入性能,需要在存储部分可以进一步优化,2011年初,Linkin开源了Kafka这个优秀的消息中间件,淘宝中间件团队在对Kafka做过充分Review之后,被Kafka无限消息堆积,高效的持久化速度所吸引。
不过当时Kafka主要定位于日志传输,对于使用在淘宝交易、订单、充值等场景下还有诸多特性不满足,例如:延迟消息,消费重试,事务消息,消息过滤等,这些都是一些企业级消息中间件需要具备的功能。
为此,淘宝中间件团队重新用Java语言编写了RocketMQ,定位于非日志的可靠消息传输。不过随着RocketMQ的演进,现在也支持了日志流式处理。
二、主要模块
Namesrv: 存储当前集群所有Brokers信息、Topic跟Broker的对应关系。
Broker: 集群最核心模块,主要负责Topic消息存储、消费者的消费位点管理(消费进度)。
Producer: 消息生产者。
Consumer: 消息消费者。
三、集群部署以及主要工作流程
集群部署架构图:
结合部署结构图,描述集群工作流程:
1,启动Namesrv,Namesrv起来后监听端口,等待Broker、Produer、Consumer连上来,相当于一个路由控制中心。
2,Broker启动,跟所有的Namesrv保持长连接,定时发送心跳包。心跳包中包含当前Broker信息(IP+端口等)以及存储所有topic信息。注册成功后,namesrv集群中就有Topic跟Broker的映射关系。
3,收发消息前,先创建topic,创建topic时需要指定该topic要存储在哪些Broker上。也可以在发送消息时自动创建Topic。
4,Producer发送消息,启动时先跟Namesrv集群中的其中一台建立长连接,并从Namesrv中获取当前发送的Topic存在哪些Broker上,然后跟对应的Broker建立长连接,直接向Broker发消息。
5,Consumer跟Producer类似。跟其中一台Namesrv建立长连接,获取当前订阅Topic存在哪些Broker上,然后直接跟Broker建立连接通道,开始消费消息。
四、相关模块功能
1、Namesrv,Namesrv用于存储Topic、Broker关系信息,功能简单,稳定性高。多个Namesrv之间相互没有通信,单台Namesrv宕机不影响其他Namesrv与集群;
即使整个Namesrv集群宕机,已经正常工作的Producer,Consumer,Broker仍然能正常工作,但新起的Producer, Consumer,Broker就无法工作。
2、Broker(client的发送、读取消息等各种核心功能)
一,高并发读写服务
Broker的高并发读写主要是依靠以下两点:
消息顺序写,所有Topic数据同时只会写一个文件(commitlog),一个文件满1G,再写新文件,真正的顺序写盘,使得发消息TPS大幅提高。
消息随机读,RocketMQ尽可能让读命中系统pagecache,因为操作系统访问pagecache时,即使只访问1K的消息,系统也会提前预读出更多的数据,在下次读时就可能命中pagecache,减少IO操作。
二,负载均衡与动态伸缩
负载均衡:Broker上存Topic信息,Topic由多个队列组成,队列会平均分散在多个Broker上,而Producer的发送机制保证消息尽量平均分布到所有队列中,最终效果就是所有消息都平均落在每个Broker上。
动态伸缩能力(非顺序消息):Broker的伸缩性体现在两个维度:Topic, Broker。
Topic维度:假如一个Topic的消息量特别大,但集群水位压力还是很低,就可以扩大该Topic的队列数,Topic的队列数跟发送、消费速度成正比。
Broker维度:如果集群水位很高了,需要扩容,直接加机器部署Broker就可以。Broker起来后向Namesrv注册,Producer、Consumer通过Namesrv发现新Broker,立即跟该Broker直连,收发消息。
三,高可用&高可靠
高可用:集群部署时一般都为主备,备机实时从主机同步消息,如果其中一个主机宕机,备机提供消费服务,但不提供写服务。
高可靠:所有发往broker的消息,有同步刷盘和异步刷盘机制;同步刷盘时,消息写入物理文件才会返回成功,异步刷盘时,只有机器宕机,才会产生消息丢失,broker挂掉可能会发生,
但是机器宕机崩溃是很少发生的,除非突然断电
四,Broker与Namesrv的心跳机制
单个Broker跟所有Namesrv保持心跳请求,心跳间隔为30秒,心跳请求中包括当前Broker所有的Topic信息。Namesrv会反查Broer的心跳信息,如果某个Broker在2分钟之内都没有心跳,
则认为该Broker下线,调整Topic跟Broker的对应关系。但此时Namesrv不会主动通知Producer、Consumer有Broker宕机。
3、消费者(consumer)
消费者启动时需要指定Namesrv地址,与其中一个Namesrv建立长连接。消费者每隔30秒从nameserver获取所有topic的最新队列情况,这意味着某个broker如果宕机,
客户端最多要30秒才能感知。连接建立后,从namesrv中获取当前消费Topic所涉及的Broker,直连Broker。
Consumer跟Broker是长连接,会每隔30秒发心跳信息到Broker。Broker端每10秒检查一次当前存活的Consumer,若发现某个Consumer 2分钟内没有心跳,
就断开与该Consumer的连接,并且向该消费组的其他实例发送通知,触发该消费者集群的负载均衡。
消费者端的负载均衡
先讨论消费者的消费模式,消费者有两种模式消费:集群消费,广播消费。
广播消费:每个消费者消费Topic下的所有队列。
集群消费:一个topic可以由同一个ID下所有消费者分担消费。具体例子:假如TopicA有6个队列,某个消费者ID起了2个消费者实例,那么每个消费者负责消费3个队列。如果再增加一个消费者ID相同消费者实例,
即当前共有3个消费者同时消费6个队列,那每个消费者负责2个队列的消费。
消费者端的负载均衡,就是集群消费模式下,同一个ID的所有消费者实例平均消费该Topic的所有队列。
4、生产者(Producer)
Producer启动时,也需要指定Namesrv的地址,从Namesrv集群中选一台建立长连接。如果该Namesrv宕机,会自动连其他Namesrv。直到有可用的Namesrv为止。
生产者每30秒从Namesrv获取Topic跟Broker的映射关系,更新到本地内存中。再跟Topic涉及的所有Broker建立长连接,每隔30秒发一次心跳。在Broker端也会每10秒扫描一次当前注册的Producer,
如果发现某个Producer超过2分钟都没有发心跳,则断开连接。
生产者端的负载均衡
生产者发送时,会自动轮询当前所有可发送的broker,一条消息发送成功,下次换另外一个broker发送,以达到消息平均落到所有的broker上。
五、服务端主要逻辑概念
服务端逻辑存储图:
1、Consumerqueue:producer、consumer与broker上某个topic打交道时都要通过cq,可以是认为是实际物理消息的一个索引,记录则物理消息在commitlog上的offset,同时维护则consumer的消费进度offset;
2、IndexFile:MessageStore中存储的消息除了通过ConsumeQueue提供给consumer消费之外,还支持通过MessageID或者MessageKey来查询消息;使用ID查询时,因为ID就是用broker+offset生成的(这里msgId指的是服务端的),
所以很容易就找到对应的commitLog文件来读取消息。对于用MessageKey来查询消息,MessageStore通过构建一个index来提高读取速度
整个slotTable+indexLinkedList可以理解成java的HashMap。每当放一个新的消息的index进来,首先取MessageKey的hashCode,然后用hashCode对slot总数取模,得到应该放到哪个slot中,slot总数系统默认500W个。
只要是取hash就必然面临hash冲突的问题,跟HashMap一样,IndexFile也是使用一个链表结构来解决hash冲突。只是这里跟HashMap稍微有点区别的地方是,slot中放的是最新index的指针。这个是因为一般查询的时候肯定是优先查最近的消息。
每个slot中放的指针值是索引在indexFile中的偏移量,如上图,每个索引大小是20字节,所以根据当前索引是这个文件中的第几个(偏移量),就很容易定位到索引的位置。然后每个索引都保存了跟它同一个slot的前一个索引的位置。
3、Commitlog:消息的物理存储文件,默认一个1G大小,超过1G重新创建文件;
4、Topic:消息的逻辑上的一个分组的概念,相同类型的消息投递到一个topic上,producer和consumer与broker打交道都是通过topic维度来进行;
5、Tag:为了业务细分在topic基础上对消息打的一个标记,同一个消息可以被打一个或者多个tag,消费者消费消息时可以根据一个或者多个tag进行过滤;
六、Consumer拉取消息两种模式:
pull模式:主动拉取的模式,需要设置topic和每次拉取的条数,具体需要拉取多少消息需要在代码里写;
push模式:主动推送模式,表象是服务端有消息了主动推动给消费者,实际实现上是通过后台启动单独拉取消息线程不断的拉取服务端消息;
七、顺序消息:
某些业务需要实现消息的顺序性来保持业务的正常进行,rocketmq不严格支持顺序消息,可以通过topic下定义一个queue或者将几条顺序的消息发送到一个queue上实现(可以通过发送消息时producer的对象选择器实现);
八、事务消息:
有些业务需要实现事务消息,在业务被提交时消息才能被消费,当业务被取消是则不消费当前消息,rocketmq实现事务消息主要是通过consumerqueue来实现事务消息,由于消费者消费消息都是先通过consumerqueue来拿到消息的物理位置,
只有普通消息或者被提交的事务消息才被放到consumerqueue里才能被 消费;
九、服务端保存消息机制:
服务端支持同步消息或者异步消息,同步消息每条消息都会被实时刷盘,异步消息是先被写到PAGECACHE,消息累积到一定量才被写到磁盘;
消息支持物理存储,但是磁盘大小是有限的,默认存储48小时删除或者存储占用磁盘空间70%时删除老文件;
十、延时消息
RocketMQ 开源版本延迟消息临时存储在一个内部主题中,不支持任意时间精度,支持特定的 level,例如定时 5s,10s,1m 等
Broker端内置延迟消息处理能力,核心实现思路都是一样:将延迟消息通过一个临时存储进行暂存,到期后才投递到目标Topic中。如下图所示
步骤说明如下:
producer要将一个延迟消息发送到某个Topic中
Broker判断这是一个延迟消息后,将其通过临时存储进行暂存。
Broker内部通过一个延迟服务(delay service)检查消息是否到期,将到期的消息投递到目标Topic中。这个的延迟服务名字为delay service,不同消息中间件的延迟服务模块名称可能不同。
消费者消费目标topic中的延迟投递的消息
十一、消费进度重置
如果想对已经消费过的消息进行重新消费可以对消费进度进行重置重新消费;