解耦
有这么个场景,A系统要发数据到BCD三个系统,如果是接口调用,挂了一个系统,要不要重发,要不要把消息存起来。这些都要A系统来协助,假如B系统没有收到消息没关系,C系统必须要收到消息,就得让A重发,这些逻辑都堆到A系统来了,很不解耦。
如果让A先把消息发到MQ里面,让BCD自己选择是否要重试,新加E系统也只需接入MQ就行,跟A系统毫无关系了。
异步
有些业务场景如后台导出,后台需要查库出数据然后生成excel然后存储到磁盘,过程相当漫长, 我们可以利用发出一个异步MQ,这样用户端立即返回,还可以做其他事情。
削峰
有些秒杀场景,系统的瞬间流量相当高,如果还操作mysql了那就GG了,MQ可以提供消息的堆积,据说单个队列可以达到上百万堆积量,然后消费者平滑消费,这样就达到了流量削峰,不会导致mysql等崩溃。
蓄流压测
线上有些链路不好压测,可以通过堆积一定量消息再放开来压测。
NameSrv简介
Producer简介
消息生产者,用于向消息服务器发送消息;Producer与NameSrv集群中一台建立长链接,定期从NameSrv取Topic路由信息,并向提供Topic服务的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。
Broker简介
每个Broker与NameSrv集群中的所有节点建立长连接,定时注册Topic信息到所有NameSrv。brocker可主从部署。
Topic路由注册和剔除过程
由于消息生产者无法实时感知 Broker 服务器的宕机,那消息发送的高可用性如何保证呢?
消息发送高可用设计
消息发送队列负载默认采用轮询机制,消息发送时默认选择重试机制来保证消息发送的高可用。
当 Broker 宕机后,虽然消息发送者无法第一时间感知 Broker 宕机,但是当消息发送者向 Broker 发送消息返回异常后,生产者会在接下来一定时间内,例如5分钟内不会再次选择该 Broker上的队列,这样就规避了发生故障的 Broker,结合重试机制,巧妙实现消息发送的高可用。
以上也有可能导致消息没法送到brocker(重试次数达到),我们可以将失败的存储到DB,定时器轮询,直到发送到brocker成功。
消息文件存储结构
CommitLog
存储Producer端写入的消息主体内容,单个文件大小默认1G ,消息是顺序写入日志文件,当文件满了,写入下一个文件;
commitLog结构图:
ConsumeQueue
磁盘路径:{alibaba-rocketmq}/store/consumequeue/{topic} 可以通过topic来定位。
消息消费队列,引入的目的主要是提高消息消费的性能,如果没有ConsumeQueue, 那么消费时就要遍历commitlog文件来根据topic检索消息,这是非常低效的。
ConsumeQueue(逻辑消费队列)作为消费消息的索引,保存了指定Topic下的队列消息在CommitLog中的起始物理偏移量offset,消息大小size和消息Tag的HashCode值。
与 CommitLog 不同,ConsumeQueue 的存储条目采用定长存储结构,如下图所示。为了实现定长存储,ConsumeQueue 存储了消息 Tag 的 Hash Code,在进行 Broker 端消息过滤时,通过比较 Consumer 订阅 Tag 的 HashCode 和存储条目中的 Tag Hash Code 是否一致来决定是否消费消息。
读写消息高效原因
rocketmq写入消息时,并不是写到磁盘才成功,而是 写入一个叫PageCache 的缓存中,然后由定时器去fush到磁盘。而且rocketmq采用mmap技术,不需要将文件中的数据先拷贝至OS的内核IO缓冲区,而是直接将用户进程私有地址空间中的一块区域与文件对象建立映射关系,这样程序就好像可以直接从内存中完成对文件读/写操作一样。
一般来说,一次只能映射1.5~2G 的文件至用户态的虚拟内存空间,这也是为何RocketMQ默认设置单个CommitLog日志数据文件为1G的原因了;
Producer消息路由
一个topic下,我们可以设置多个MessageQueue,当一条消息发送时,Producer 会根据topic获取所有的MessageQueue,然后根据一个路由的object类型参数的hash code取模MessageQueue的个数得到要发送到的MessageQueue。之所以不在消息服务器(brocker)上做是因为这样可以让用户自己决定该如何路由消息,具有更大的灵活性。
如果没有这个object参数,默认会轮询所有的Message Queue发送,以实现消息平均落在不同的Broker上。如下图:
Consumer Rebalance
消息队列系统中,经常会出现 Broker 实例的增删、Topic 的增减、Topic 下 MessageQueue 数目的增减、消费组实例数目的增减等情况,它们都会触发消费关系的重新分配,这个过程称之为 Rebalance。
RocketMQ 的 Rebalance 机制有主动和被动之分,主动意为消费者实例每隔 20s 会定时计算自己的消费拓扑并和内存中的对比,一旦发现部分 MessageQueue 不再是自己负责消费,则停止对它的消息拉取任务;如果有新的 MessageQueue 变为自己负责,则创建对它的消息拉取任务。
被动意为,Broker 可以主动通知某个消费组下的所有实例,要求它们立即开始一次 Rebalance,常用于新的消费者实例加入、或者 Broker 检测到有消费者实例心跳失联等情况,下面是一个消费者实例新加入的场景。
以上是集群模式时的Rebalance, 且这个模式只对于一个ConsumerGroup才起作用,如果是广播模式则应该是:
每台consumer机器都订阅到相关topic的所有queue,这样就会让每台consumer机器都消费到。
生产者端处理: Producer端发送时,指定MessageSelector,算出将消息放到哪个queue
消费端处理: 如何保证消费端 顺序消费? 如果是使用MessageListenerOrderly(**顺序消费)**则自带顺序消费实现,如果是使用MessageListenerConcurrently(并发消费),则需要把线程池改为单线程模式。
MessageListenerOrderly(顺序消费):有序消费,同一队列的消息同一时刻只能一个线程消费,可保证消息在同一队列严格有序消费
MessageListenerConcurrently(并发消费):
怎么保证Producer发送消息阶段不丢失?
rocketmq提供了三种方式发送消息
同步发送: Producer 向 broker 发送消息,阻塞当前线程等待 broker 响应 发送结果。
异步发送: Producer 首先构建一个向 broker 发送消息的任务,把该任务提交给线程池,等执行完该任务时,回调用户自定义的回调函数,执行处理结果。
Oneway发送: Oneway 方式只负责发送请求,不等待应答,Producer 只负责把请求发出去,而不处理响应结果。
我们使用同步发送返送,并且捕获返回结果进行重试,可以减小消息发送丢失。
怎么保证Conusmer消费消息不丢失?
PushConsumer为了保证消息肯定消费成功,只有使用方明确表示消费成功,RocketMQ才会认为消息消费成功。中途断电,抛出异常等都不会认为成功——即都会重新投递。
ConsumeConcurrentlyStatus.CONSUME_SUCCESS
怎么保证brocker存储消息不丢失?
采用同步刷盘模式,当刷盘成功后才返回producer投递消息成功。
事务消息
Producer Group
标识发送同一类消息的Producer,通常发送逻辑一致。发送普通消息的时候,仅标识使用,并无特别用处。若事务消息,如果某条发送某条消息的producer-A宕机,使得事务消息一直处于PREPARED状态并超时,则broker会回查同一个group的其 他producer,确认这条消息应该commit还是rollback。但开源版本并不支持事务消息。
consumer.setMessageModel(MessageModel.BROADCASTING)
consumer.setMessageModel(MessageModel.CLUSTERING);