《RockeMQ实战与原理解析》 学习笔记

第 1 章

1.1 消息队列的功能介绍

1.1.1 应用解耦

1.1.2 流量消峰

大部分应用系统在高峰时间段流量会猛增, 这个时候如果没有缓冲机制,不可能短时间内承受住大流量的冲击。利用消息队列,把大量的请求缓存起来,分散到相对长的一个时间段内处理,能大大提高系统的稳定性和用户提现。

1.1.3 消息分发

数据产生方将数据写入一个消息队列,数据使用方根据自己的需求订阅感兴趣的数据,各个团队订阅的数据可以重复,也可以不重复,互不干扰。

第 2 章 生产环境下的配置和使用

2.1 RocketMQ 各部分角色介绍

(1) Producer, Consumer, Broker, NameServer
(2) Producer, Consumer 从 NameServer 获得 Broker 的 信息。
(3) Broker 将 Routing Info 发给 NameServer。
(4) Producer 发送 消息 给Broker, Consumer 从Broker 接受消息。
(5) Topic: 发送和接收消息前, 先创建Topic, 针对某个Topic 发送接收消息。
(6) MessageQueue: 如果一个Topic 要发送或接收的消息量特别大,需要能够支持并行处理的机器来提高速度,这时候一个Topic 可以根据需要设置多个MessageQueue,MessageQueue 类似分区或Partition. 消息可以并行的向各个MessageQueue 发送,消费者也可以并行的从多个MessageQueue读取消息并消费

2.2 多机集群配置和部署

两台物理机,搭建双主,双从,无单点故障的高可用集群。 首先在两台机器上分别启动NameServer。然后分别启动一个Master角色的Broker 和一个Slave 角色的Broker, 并且互相为主备。

2.2.2 Broker配置文件参数配置介绍

(1) namsrvAddr = 192.168.100.131:9876;192.168.100.132:9876 namesrv 端口
(2) brokerName=broker-a
Broker 的名称, Master 和Slave通过相同的名称标识相互关系,说明某个Slave 是哪个Master 的Slave
(3)brokerId=0
一个master可以有多个Slave, 0 表示Master, 大于0 表示不同的Slave的ID。
(4)fileReservedTime=48
在磁盘上保存消息的时长,单位是小时,自动删除超时消息。
(5)deleteWhen=04
表明在几点做删除消息的动作, 默认是凌晨4点。
(6)brokerRole=SYNC_MASTER
brokerRole 有3种: SYNC_MASTER, ASYNC_MASTER, SLAVE。关键词SYNC和ASYNC表示Master和Slave之间同步消息的机制。SYNC表示当Slave和Master消息同步完成后,再返回发送成功的状态
(7)flushDiskType=ASYNC_FLUSH
表示刷盘策略,分别为SYNC_FLUSH, ASYNC_FLUSH。同步刷盘和异步刷盘。同步:消息真正写入磁盘后再返回成功。异步:消息写入page_cache 后就返回成功状态。
(8)listenPort=10911
Broker 监听的端口, 如果一台物理机器上启动了多个Broker, 则要设置不同的端口。

第 3 章 用合适的方式发送和接收消息

3.1 不同类型的消费者

3.1.1 DefaultMQPushConsumer

(1) DefaultMQPushConsumer 的三个参数:

  • GroupName:把多个Consumer 组织到一起,提供并发处理能力,GroupName 要和消息模式(MessageModel)配合使用。
  • NameServer的地址和端口号。
  • Topic: 需要提前创建。如果不需要消费某个Topic下的所有消息,可以指定消息的Tag进行消息的过滤, 比如 Conusmer.subscribe(“TopicTest”, “tag1||tage2”)。

(2) RocketMQ 支持的两种消息模式:Clustering 和 Broadcasting:

  • Clustering: 同一个ConsumerGroup(GroupName相同)里的每个Consumer 只消费所订阅的一部分内容,同一个ConsumerGroup里所有的Consumer消费的内容合起来是所订阅的Topic内容的整体,从而达到负载均衡。
  • Broadcasting; 同一个ConsumerGroup 里的每个Consumer 都能消费到所订阅的Topic的全部内容,也就是一个消息被分发多次,被多个Consumer消费。

(3)DefaultMQPushConsumer的处理流程

  • 通过“长轮询”方式达到Push效果,长轮询方式既有Pull的优点,有兼具Push方式的实时性。
  • Push 方式: 当Server端接收到消息后,主动把消息推送给Client端,优点是实时性高。缺点是:(1)首先加大了Server端的工作量,进而影响Server的性能。(2)如果客户端不能及时处理Server推送过来的消息,会造成各种潜在危险。
  • Pull 方式:Client 段循环的从Server段拉取消息,优点是:自己拉取到一定量消息后,处理妥当了再接着取。缺点是:拉取消息的时间间隔不好设置,间隔短就是忙等,间隔长Server段的消息没有被及时拉取。
  • 长轮询方式核心是–Broker 端HOLD 住客户端过来的请求一小段时间,在这个时间段内有新消息到达,就利用现有的连接返回给Consumer。通过 RequestHeader.setSuspendTimeoutMillis() 语句实现,默认设置是15秒。服务端接到客户端请求新消息的请求后,如果队列里没有消息,就会通过一个循环,每5秒检查下有没有消息。如果第三次检查,依旧没有消息,就返回NULL。如果等待的过程中,有新消息到达,就会立即返回给客户段。局限性:HOLD住Consumer 请求的时候需要占用资源,适合用在客户端连接数可控的场景。

(4) DefaultMQPushConsumer 的流量控制

  • 流量的控制。首先,每个MessageQueue都有一个快照类ProcessQueue保存这个MessageQueue 消息处理的状态, 比如当前消息堆积的个数,当前消息的总大小,消息的Offset的跨度。只要任何一个值超过设定的预设大小,就会过一段时间再去Broker拉去消息,从而达到流量控制的目的。
  • ProcessQueue对象主要内容是一个TreeMap和一个读写锁。 TreeMap 以Message Queue 的offeSet 为key, 消息的内容为Value, 保存MessageQueue中还未处理的消息。 读写锁控制对ProcessQueue的并发访问。

3.2 消息的生产者

不同的业务场景,需要生产者采用不同的写入策略:同步发送,异步发送,延迟发送,发送事务消息。

3.2.1 DefaultMQProducer

  • 发送消息的五个步骤: (1)设置Producer的GroupName (2)设置InstanceName。当一个JVM需要启动多个Producer的时候,通过设置InstanceName来区分。(3)设置发送失败重试次数。(4) 设置NameServer地址。(5) 组装消息并且发送
  • 消息发送返回状态:(1)Flush_DISK_TIMEOUT (2)FLUSH_SLAVE_TIMEOUT (3)SLAVE_NOT_AVAILABLE (4)SEND_OK

3.2.2 发送延迟消息

  • Broker 收到这类消息后,延迟一段时间在处理,使消息在规定的一段时间后生效。
  • 延迟消息使用方法是在创建Message对象时,调用setDelayTimeLevel(int level)。

3.2.3 自定义消息发送规则

  • 一个Topic 会用多个Message Queue, 如果使用Producer的默认设置,这个Producer会轮流向各个Message Queue 发送消息。Consumer 在消费消息时,会根据负载均衡策略,消息被分配到的Message Queue, 不进过特定的设置,某条消息被发送到哪个MessageQueue, 被哪个Consumer 消费是未知的。
  • 把同一类型的消息都发送到相同的Message Queue,使用 MessageQueueSelector。实现顺序消费, 见我的博客 《RocketMQ实现顺序消息》。

3.2.4 事务消息

  • 发送消息事件和其他事件需要同时成功或同时失败。
  • RocketMQ 采用两阶段提交的方式实现事务消息。
  • (1)发送方向Broker 发送 “待确认”消息(b 银行增加一万元)。
  • (2)Broker 将受到的 “待确认”消息持久化成功,向发送方回复消息已经发送成功。
  • (3)发送方执行本地的事件逻辑(从A银行账户扣除一万元)。
  • (4)发送方根据本地事件执行结果向Broker发送二次确认(Commit或者Rollback)消息。如果Broker收到commit 则将第一阶段消息标记位可投递,订阅发就可以收到这个消息。如果Broker收到Rollback状态,则删除第一阶段的消息,订阅方就收不到消息。
  • (5)如果步骤 4)出现异常,commit/rollback 消息不能到达broker,服务器在进过固定时间段后将对“待确认”的消息发起回查请求
  • (6)发送端收到5)的消息请求后,检查对应消息的本地事件执行结果返回commit/rollback。
  • (7)事务消息的缺点:RocketMQ 依赖将数据顺序写入磁盘来提高性能,但是步骤4)却需要更改第一阶段消息的状态,这样会降低系统性能。
  • (8)三个类支持事务消息:LocalTransactionExecuter, TransactionMQProducer, TransactionCheckListener。

3.3 存储队里位置信息

  • (1)offset 含义:指某个Topic下的一条消息在某个Message Queue里的位置。
  • (2)Offset 分为本地文件类型Broker代存类型。当消息模式为CLUSTERING, Broker段控制和存储Offset的值,使用RemoteBrokerOffsetStore。当消息模式是BROADCASTING时,RocketMQ使用LocalFileOffsetStore, 把offset存放在本地。
  • (3)offset采用json格式存储。

第 4 章

4.1 NameServer 的功能

  • NameServer是整个消息队列的状态服务器,集群的各个组件 通过它来了解全局的信息。
  • NameServer 中的Broker, Topic 等状态信息不会被持久化存储,存储在内存中。
  • 他的主要功能包括两部分:Broker 定期上报自己的状态信息到NameServer;另一个是各个客户端,包括Producer,Consumer 之间获取最新的状态信息。NameServer 占用资源不多,可以和Broker 部署在同一机器上。

4.1.1 集群状态的存储

  • 在RouteInfoManager类中,通过map去存储。

4.1.2 状态维护逻辑

  • NameServer 根据Broker上报消息里的请求码做相应的处理,更新存储Map的信息。

4.2.2 为什么不用ZooKeeper

ZooKeeper功能强大,包括自动Master选举等。RocketMQ的架构不需要ZooKeeper复杂的功能,只要一个轻量级的元数据服务器即可。而且中间节RockMQ对稳定性要求高,NameServer代码少,好维护,少依赖一个另外的中间件,减少维护成本。

第 5 章 消息队列的核心机制

5.2 消息存储结构

(1) 消息真正的物理存储文件是CommitLog, ConsumerQueue是消息的逻辑队列,类似数据库的索引文件,存储的是指向物理存储的地址。
(2)每个Topic下的每个Message Queue都有一个对应的ConsumeQueue。
(3)一条ConsumeQueue的格式: (CommitLog Offerset)8byte + (Size)4byte + (Tag HashCode)8byte
(4) CommitLog顺序写,可以大大提高写的速度

5.3 高可用性机制

(1)RocketMQ 分布式集群是通过Master 和 Slave 的配合达到高可用性的。Master 角色的Broker支持读写,Slave角色支仅支持读,也就是Producer只能和Master角色的Broker连接写入消息;Consumer 可以连接Master角色的Broker, 也可以连接Slave角色的Broker来读取消息。
(2)消费端的高可用性 :当一个Master角色的机器出现故障后,Consumer任然可以从Slave读取消息,不影响Conumer程序。这就达到了消费端的高可用性。
(3)发送端高可用性:相同Broker名称,不同BrokerId 的机器组成一个Broker组。当一个Broker组的Master不可用,其他组的Master任然可用。

5.4 同步刷盘和异步刷盘

(1)异步刷盘:在返回写成功状态时,消息可能只是被写入了内存的PAGECACHE,这样写操作的返回快,吞吐量大。当内存里的消息累计到一定程度,统一触发写磁盘操作,快速写入。
(2)同步刷盘:在返回写成功状态时,消息已经被写入磁盘。具体流程是,消息写入内存的PAGECACHE后,立即通知刷盘线程刷盘,等刷盘完成后,返回消息写成功的状态。
(3)刷盘方式通过Broker配置文件中的flushDiskType参数设置。

5.5 同步复制和异步复制

如果一个Broker组有Master 和 Slave, 消息需要从 Master 复制到 Slave 上, 有同步和异步两种复制方式
(1)同步复制:等 Master 和 Slave 均写成功后才反馈客户端写成功状态。
(2)异步复制:只要 Master 写成功即可返回给客户端写成功状态。
(3)复制方式通过Broker配置文件中的brokerRole参数进行配置:ASYNC_MASTER, SYNC_MASTER, SLAVE三个参数。
(4)最佳实践: 刷盘方式(ASYNC_FLUSH) + 复制方式(SYNC_MASTER)。

第 6 章 可靠性优先的使用场景

6.1 部分顺序消费

参见我的《RocketMQ实现顺序消息》一文。

6.2 消息重复问题

(1)消息重复一般不会发生,但是如果消息量大,网络有波动,消息重复就是大概率时间。
(2)解决消息重复的方法:第一种是保证消费逻辑的幂等性,就是多次调用和一次调用的结果是一样的。另一种是维护一个已消费的记录,消费前查询这个消息是否被消费过。

6.3 动态增减机器

由于业务的需求或硬件的故障,经常需要增加或减少各个角色的机器,要做到在不影响服务稳定性的情况下动态增减机器。

6.3.1 动态增减 NameServer

通过HTTP服务来设置,程序回向一个HTTP 地址发送请求来获取 NameServer 地址。其他组件会每隔2分钟请求一次这个URL,获取最新的NameServer 地址。这种方式,无需重启其他组件。

6.3.2 动态增减Broker

集群扩容后,一种方式是:把新建的 Topic 指定到新的 Broker 机器上, 另一种方式是通过updateTopic 命令更改现有的Topic 配置,在新加的 Broker 上创建新的对列。

6.5 消息优先级

(1)RocketMQ 是个先进先出的对列,不支持消息级别或者 Topic 级别的优先级。业务中简单的优先级需求,可以通过间接的方式解决。
(2) 如果当前 Topic 中有很多相似类型的消息,不如AA, AB,AC,当AB, AC 的消息量很大,但是处理速度比较慢的时候,队列中会有很多AB, AC 类型的消息, AA 的消息就会等待很久才会被处理。解决方法是,可以把 AA 类型放在一个单独的 Topic 中, AB, AC 放在另外一个 Topic 中

第 7 章 吞吐优先的使用场景

7.1 在 Broker 段进行消息过滤

Broker 段有三种方式进行消息过滤。

7.1.2 通过 Tag 进行过滤

(1)一个应用尽量只用一个 Topic, 不同的消息子类型用 Tag 来标识。发送消息设置了 Tag 以后, 消费方在订阅消息时,才能利用 Tag 在 Broker 段做消息过滤。
(2)一个 Message 只能有一个 Tag。Broker 在 ConsumeQueue 中做这种过滤,只从 CommitLog 里读取过滤后被命中的消息。ConsumeQueue 的存储格式 CommitLogOffeset + Size + MessageTagHash

7.1.3 用SQL 表达式的方式进行过滤

(1)在构造 Message 的时候, 通过 putUserProperty 函数来增加多个自定义的属性。
(2) SQL 表达式的过滤需要Broker 先读出消息里内容,然后做 SQL 计算, 增大磁盘压力,没有 Tag 方式高效。

7.1.4 Filter Server 方式过滤

(1)在 Broker 配置FilterServer。这种方式会占用很多Broker 机器的CPU,要慎用。

7.2 提高Consumer 处理能力

三种方式提高Consumer 的处理能力:
(1) 先查看消费逻辑本省有没有优化的空间。

(2)提高消费并行度。在同一个ConsumerGroup 下(Clustering),可以通过增加Consumer 实例的数量来提高并行度(增加机器,或者在已有机器中增加多个Consumer 进程)。但注意:总的Consumer数量不要超过Read Queue数量。超过d的Consumer 实例接受不到消息。或者,提高单个Consumer 实例的并行处理线程数。(设置方法是修改conusmeThreadMin 和 consumeThreadMax)。

(3)以批量的方式进行消费。设置consumeMessageBatchMaxSize 这个参数。

7.3 Consumer 的负载均衡

(1)见我的博客《RocketMQ Consumer 负载均衡算法学习 xxx》
(2)负载均衡的结果与 Topic 的 MessageQueue数量,以及 ConsumerGroup 里的 Consumer 的数量有关。
(3)通常情况下把一个 Topic 的 Message Queue 数设置为16。

7.4 提高 Prouducer 的发送速度

(1)在一些对速度要求高,但可靠性要求不高的场景下,比如日志搜集应用,可以采用 Onway 方式发送。OneWay 方式只发送请求不等待响应,即将数据写入客户端的 Socket 缓冲区就返回,不等待返回结果。这样耗时可以降到微妙。

第 8 章 和其他系统交互

8.1 在 SpringBoot 中使用 RocketMQ

(1)把 NameServer 的地址, GroupName 名称和 Topic 名称 写到 application.properties 配置文件中。
(2)把 Producer 和 Consumer 的初始化操作代码写到 @PostConstruct 和 @PreDestory 函数中。把发送消息和消费消息的功能封装成 Service,供其他代码使用。

8.2 直接使用云上 RocketMQ

(1)中小型企业公司,直接使用云产品,可以省去部署,运维的繁琐工作,加快自身核心产品的上线速度

第 11 章 最常用的消费类

11.2 消息的并发处理

(1)ConsumeMessageConcurrentlyService 定义了三个线程池。一个是主线程用来执行收到的消息。另外两个都是单线程的线程池,一个是用来执行推迟消费的消息,另外一个用来定期清理超时消息(15分钟)。
(2)ProcessQueue 对象 。每个 Message Queue 都会有一个对应的 ProcessQueue 都会有一个对应的 ProcessQueue 对象,保存这个 Message Queue 消息处理状态的快照,比如:当前消息堆积的数量。ProcessQueue 对象主要是一个 TreeMap 和 一个读写锁。MessageQueue 中的 offset 是 key, 消息内容是 value。

11.3 MQClientInstance

(1)MQClientInstance 类是各种 Consumer 和 Producer 的底层类

(2)普通情况下,一个 RockeMQ 客户端的 Java 程序, 或者说一个JVM进程只要有一个 MQClientInstance。也就是说,一个或多个 Consumer 或者 Producer,底层使用的是一个 MQClientInstance。

(3)有些情况下一个MQClientInstance 对象是不够的,比如一个Java 程序需要连接两个 RocketMQ集群,从一个集群读取消息,发送到另外一个集群。这种情况下一定要手动指定不同的InstanceName,底层会创建两个 MQClientInstance 对象。

你可能感兴趣的:(RocketMQ)