1.基本术语
Topic:表示消息的第一级类型,比如测试环境消息、生产环境消息,一条消息必须有一个topic
Tag:表示消息的第二级类型,比如测试环境消息下的分类,分为货主消息、司机消息,tag非必需,一条消息可以没有tag
Queue:一个topic下可以设置多个queue队列,发送消息时需要指定topic,mq 会轮询该topic下所有队列
Producer :表示消息队列的生产者,消息队列的本质是publish-subscribe模式,生产者生产消息,消费者消费消息,Producer主要用于生产和发送消息
Producer Group:表示一类Producer的集合名称,这类Producer通常发送一类消息,且发送逻辑一致
Consumer :消息消费者,由后台异步消费消息
Consumer Group:表示一类Consumer的集合名称,这类Consumer通常消费一类消息,且消费逻辑一致,可以在控制台查看该group 下消息的消费积压情况
Push Consumer:Consumer的一种,通常向Consumer对象注册一个Listener接口,一旦收到消息,Consumer对象立刻回调Listener接口方法
Pull Consumer:Consumer的一种,通常主动调用Consumer的拉消息方法从Broker拉取消息,主动权由应用控制
Broker:消息的中转者,负责存储和转发消息。可以理解为消息队列服务器,提供了消息接收、存储、拉取和转发服务,broker是Rocketmq的核心,不能挂,必须保证broker的高可用
NameServer:接收broker的请求,注册broker的路由信息,接收client的请求,根据某个topic获取其到某个broker的路由信息。NameServer没有状态,可以横向扩展,每个broker在启动时会到NameServer注册,Producer在发送消息前会根据topic到NameServer获取到topic的路由信息,Consumer也会定时获取topic的路由信息
广播消费:一条消息被多个Consumer消费,即使这些消息属于同一个Consumer Group,消息也会被Group中的每个Consumer消费一次
集群消费:一个Consumer Group中的Consumer实例平均分摊消费消息,如某个Topic有9条消息,其中一个Consumer Group 有3个实例,那么每个实例只消费其中的3条消息
2.Rocketmq网络结构
节点特点:
Name server:几乎无状态节点,集群部署,节点之间无任何通讯
Broker:broker分为master和slave,一个master对应多个salve,一个salve只能有一个master,master与slave的关系通过指定相同的brokerName,不同的brokerId,BrokerId为0表示master,BrokerId>0表示salve,master可部署多个
Producer:完全无状态节点,可集群部署,与Name server集群中的一个节点(随机)建立长连接,定期从Name server读取topic路由信息,并向提供topic服务的master节点建立长连接,定时向master发送心跳
Consumer:与Name server集群中的一个节点(随机)建立长连接,定期从Name server读取topic路由信息,并向提供topic服务的master、salve节点建立长连接,定时向master和salve发送心跳,Consumer即可以从master订阅消息,也可以向salve订阅消息,订阅规则由broker配置决定
心跳机制:
broker每隔30S(无法修改)向nameserver发送心跳,心跳包含自身的topic配置信息,nameserver每隔10S(无法修改),扫描还存活的broker连接,若某个连接2分钟内没发送心跳,则断开连接,调整topic和broker的对应关系,nameserver不会主动通知producer和comsumer有broker宕机
consumer每隔30S从nameserver获取所有topic最新队列情况
consumer每隔30S向所有关联的broker发送心跳,该时间由DefaultMQPushConsumer的heartBeatBrokerInterval决定,可配置,broker每隔10S扫描还存活的连接,若某个连接2分钟内无心跳,则关闭连接,并向消费者分组的所有消费者发送通知,分组内消费者重新分配队列继续消费(rebalance)
producer每隔30S从nameserver获取所有topic所有队列情况,该参数由DefaultMQProducer的pollNameserverInterval参数决定,可配置
producer每隔30S向所有的broker发送心跳,该参数有DefaultMQProducer的heartBeatBrokerInterval参数决定,可配置,broker每隔10S扫描还存活的连接,若某个连接2分钟内无心跳,则关闭连接
集群模式:
单master:
优点:配置简单
缺点:一旦boroker重启或宕机,会导致整个服务不可用
多master,无salve:
优点:配置简单,单个mster宕机或重启对应用无影响,性能最高
缺点:
1)消息的实时性会受到影响,rocketmq的各个master之间互相不通信,所以如果某个master宕机,该机器上未被消费的消息,在机器恢复前无法被消费
2)如果宕机的master磁盘有问题,会造成消息数据丢失
注意:
当使用多master无slave的集群搭建方式时,master的brokerRole配置必须为ASYNC_MASTER。如果配置为SYNC_MASTER,则producer发送消息时,返回值的SendStatus会一直是SLAVE_NOT_AVAILABLE
多master,多salve:
特点:
这种模式下每个master至少会有一个salve,(master和salve的集群名称和节点名称相同,节点id不同,且master的节点id必须为0,salve的节点id必须大于0)
多master,多salve,异步复制:
优点:
1)消息的实时性高,master宕机,消费者可以从slave消费,性能比同步双写高,比多master无slave低
缺点:
如果master宕机,master磁盘损坏的情况下,会有少量消息丢失
多master,多salve,同步双写:
优点:
1)消息实时性高,数据可用性最高
缺点:
性能比异步复制低10%,producer发送消息时,只有master和salve都写成功了,才会返回success,这样即使master宕掉,salve也会保存完整的数据,适合比较重要的业务
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样 例如:在a.properties 文件中写 broker-a 在b.properties 文件中写 broker-b
brokerName=broker-b
#0 表示 Master,>0 表示 Slave
brokerId=0
#nameServer地址,分号分割
namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=ASYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
3.Consumer消费消息轮询队列策略
Rocketmq消息消费模式:
1)push模式,即Rocketmq server端主动向Consumer client端推送消息
应用向consumer对象注册Listener对象,一旦接受到消息,Consumer对象立刻回调Listener接口,Push指客户端内部的回调机制,并不是服务端的机制
2)pull模式,应用主动调用consumer从服务端拉消息,consumer向broker发送拉消息请求,PullMessageService服务从broker拉取消息,可批量拉取(pullBatchSize = 32)长轮询方式,也可由应用程序主动拉取,短轮询方式
同一个ConsumerGroup下多个Consumer消费消息队列,分配策略如下:
1)采用平均分配策略,类似于分页,如果有5个队列,2个Consumer,那么第一个Consumer消费3个队列,第二个Consumer消费2个队列
2)消费端会用RebalanceService线程,10分钟做一次基于topic下所有队列的负载,获取同一个Group下所有Consumer实例或topic下queue的个数是否改变,通知所有的Consumer做一次负载均衡算法
4.顺序消费
分布式环境下的顺序问题
1)不同线程或进程间,可能分布在不同的服务器上,其时间可能是不同步的,其顺序只能通过因果关系去推断,如状态变化
2)同一线程上的事件顺序可以确定,其参照的是同一个时间参考点
rocketmq的顺序:
1)分区顺序:一个partition内的所有消息按照先进先出的原则进行发布和消费
2)全局顺序:一个topic内所有的消息按照先进先出的顺序进行发布和消费
mq如何保证顺序
1)消息在被发送是保持顺序
2)消息在被存储时保持和发送的顺序一致
生产和消费要求:
1)Producer端保证发消息有序,且发送到同一个队列
2)Consumer端保证消费同一个队列
5.发送定时消息
Rocketmq可设置不同level的定时发送消息
1)配置在broker中,默认为:messageDelayLevel = 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
配置示例
1)level = 1 表示一级延时为1s,level = 2 表示二级延时为5s,默认不延时
Message msg = new Message(topic, tags, keys, body);
#延时10s发送
msg.setDelayTimeLevel(3);
SendResult sendResult = producer().send(msg);
6.消费失败重试机制
消费失败判断条件:
1)消费端消费失败返回RECONSUME_LATER
重试机制:
1)如果消费失败,根据在broker中设置的messgeDelayLevel,定时发送重试消费,直到消费端返回CONSUME_SUCCESS终止重试
2)如果重试次数达到一定此时(默认16次),该消息不再进入重试队列,进入死信队列
7.生产失败重试机制
生产失败判断条件:
1)抛出异常(RemotingException、MQClientException、MQBrokerException) ,Producer会选择另一个队列发消息
2)重试次数 < retryTimesWhenSendFailed+1,retryTimesWhenSendFailed一定小于队列个数,重试完队列会直接退出重试
3)设置方式,mqProducer.setRetryTimesWhenSendFailed()
4)非异常时,正常返回send_status,但返回状态不为SEND_OK,根据RetryAnotherBrokerWhenNotStoreOK = true (默认false),重试逻辑与上相同
8.队列个数
producer发送消息时设置,同一个topic 当且仅当第一次创建时设置有效
配置示例:mqProducer.setDefaultTopicQueueNums(5)
每个topic可设置队列个数,自动创建topic时默认4个,需要顺序消费的消息发往同一队列,比如同一订单号相关的几条需要顺序消费的消息发往同一队列, 顺序消费的特点的是,不会有两个消费者共同消费任一队列,且当消费者数量小于队列数时,消费者会消费多个队列。至于消息重复,在消费端处理
9.ConsumerFromWhere
producer发送消息时设置,同一个topic 当且仅当第一次创建时设置启动有效,后续再启动,会接着上次消费的进度开始消费
ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET,第一次启动时从队列的最前位置开始启动
ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET,第一次启动从队列的最后位置开始启动
10.消息存储结构
Consumer queue,消息的逻辑队列,想当于字段,用来指定消息体在commit log上的位置,在broker的配置文件中配置
1)根据topic和queueId来组织文件,图中TopicA有两个队列0,1,那么TopicA和QueueId=0组成一个ConsumeQueue,TopicA和QueueId=1组成另一个ConsumeQueue
2)按照消费端的GroupName来分组重试队列,如果消费端消费失败,消息将被发往重试队列中,比如图中的%RETRY%ConsumerGroupA
3)按照消费端的GroupName来分组死信队列,如果消费端消费失败,并重试指定次数后,仍然失败,则发往死信队列,比如图中的%DLQ%ConsumerGroupA
Consume Queue中存储单元是一个20字节定长的二进制数据,顺序写顺序读,存储格式如下:
consumequeue文件存储单元格式
1)CommitLog Offset是指这条消息在Commit Log文件中的实际偏移量
2)Size存储中消息的大小
3)Message Tag HashCode存储消息的Tag的哈希值:主要用于订阅时消息过滤(订阅时如果指定了Tag,会根据HashCode来快速查找到订阅的消息)
为什么将一个topic抽象出多个queue?
为了实现负载均衡,Broker上存Topic信息,Topic由多个队列组成,队列会平均分散在多个Broker上。Producer的发送机制保证消息尽量平均分布到 所有队列中,最终效果就是所有消息都平均落在每个Broker上
如下图,yunlian-truck-message-test是一个消费者组,后面有2个broker,每个broker有4个队列,每个broker上的消息是平均分配的
commit log
消息存放的物理文件,每台broker上的commitlog被本机所有的queue共享,不做任何区分
CommitLog的消息存储单元长度不固定,文件顺序写,随机读。消息的存储结构如下表所示,按照编号顺序以及编号对应的内容依次存储。
消息索引
如果一个消息包含key值,会使用indexfile存储消息索引,索引文件主要用于根据key来查询消息
索引文件主要用于根据key来查询消息的,流程主要是:
1)根据查询的 key 的 hashcode%slotNum 得到具体的槽的位置(slotNum 是一个索引文件里面包含的最大槽的数目,例如图中所示 slotNum=5000000)
2)根据 slotValue(slot 位置对应的值)查找到索引项列表的最后一项(倒序排列,slotValue 总是指向最新的一个索引项)
3)遍历索引项列表返回查询时间范围内的结果集(默认一次最大返回的 32 条记录)
消息存储
commitLog顺序写:
1)顺序写,所有的topic数据只会写一个文件,一个文件满1.1G,再写新文件
2)目前的消息队列通过commitLog持久化,为长度无限的数据结构(每个存储单元定长),根据过期时间将定期删除
PageCache 与Mmap内存映射
系统的所有文件I/O请求,操作系统都是通过page cache实现。操作系统内核在处理文件I/O请求时,首先到page cache中查找(page cache中的每一个数据块都设置了文件及偏移量地址信息),如果未命中,则启动磁盘I/O,将磁盘文件中的数据加载到page cache中的空闲块,然后再copy到用户缓冲区中pagecache本身会对数据文件进行预读取,对每个文件的第一个读操作请求,操作系统在读入所请求页面的同时会读入紧随其后的几个页面。
在mq中,consumer queue逻辑消费队列存储的数据内容较少,且顺序读取,在page cache机制的预读取作用下,consumer queue读取性能接近于内存,即使在消息堆积的情况下,也不会影响性能。
commitLog随机读:
mq通过MappedByteBuffer对文件进行读写操作,利用了NIO中的FileChannel模型直接将磁盘上的物理文件直接映射到用户态的内存地址中(这种Mmap的方式减少了传统IO将磁盘文件数据在操作系统内核地址空间的缓冲区和用户应用程序地址空间的缓冲区之间来回进行拷贝的性能开销),将对文件的操作转化为直接对内存地址进行操作,从而极大地提高了文件的读写效率,MappedByteBuffer这种内存映射的方式有几个限制,其中之一是一次只能映射1.5~2G 的文件至用户态的虚拟内存,这也是为何RocketMQ默认设置单个CommitLog日志数据文件为1G的原因了
11.消费端实现负载均衡
Consumer queue
消费端负载均衡原理:
1)消费端通过RebalanceService线程,10S做一次基于topic下所有队列的负载
2)根据topic获取该topic下所有的queue
3)选择一台broker获取基于group的所有消费端,
负载算法:
平均分配算法,类似分页算法
1)将所有queue排好序,相当于数据总长
2)将所有消费端consumer排好序,相当于页数
3)每个consumer端分配的数据为每页数据长度
12.nameserver数据结构
无状态节点,相互独立,producer、consumer、broker、topic定期同时向nameserver上报状态信息,以达到热备份的目的。
集群状态存储结构:
1)topic属性信息,private final HashMap <String topic,List> topicQueueTable,存储topic的属性信息。QueueData的长度为Master Broker的个数,其中存储broker名称、读写queue数量、同步标识
2)brokername属性信息,private final HashMap <String BrokerName,BrokerData> BrokerAddrTable
相同名称的broker可能存在多台机器,一个master和多个slaver,BrokerData存储BrokerName对应的属性信息,包含cluster名称,多个broker的地址信息
3)cluster的信息,private final HashMap<String ClusterName,Set> ClusterAddrTable
存储cluster名称对应的brokername的集合
4)broker实时状态信息,private final HashMap<String BrokerAddr,BrokerLiveInfo> BrokerLiveTable
以broker的地址为key,存储这台broker机器的实时状态,包括上次更新状态的时间戳,NameServer 会定期检查这个时间戳,超时没更新就认为这个broker无效,从broker列表中删除
5)过滤服务器列表,private final HashMap<String BrokerAddr,List filterServer> FilterServetTable,
存储broker关联的多个Fileter server 的地址