RocketMQ问题

1.为什么要用消息中间件:
1.解耦:   系统A只负责把数据写到队列中,谁想要或不想要这个数据(消息),系统A一点都不关心。
      即便现在系统D不想要userId这个数据了,系统B又突然想要userId这个数据了,都跟系统A无关,系统A一点代码都不用改。
      系统D拿userId不再经过系统A,而是从消息队列里边拿。系统D即便挂了或者请求超时,都跟系统A无关,只跟消息队列有关。
 
2.异步:  假设系统A运算出userId具体的值需要50ms,调用系统B的接口需要300ms,调用系统C的接口需要300ms,调用系统D的接口需要300ms。那么这次请求就需要50+300+300+300=950ms
      并且我们得知,系统A做的是主要的业务,而系统B、C、D是非主要的业务。比如系统A处理的是订单下单,而系统B是订单下单成功了,那发送一条短信告诉具体的用户此订单已成功,而系统C和系统D也是处理一些小事而已。
3.削峰/限流:  系统B和系统C根据自己的能够处理的请求数去消息队列中拿数据,这样即便有每秒有8000个请求,那只是把请求放在消息队列中,去拿消息队列的消息由系统自己去控制,这样就不会把整个系统给搞崩。

2.RocketMQ由哪些角色组成,每个角色作用和特点是什么?

  RocketMQ主要由 Producer、Broker、Consume组成。Producer 生产消息,Consumer 消费消息,Broker 存储消息。
    1、消息生产者(producer):负责生产消息,一般由业务系统负责生产消息。一个消息生产者会把业务应用系统里产 生的消息发送到broker服务器。RocketMQ提供多种发送方式,同步发送、异步发送、顺 序发送、单向发送。同步和异步方式均需要Broker返回确认信息,单向发送不需要。
    2、消息消费者(Consumer):负责消费消息,一般是后台系统负责异步消费。一个消息消费者会从Broker服务器拉 取消息、并将其提供给应用程序。从用户应用的角度而言提供了两种消费形式:拉取式消费 (pull consumer)、推动式消费(push consumer)。
    3、代理服务器(Broker Server):消息中转角色,负责存储消息、转发消息。代理服务器在RocketMQ系统中负责接收 从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。代理服务器也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等。
 

RocketMQ从消费发送到消费的执行流程

  1. Producer发送消息到Broker,负载均衡策略默认随机
  2. Broker接收消息,写入PageCage,返回成功
  3. Broker刷盘,消息存储Consumer queue、commit log
  4. Consumer从Broker拉取消息,拉取方式长轮询pull
  5. Consumer消费消息,处理业务逻辑
  6. Consumer返回ACK,更新Broker offset
  7. 消费失败,消息转入失败队列
  8. Broker的ScheduleService从重试队列拉取消息,重放这个消息
  9. 重试16次如果还是失败,消息进入死信队列
  10. 通过RocketMQ操作面板监控死信队列,手动处理

3.RocketMQ Broker中的消息被消费后会立即删除吗?

    不会,每条消息都会持久化到CommitLog中,每个consumer连接到broker后会维持消费进度信息,当有消息消费后只是当前consumer的消费进度(CommitLog的offset)更新了。

  那么消息会堆积吗?什么时候清理过期消息?

    4.6版本默认48小时后会删除不再使用的CommitLog文件。

      - 检查这个文件最后访问时间
      - 判断是否大于过期时间
      - 指定时间删除,默认凌晨4点

RocketMQ消费模式有几种?

  消费模型由consumer决定,消费维度为Topic

集群消费,广播消费

  集群消费: 一组consumer同时消费一个topic,可以分配消费负载均衡策略分配consumer对应消费topic下的哪些queue

        多个group同时消费一个topic时,每个group都会消费到数据

        一条消息只会被一个group中的consumer消费,

  广播消费: 消息将对一 个Consumer Group 下的各个 Consumer 实例都消费一遍。即使这些 Consumer 属于同一个Consumer Group ,消息也会被 Consumer Group 中的每个 Consumer 都消费一次。

顺序消息缺陷

发送顺序消息无法利用集群Fail Over特性消费顺序消息的并行度依赖于队列数量队列热点问题,个别队列由于哈希不均导致消息过多,消费速度跟不上,产生消息堆积问题遇到消息失败的消息,无法跳过,当前队列消费暂停。

消费消息时使用的是push还是pull?

  在刚开始的时候就要决定使用哪种方式消费。两种(都实现了MQConsumerInner接口):

         `DefaultLitePullConsumerImpl` 拉

         `DefaultMQPushConsumerImpl`推

  名称上看起来是一个推,一个拉,但实际底层实现都是采用的**长轮询机制**,即拉取方式broker端属性 longPollingEnable 标记是否开启长轮询。默认开启。

为什么要主动拉取消息而不使用事件监听方式?

  事件驱动方式是建立好长连接,由事件(发送数据)的方式来实时推送。

  如果broker主动推送消息的话有可能push速度快,消费速度慢的情况,那么就会造成消息在consumer端堆积过多,同时又不能被其他consumer消费的情况。

NameServer实现原理:

  NameServer是一个非常简单的Topic路由注册中心,其角色类似Dubbo中的zookeeper,支持Broker的动态注册与发现。主要包括两个功能

    Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;
路由信息管理,每个NameServer将保存关于Broker集群的整个路由信息和用于客户端查询的队列信息。然后Producer和Conumser通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费
    NameServer通常也是集群的方式部署,各实例间相互不进行信息通讯。Broker是向每一台NameServer注册自己的路由信息,所以每一个NameServer实例上面都保存一份完整的路由信息。当某个NameServer因某种原因下线了,Broker仍然可以向其它NameServer同步其路由信息,Producer,Consumer仍然可以动态感知Broker的路由的信息

  NameServer实例之间互不通信,这本身也是其设计亮点之一,即允许不同NameServer之间数据不同步(像Zookeeper那样保证各节点数据强一致性会带来额外的性能消耗)

RocketMQ集群模式

  单Master模式:只有一个 Master节点

​     优点:配置简单,方便部署

​     缺点:这种方式风险较大,一旦Broker重启或者宕机时,会导致整个服务不可用,不建议线上环境使用

  多Master模式: 一个集群无 Slave,全是 Master,例如 2 个 Master 或者 3 个 Master

​     优点:配置简单,单个Master 宕机或重启维护对应用无影响,在磁盘配置为RAID10 时,即使机器宕机不可恢复情况下,由与 RAID10磁盘非常可靠,消息也不会丢(异步刷盘丢失少量消息,同步刷盘一条不丢)。性能最高。多 Master 多 Slave 模式,异步复制

​     缺点:单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受到受到影响

  多Master多Slave模式(异步复制):每个 Master 配置一个 Slave,有多对Master-Slave, HA,采用异步复制方式,主备有短暂消息延迟,毫秒级。

​     优点:即使磁盘损坏,消息丢失的非常少,且消息实时性不会受影响,因为Master 宕机后,消费者仍然可以从 Slave消费,此过程对应用透明。不需要人工干预。性能同多 Master 模式几乎一样。

​     缺点: Master 宕机,磁盘损坏情况,会丢失少量消息。

  多Master多Slave模式(同步双写):每个 Master 配置一个 Slave,有多对Master-Slave, HA采用同步双写方式,主备都写成功,向应用返回成功。

     优点:数据与服务都无单点, Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高

​     缺点:性能比异步复制模式略低,大约低 10%左右,发送单个消息的 RT会略高。目前主宕机后,备机不能自动切换为主机。

broker如何处理拉取请求的?

  consumer首次请求broker,broker中是否有符合条件的消息

   有 -> 响应consumer,等待下次consumer的请求
   没有->挂起consumer的请求,即不断开连接,也不返回数据
   挂起时间长短,长轮询写死,短轮询可以配
  使用consumer的offset,DefaultMessageStore#ReputMessageService#run方法:每隔1ms检查commitLog中是否有新消息,有的话写入到pullRequestTable,当有新消息的时候返回请求,PullRequestHoldService 来Hold连接,每个5s执行一次检查pullRequestTable有没有消息,有的话立即推送。

 

RocketMQ如何做负载均衡?

通过Topic在多broker种分布式存储实现。

producer端

  发送端指定Target message queue发送消息到相应的broker,来达到写入时的负载均衡

    - 提升写入吞吐量,当多个producer同时向一个broker写入数据的时候,性能会下降
    - 消息分布在多broker种,为负载消费做准备

  每 30 秒从 nameserver获取 Topic 跟 Broker 的映射关系,近实时获取最新数据存储单元,queue落地在哪个broker中

  在使用api中send方法的时候,可以指定Target message queue写入或者使用MessageQueueSelector

默认策略是随机选择:

  - producer维护一个index
  - 每次取节点会自增
  - index向所有broker个数取余
  - 自带容错策略

consumer端

客户端完成负载均衡

  - 获取集群其他节点
  - 当前节点消费哪些queue
  - **负载粒度直到Message Queue**
  - **consumer的数量最好和Message Queue的数量对等或者是倍数,不然可能会有消费倾斜**
  - 每个consumer通过**balanced**维护processQueueTable
  - processQueueTable为当前consumer的消费queue
  - processQueueTable中有 
    - ProcessQueue :维护消费进度,从broker中拉取回来的消息缓冲
    - MessageQueue : 用来定位查找queue

负载均衡算法

  平均分配策略(默认)(AllocateMessageQueueAveragely)
  环形分配策略(AllocateMessageQueueAveragelyByCircle)
  手动配置分配策略(AllocateMessageQueueByConfig)
  机房分配策略(AllocateMessageQueueByMachineRoom)
  一致性哈希分配策略(AllocateMessageQueueConsistentHash)
  靠近机房策略(AllocateMachineRoomNearby)

 MQ与DB一致性原理(两方事务):

单个topic可以分布在多个broker上吗?
  • 一个topic分布在多个broker上,一个broker可以配置多个topic,它们是多对多的关系。 
  •  如果某个topic消息量很大,应该给它多配置几个队列,并且尽量多分布在不同broker上,减轻某个broker的压力。
  •  topic消息量都比较均匀的情况下,如果某个broker上的队列越多,则该broker压力越大。

RocketMQ如何做负载均衡

Topic在Broker集群中分布式存储

Producer端:轮询
Consumer端:平均分配策略,一个队列最多被一个消费组的一个Consumer消费,一个Consumer可以消费多个队列

 

重复消费的原因

  • Consumer消费完,宕机,未返回ACK
  • Consumer消费完,返回ACK,网络断开,Broker未收到
  • 主Broker更新ACK,副Broker未复制,主Broker宕机
 

消息被重复消费,如何保证(也就是说,在某个consumser已经消费过了,由于网络波动或者其他原因导致的消息重复被消费)?

  幂等性,也就是访问多次,结果不变,实现的方式有很多,通过flag去标记,或者保存数据库的时候,将其中某个字段设置为主键,当再次被消费的时候,发现主键存在数据库,则不再进行消费。  
 

broker与nameserver关系

  • 连接
  • 单个broker和所有nameserver保持长连接
  • 心跳
  •      心跳间隔:每隔30秒(此时间无法更改)向所有nameserver发送心跳,心跳包含了自身的topic配置信息。
  •      心跳超时:nameserver每隔10秒钟(此时间无法更改),扫描所有还存活的broker连接,若某个连接2分钟内(当前时间与最后更新时间差值超过2分钟,此时间无法更改)没有发送心跳数据,则断开连接。
  •  断开
  •      时机:broker挂掉;心跳超时导致nameserver主动关闭连接
  •      动作:一旦连接断开,nameserver会立即感知,更新topc与队列的对应关系,但不会通知生产者和消费者
可用性
   由于消息分布在各个broker上,一旦某个broker宕机,则该broker上的消息读写都会受到影响。所以rocketmq提供了master/slave的结构,salve定时从master同步数据,如果master宕机,则slave提供消费服务,但是不能写入消息,此过程对应用透明,由rocketmq内部解决。
这里有两个关键点:
  • 一旦某个broker master宕机,生产者和消费者多久才能发现?受限于rocketmq的网络连接机制,默认情况下,最多需要30秒,但这个时间可由应用设定参数来缩短时间。这个时间段内,发往该broker的消息都是失败的,而且该broker的消息无法消费,因为此时消费者不知道该broker已经挂掉。
  •  消费者得到master宕机通知后,转向slave消费(重定向,对于2次开发者透明),但是slave不能保证master的消息100%都同步过来了,因此会有少量的消息丢失。但是消息最终不会丢的,一旦master恢复,未同步过去的消息会被消费掉。
靠性
  •  所有发往broker的消息,有同步刷盘和异步刷盘机制,总的来说,可靠性非常高
  •  同步刷盘时,消息写入物理文件才会返回成功,因此非常可靠
  •  异步刷盘时,只有机器宕机,才会产生消息丢失,broker挂掉可能会发生,但是机器宕机崩溃是很少发生的,除非突然断电
消息清理
  • 扫描间隔
  •      默认10秒,由broker配置参数cleanResourceInterval决定
  •  空间阈值
  •      物理文件不能无限制的一直存储在磁盘,当磁盘空间达到阈值时,不再接受消息,broker打印出日志,消息发送失败,阈值为固定值85%
  •  清理时机
  •      默认每天凌晨4点,由broker配置参数deleteWhen决定;或者磁盘空间达到阈值
  •  文件保留时长
  •      默认72小时,由broker配置参数fileReservedTime决定
消费者与nameserver关系
  •   连接
  •      单个消费者和一台nameserver保持长连接,定时查询topic配置信息,如果该nameserver挂掉,消费者会自动连接下一个nameserver,直到有可用连接为止,并能自动重连。
  • 心跳
  • 与nameserver没有心跳
  •  轮询时间
  • 默认情况下,消费者每隔30秒从nameserver获取所有topic的最新队列情况,这意味着某个broker如果宕机,客户端最多要30秒才能感知。该时间由DefaultMQPushConsumer的pollNameServerInteval参数决定,可手动配置。
与broker关系
  •  连接
  • 单个消费者和该消费者关联的所有broker保持长连接。
  • 心跳
  • 默认情况下,消费者每隔30秒向所有broker发送心跳,该时间由DefaultMQPushConsumer的heartbeatBrokerInterval参数决定,可手动配置。broker每隔10秒钟(此时间无法更改),扫描所有还存活的连接,若某个连接2分钟内(当前时间与最后更新时间差值超过2分钟,此时间无法更改)没有发送心跳数据,则关闭连接,并向该消费者分组的所有消费者发出通知,分组内消费者重新分配队列继续消费
  •  断开
  • 时机:消费者挂掉;心跳超时导致broker主动关闭连接
  • 动作:一旦连接断开,broker会立即感知到,并向该消费者分组的所有消费者发出通知,分组内消费者重新分配队列继续消费
负载均衡
集群消费模式下,一个消费者集群多台机器共同消费一个topic的多个队列,一个队列只会被一个消费者消费。如果某个消费者挂掉,分组内其它消费者会接替挂掉的消费者继续消费。
消费机制
  •  本地队列
  •     消费者不间断的从broker拉取消息,消息拉取到本地队列,然后本地消费线程消费本地消息队列,只是一个异步过程,拉取线程不会等待本地消费线程,这种模式实时性非常高(本地消息队列达到解耦的效果,响应时间减少)。对消费者对本地队列有一个保护,因此本地消息队列不能无限大,否则可能会占用大量内存,本地队列大小由DefaultMQPushConsumer的pullThresholdForQueue属性控制,默认1000,可手动设置。
  •  轮询间隔
  •      消息拉取线程每隔多久拉取一次?间隔时间由DefaultMQPushConsumer的pullInterval属性控制,默认为0,可手动设置。
  • 消息消费数量
  •      监听器每次接受本地队列的消息是多少条?这个参数由DefaultMQPushConsumer的consumeMessageBatchMaxSize属性控制,默认为1,可手动设置。
默认的分配算法是AllocateMessageQueueAveragely
还有另外一种平均的算法是AllocateMessageQueueAveragelyByCircle,也是平均分摊每一条queue,只是以环状轮流分queue的形式,如下图:
消费进度存储
     每隔一段时间将各个队列的消费进度存储到对应的broker上,该时间由DefaultMQPushConsumer的persistConsumerOffsetInterval属性控制,默认为5秒,可手动设置。
 
如果一个topic在某broker上有3个队列,一个消费者消费这3个队列,那么该消费者和这个broker有几个连接?
     一个连接,消费单位与队列相关,消费连接只跟broker相关,事实上,消费者将所有队列的消息拉取任务放到本地的队列,挨个拉取,拉取完毕后,又将拉取任务放到队尾,然后执行下一个拉取任务
不同消费者组中,对于同一条消息,可以重复消费,也就是说,A消息在group1中消费过后,到了group2中是一条新消息。
同一个消费者组中的消费者,可以订阅不同的topic吗?
不可以
这里其实是做了一个检查,做这个检查的默认前提是一个consumerGroup下面的订阅消息是一样的,就是每个consumer注册的subscription应该是一样的,如果不一样就把之前注册的删除。
consumerTable中存放的消费者信息是按照消费组来的,那么一个组的消费信息如果不一样,按照我们的例子中,则订阅了  TOPICA的消费者心跳信息告诉  Broker:我们组订阅的是  TOPICA!然后  Broker
就记录下来了。过了一会订阅了  TOPICB的消费者心跳信息高速  Broker:我们订阅的是  TOPICB! 
这里就导致了订阅消息相互覆盖,那么拉取消息时,肯定有一个消费者没法拉到消息,因为  Broker
上查询不到订阅信息。
 
RocketMQ了如何处理?UI转圈,不给响应。
1. 对于大规模消息发送接收可以使用pull模式,手动处理消息拉取速度,消费的时候统计消费时间以供参考,保证单机上的消费速率。
2. 什么情况下产生消息堆积?
1.consumer故障(断网,断电等)导致,consumer重启。
 
2.堆积的消息会过期吗?不会过期,只有cimmitLog过期,如果堆积了几百万条,怎么处理?
即使加设备,上线了几个新的consumer,短时间内也处理不了堆积的消息,超过一小时,怎么处理?

消息堆积如何处理

1、增加Consumer,增加MessageQueue,增加Consumer线程数
2、新建一个Topic,先消费将消息搬运到另外一个Topic,后用新Consumer消费处理

主要是在消费的时候,会影响新写进来的消息,队列:先进先出。
1.新上线一个group,将fromWhere定位到最后面,保证新来的消息可以正常处理,另外,让老的group继续消费堆积的消息,两个并行执行。为了防止重复消费新的消息,记录fromwhere,当offset到达fromwhere时候,停止消费。
2.准备一个临时的topic,把原来topic中的消息挪到新的topic里,不做业务逻辑处理,只是挪过去, 新上线一个group,其中的consumer同时消费临时topic中的数据。老的consumer继续消费老的topic,因为此时老的topic中的消息是新进来的消息,刚生产的消息。只适合并发消费,临时消费。
消息永远在cimmitlog中,根据cimmitlog啥时候访问到最后,超过48小时,才会进行删除。
消息消费,是以group进行区分的,不是在message上打标记。维护了内存中的一个mapgroup,对应的offset定位消息消费到哪里,消息失败的时候,先会进retry重试队列,超过16次之后,才会进入到死信队列,死信队列也是根据group来的。
 

rocketmq怎么保证队列完全顺序消费?

RocketMQ可以严格的保证消息有序。但这个顺序,不是全局顺序,只是分区(queue)顺序。要全局顺序只能一个分区。

也就是说:同一个topic,在broker中默认会存在多个queue,因此,不论是producer还是customer,都不能保证生产或者消费的顺序性,即消费端消费的时候,是会分配到多个queue的,多个queue是同时拉取提交消费。生产者同样。

在同一条queue里面,RocketMQ的确是能保证FIFO的。那么要做到顺序消息,必须把消息确保投递到同一条queue。

 
posted @ 2020-11-26 17:19  c++c鸟  阅读(1393)  评论(0编辑  收藏  举报