揭开RocketMQ的神秘面纱

RocketMQ概念

RocketMQ 前身叫做 MetaQ, 在 MeataQ 发布 3.0 版本的时候改名为 RocketMQ,其本质上的设计思路和 Kafka 类似,但是和 Kafka 不同的是其使用 Java 进行开发,由于在国内的 Java 受众群体远远多于 Scala,所以 RocketMQ 是很多以 Java 语言为主的公司的首选。同样的 RocketMQKafka 都是 Apache 基金会中的顶级项目,他们社区的活跃度都非常高,项目更新迭代也非常快。

NameServer架构设计

Broker 消息服务器在启动时向所有 NameServer 注册,消息生产者(Producer)在发送消息之前先从 NameServer 获取 Broker 服务器地址列表,然后根据负载算法从列表中选择一台消息服务器进行消息发送 NameServer 与每台 Broker 服务器保持长连接,并间隔 30sBroker 是否存活,如果检测到 Broker 从路由注册表中将其移除 但是路由变化不会马上通知消息生产者,为什么要这样设计呢?这是为了降低 NameSever 实现的复杂,在消息发送端提供容错机制来保证消息发送的高可用性。

NameServer 本身的高可用可通过部署多台 NameServer 服务器来实现,但彼此之间
互不通信,也就是 NameServer 务器之间在某一时刻的数据并不会完全相同,但这对消
息发送不会造成任何影响,这也是 RocketMQ NameServer 设计的 一个亮点, RocketMQ NameServer 计追求简单高效。

NameServer 的核心架构设计下图所示:

在这里插入图片描述

消息客户端与NameServerBroker的交互设计要点如下:

  • Broker 每隔30s向 NameServer 集群的每一台机器发送心跳包,包含自身创建的 topic 路由等信息。
  • 消息客户端每隔30s向 NameServer 更新对应的 topic 的路由信息。
  • NameServer 收到Broker 发送的心跳包时会记录时间戳。
  • NameServer 每隔10s会扫描一次 brokerLiveTable(存放心跳包的时间戳信息),如果在120s内没有收到心跳包,则认为Broker 失效,更新 topic的路由信息,将失效的Broker 信息移除。

RocketMQ相关名词

RocketMQ 架构图中多个 Producer,多个主 Broker,多个从 Broker,每个 Producer 可以对应多个 Topic,每个 Consumer 也可以消费多个 Topic。

Broker 信息会上报至 NameServer,Consumer 会从 NameServer 中拉取 Broker 和 Topic 的信息。

  • Producer:消息生产者,向 Broker 发送消息的客户端,三种发送方式:同步可靠(sync)、异步可靠(async)、单向不可靠(one way)。
  • Consumer:消息消费者,从 Broker 读取消息的客户端。
  • Broker:消息中间的处理节点,这里和 kafka 不同,kafka 的 Broker 没有主从的概念,都可以写入请求以及备份其他节点数据,RocketMQ 只有主 Broker 节点才能写,一般也通过主节点读,当主节点有故障或者一些其他特殊情况才会使用从节点读,有点类似- 于 mysql 的主从架构。
  • Topic:消息主题,一级消息类型,生产者向其发送消息, 消费者读取其消息。
  • Group:分为 ProducerGroup, ConsumerGroup, 代表某一类的生产者和消费者,一般来说同一个服务可以作为 Group, 同一个 Group 一般来说发送和消费的消息都是一样的。
  • Tag:Kafka 中没有这个概念,Tag 是属于二级消息类型,一般来说业务有关联的可以使用同一个 Tag, 比如订单消息队列,使用 Topic_Order, Tag 可以分为 Tag_食品订单, Tag_服装订单等等。
  • Queue: 在 kafka 中叫 Partition, 每个 Queue 内部是有序的,在 RocketMQ 中分为读和写两种队列,一般来说读写队列数量一致,如果不一致就会出现很多问题。
  • NameServer:Kafka 中使用的是 ZooKeeper 保存 Broker 的地址信息,以及 Broker 的 Leader 的选举,在 RocketMQ 中并没有采用选举 Broker 的策略,所以采用了无状态的 NameServer 来存储,由于NameServer 是无状态的,集群节点之间并不会通信,所以上传数据的时候都需要向所有节点进行发送。

RocketMQ的基本特性

订阅与发布

  • 消息的发布:指某个生产者向某个topic发送消息。
  • 消息的订阅:指某个消费者关注了某个topic中带有某些tag的消息。

消息顺序

消息有序指的是一类消息消费时,能按照发送的顺序来消费。例如:一个订单产生了三条消息分别是订单创建、订单付款、订单完成。消费时要按照这个顺序消费才能有意义,但是同时订单之间是可以并行消费的。RocketMQ可以严格的保证消息有序。

消息过滤

RocketMQ的消费者可以根据Tag进行消息过滤,也支持自定义属性过滤。消息过滤目前是在Broker端实现的,优点是减少了对于Consumer无用消息的网络传输,缺点是增加了Broker的负担、而且实现相对复杂。

消息可靠性

RocketMQ支持消息的高可靠,影响消息可靠性的几种情况:

  • Broker非正常关闭
  • Broker异常Crash
  • 操作系统Crash
  • 机器掉电,但是能立即恢复供电情况
  • 机器无法开机(可能是cpu、主板、内存等关键设备损坏)
  • 磁盘设备损坏

注意:

前四种情况都属于硬件资源可立即恢复情况,RocketMQ在这四种情况下能保证消息不丢,或者丢失少量数据(依赖刷盘方式是同步还是异步)。

最后两种情况属于单点故障,且无法恢复,一旦发生,在此单点上的消息全部丢失。

单点故障解决办法:

RocketMQ在这两种情况下,通过异步复制,可保证99%的消息不丢,但是仍然会有极少量的消息可能丢失。通过同步双写技术可以完全避免单点,同步双写势必会影响性能,适合对消息可靠性要求极高的场合,例如与Money相关的应用。

Consumer消息重试

Consumer消费消息失败后,要提供一种重试机制,令消息再消费一次。

Consumer消费消息失败通常可以认为有以下几种情况:

  • 由于消息本身的原因,例如反序列化失败,消息数据本身无法处理(例如话费充值,当前消息的手机号被注销,无法充值)等。这种错误通常需要跳过这条消息,再消费其它消息,而这条失败的消息即使立刻重试消费,99%也不成功,所以最好提供一种定时重试机制,即过10秒后再重试。
  • 由于依赖的下游应用服务不可用,例如db连接不可用,外系统网络不可达等。遇到这种错误,即使跳过当前失败的消息,消费其他消息同样也会报错。这种情况建议应用sleep 30s,再消费下一条消息,这样可以减轻Broker重试消息的压力。

Producer消息重投

生产者在发送消息时:

  • 同步消息:有重投机制,调用发送消息方法后,同步阻塞,直到返回SendResult。配置retryTimesWhenSendFailed重试次数。
  • 异步消息:有重投机制,调用发送消息方法后,立即返回,发送结果会通过开发者自己注册的回调函数SendCallback进行处理。配置retryTimesWhenSendAsyncFailed重试次数。
  • oneway:没有重投,这种方法完全不关心发送后的返回结果。显然,它具有最大吞吐量,但也存在消息丢失的潜在风险。

消息重投保证消息尽可能发送成功、不丢失,但可能会造成消息重复,消息重复在RocketMQ中是无法避免的问题。消息重复在一般情况下不会发生,当出现消息量大、网络抖动,消息重复就会是大概率事件。另外,生产者主动重发、consumer负载变化也会导致重复消息。

如何设置Producer消息重投?

  1. retryTimesWhenSendFailed

同步发送失败重投次数,默认为2,因此生产者会最多尝试发送retryTimesWhenSendFailed + 1次。不会选择上次失败的broker,尝试向其他broker发送,最大程度保证消息不丢失。超过重投次数,抛异常,由客户端保证消息不丢失。当出现RemotingException、MQClientException和部分MQBrokerException时会重投。

  1. retryTimesWhenSendAsyncFailed

异步发送失败重试次数,异步重试不会选择其他broker,仅在同一个broker上做重试,不保证消息不丢。

  1. retryAnotherBrokerWhenNotStoreOK

消息刷盘(主或备)超时或slave不可用(返回状态非SEND_OK),是否尝试发送到其他broker,默认false。十分重要消息可以开启。

流量控制

  • 生产者流控,因为broker处理能力达到瓶颈;
  • 消费者流控,因为消费能力达到瓶颈。

生产者流控

broker通过拒绝send请求方式实现流量控制。

  1. commitLog文件被锁时间超过osPageCacheBusyTimeOutMills时,参数默认为1000ms,发生流控。
  2. 如果开启transientStorePoolEnable = true,且broker为异步刷盘的主机,且transientStorePool中资源不足,拒绝当前send请求,发生流控。
  3. broker每隔10ms检查send请求队列头部请求的等待时间,如果超过waitTimeMillsInSendQueue,默认200ms,拒绝当前send请求,发生流控。

消费者流控

消费者流控的结果是降低拉取频率。

  • 费者本地缓存消息数超过pullThresholdForQueue时,默认1000。
  • 消费者本地缓存消息大小超过pullThresholdSizeForQueue时,默认100MB。
  • 消费者本地缓存消息跨度超过consumeConcurrentlyMaxSpan时,默认2000。

死信队列

死信队列用于处理无法被正常消费的消息。

当一条消息初次消费失败,消息队列会自动进行消息重试;达到最大重试次数后,若消费依然失败,则表明消费者在正常情况下无法正确地消费该消息,此时,消息队列不会立刻将消息丢弃,而是将其发送到该消费者对应的特殊队列中。

RocketMQ将这种正常情况下无法被消费的消息称为死信消息(Dead-Letter Message),将存储死信消息的特殊队列称为死信队列(Dead-Letter Queue)。

在RocketMQ中,可以通过使用console控制台对死信队列中的消息进行重发来使得消费者实例再次进行消费。

  • 30
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术杠精

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值