大部分应用系统在高峰时间段流量会猛增, 这个时候如果没有缓冲机制,不可能短时间内承受住大流量的冲击。利用消息队列,把大量的请求缓存起来,分散到相对长的一个时间段内处理,能大大提高系统的稳定性和用户提现。
数据产生方将数据写入一个消息队列,数据使用方根据自己的需求订阅感兴趣的数据,各个团队订阅的数据可以重复,也可以不重复,互不干扰。
(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读取消息并消费。
两台物理机,搭建双主,双从,无单点故障的高可用集群。 首先在两台机器上分别启动NameServer。然后分别启动一个Master角色的Broker 和一个Slave 角色的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, 则要设置不同的端口。
(1) DefaultMQPushConsumer 的三个参数:
(2) RocketMQ 支持的两种消息模式:Clustering 和 Broadcasting:
(3)DefaultMQPushConsumer的处理流程
(4) DefaultMQPushConsumer 的流量控制
不同的业务场景,需要生产者采用不同的写入策略:同步发送,异步发送,延迟发送,发送事务消息。
ZooKeeper功能强大,包括自动Master选举等。RocketMQ的架构不需要ZooKeeper复杂的功能,只要一个轻量级的元数据服务器即可。而且中间节RockMQ对稳定性要求高,NameServer代码少,好维护,少依赖一个另外的中间件,减少维护成本。
(1) 消息真正的物理存储文件是CommitLog, ConsumerQueue是消息的逻辑队列,类似数据库的索引文件,存储的是指向物理存储的地址。
(2)每个Topic下的每个Message Queue都有一个对应的ConsumeQueue。
(3)一条ConsumeQueue的格式: (CommitLog Offerset)8byte + (Size)4byte + (Tag HashCode)8byte
(4) CommitLog顺序写,可以大大提高写的速度
(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任然可用。
(1)异步刷盘:在返回写成功状态时,消息可能只是被写入了内存的PAGECACHE,这样写操作的返回快,吞吐量大。当内存里的消息累计到一定程度,统一触发写磁盘操作,快速写入。
(2)同步刷盘:在返回写成功状态时,消息已经被写入磁盘。具体流程是,消息写入内存的PAGECACHE后,立即通知刷盘线程刷盘,等刷盘完成后,返回消息写成功的状态。
(3)刷盘方式通过Broker配置文件中的flushDiskType参数设置。
如果一个Broker组有Master 和 Slave, 消息需要从 Master 复制到 Slave 上, 有同步和异步两种复制方式。
(1)同步复制:等 Master 和 Slave 均写成功后才反馈客户端写成功状态。
(2)异步复制:只要 Master 写成功即可返回给客户端写成功状态。
(3)复制方式通过Broker配置文件中的brokerRole参数进行配置:ASYNC_MASTER, SYNC_MASTER, SLAVE三个参数。
(4)最佳实践: 刷盘方式(ASYNC_FLUSH) + 复制方式(SYNC_MASTER)。
参见我的《RocketMQ实现顺序消息》一文。
(1)消息重复一般不会发生,但是如果消息量大,网络有波动,消息重复就是大概率时间。
(2)解决消息重复的方法:第一种是保证消费逻辑的幂等性,就是多次调用和一次调用的结果是一样的。另一种是维护一个已消费的记录,消费前查询这个消息是否被消费过。
由于业务的需求或硬件的故障,经常需要增加或减少各个角色的机器,要做到在不影响服务稳定性的情况下动态增减机器。
通过HTTP服务来设置,程序回向一个HTTP 地址发送请求来获取 NameServer 地址。其他组件会每隔2分钟请求一次这个URL,获取最新的NameServer 地址。这种方式,无需重启其他组件。
集群扩容后,一种方式是:把新建的 Topic 指定到新的 Broker 机器上, 另一种方式是通过updateTopic 命令更改现有的Topic 配置,在新加的 Broker 上创建新的对列。
(1)RocketMQ 是个先进先出的对列,不支持消息级别或者 Topic 级别的优先级。业务中简单的优先级需求,可以通过间接的方式解决。
(2) 如果当前 Topic 中有很多相似类型的消息,不如AA, AB,AC,当AB, AC 的消息量很大,但是处理速度比较慢的时候,队列中会有很多AB, AC 类型的消息, AA 的消息就会等待很久才会被处理。解决方法是,可以把 AA 类型放在一个单独的 Topic 中, AB, AC 放在另外一个 Topic 中。
Broker 段有三种方式进行消息过滤。
(1)一个应用尽量只用一个 Topic, 不同的消息子类型用 Tag 来标识。发送消息设置了 Tag 以后, 消费方在订阅消息时,才能利用 Tag 在 Broker 段做消息过滤。
(2)一个 Message 只能有一个 Tag。Broker 在 ConsumeQueue 中做这种过滤,只从 CommitLog 里读取过滤后被命中的消息。ConsumeQueue 的存储格式 CommitLogOffeset + Size + MessageTagHash。
(1)在构造 Message 的时候, 通过 putUserProperty 函数来增加多个自定义的属性。
(2) SQL 表达式的过滤需要Broker 先读出消息里内容,然后做 SQL 计算, 增大磁盘压力,没有 Tag 方式高效。
(1)在 Broker 配置FilterServer。这种方式会占用很多Broker 机器的CPU,要慎用。
三种方式提高Consumer 的处理能力:
(1) 先查看消费逻辑本省有没有优化的空间。
(2)提高消费并行度。在同一个ConsumerGroup 下(Clustering),可以通过增加Consumer 实例的数量来提高并行度(增加机器,或者在已有机器中增加多个Consumer 进程)。但注意:总的Consumer数量不要超过Read Queue数量。超过d的Consumer 实例接受不到消息。或者,提高单个Consumer 实例的并行处理线程数。(设置方法是修改conusmeThreadMin 和 consumeThreadMax)。
(3)以批量的方式进行消费。设置consumeMessageBatchMaxSize 这个参数。
(1)见我的博客《RocketMQ Consumer 负载均衡算法学习 xxx》
(2)负载均衡的结果与 Topic 的 MessageQueue数量,以及 ConsumerGroup 里的 Consumer 的数量有关。
(3)通常情况下把一个 Topic 的 Message Queue 数设置为16。
(1)在一些对速度要求高,但可靠性要求不高的场景下,比如日志搜集应用,可以采用 Onway 方式发送。OneWay 方式只发送请求不等待响应,即将数据写入客户端的 Socket 缓冲区就返回,不等待返回结果。这样耗时可以降到微妙。
(1)把 NameServer 的地址, GroupName 名称和 Topic 名称 写到 application.properties 配置文件中。
(2)把 Producer 和 Consumer 的初始化操作代码写到 @PostConstruct 和 @PreDestory 函数中。把发送消息和消费消息的功能封装成 Service,供其他代码使用。
(1)中小型企业公司,直接使用云产品,可以省去部署,运维的繁琐工作,加快自身核心产品的上线速度。
(1)ConsumeMessageConcurrentlyService 定义了三个线程池。一个是主线程用来执行收到的消息。另外两个都是单线程的线程池,一个是用来执行推迟消费的消息,另外一个用来定期清理超时消息(15分钟)。
(2)ProcessQueue 对象 。每个 Message Queue 都会有一个对应的 ProcessQueue 都会有一个对应的 ProcessQueue 对象,保存这个 Message Queue 消息处理状态的快照,比如:当前消息堆积的数量。ProcessQueue 对象主要是一个 TreeMap 和 一个读写锁。MessageQueue 中的 offset 是 key, 消息内容是 value。
(1)MQClientInstance 类是各种 Consumer 和 Producer 的底层类。
(2)普通情况下,一个 RockeMQ 客户端的 Java 程序, 或者说一个JVM进程只要有一个 MQClientInstance。也就是说,一个或多个 Consumer 或者 Producer,底层使用的是一个 MQClientInstance。
(3)有些情况下一个MQClientInstance 对象是不够的,比如一个Java 程序需要连接两个 RocketMQ集群,从一个集群读取消息,发送到另外一个集群。这种情况下一定要手动指定不同的InstanceName,底层会创建两个 MQClientInstance 对象。