本篇博客主要参考:

《浅入浅出》-RocketMQ 敖丙

APACHE-RocketMQ Gitee RocketMQ官方文档

RocketMQ 实战与进阶 GitChat

又是好久没有写博客了,虽然可以找出无数个没有写的博客的理由,但是说到底,还是一个字“懒”。今天我终于吃了一颗治疗懒癌的药丸,决定写一篇博客。介绍什么好呢,思来想去,还是介绍下RocketMQ吧,毕竟写了30多篇博客,还没有好好写过关于MQ的博客呢。本篇博客比较基础,不涉及到源码分析,只是扫盲。

MQ有什么用

解耦

我觉得从某种角度来说,微服务促进了MQ的蓬勃发展,本来一个系统有N多个模块,所有模块都强耦合在一起,现在微服务了,一个模块就是一个系统,系统之间肯定需要交互,交互有三种常见的方法,一种是RPC,一种是HTTP,一种就是MQ了。

异步

原本一个业务分为N步,要一步一步处理,才能把最终的结果返回给用户,现在有了MQ,先把最关键的部分处理完毕,然后发送消息到MQ,直接返回给用户OK,至于后面的步骤在后台慢慢处理吧,真乃提升用户体验的神器。

削峰

某个接口的请求量突然飙升,势必会对应用服务器、数据库服务器造成很大的压力,现在有了MQ,来多少请求都不在怕的,后台慢慢处理呗。

RocketMQ简介

RocketMQ是用Java编写的,是阿里开源的消息中间件,吸收了Kafka很多优点。Kafka也是比较热门的消息中间件,不过Kafka是用Scala编写的,不利于Java程序员去阅读源码,也不利于Java程序员做一些定制化的开发。接触过Kafka的小伙伴都知道,要用好Kafka实属不易,相对来说,RocketMQ简单多了,而且RocketMQ有阿里加持,经历了N次双11的考验,比较适合国内互联网公司,所以国内使用RocketMQ的公司很多。

RocketMQ四大组件



图片来自https://gitee.com/mirrors/rocketmq/blob/master/docs/cn/architecture.md

可以看到RocketMQ主要有四个组件:

NameServer

  • 无状态服务,注册中心,可集群部署,但是NameServer节点之间没有任何数据交互。
  • Borker会以定时把Topic路由信息上报给所有的NameServer。Producer、Consumer会随机选择一个NameServer定时Topic更新路由信息。
  • Topic路由信息在NameServer集群中采用最终一致性。
  • 保证AP。

Borker

  • RocketMQ的服务端,用于存储消息、分发消息。
  • Borker会定时把自身拥有的所有的Topic路由信息上报给NameServer。
  • Borker有两个角色:Master、Follower,Master承担读(消费消息)写(生产消息)操作,如果Master比较忙,或者不可用,Follower可以承担读操作。BorkerId=0,代表是Matser,BorkerId!=0,代表是Follower,需要注意的有两点:

    其一,目前为止,BorkerId=1的Follower才可以承担读操作;

    其二,只有较高版本的RocketMQ才支持当Master节点挂掉,Follower自动升级到Master。

Producer

生产者,每隔一定时间向NameServer发起Topic的路由信息查询。

Consumer

消费者,每隔一定时间向NameServer发起Topic的路由信息查询。

为什么注册中心不选用Zookeeper

其实,在低版本的RocketMQ中,确实是选用Zookeeper作为注册中心的,但是后面改成了现在的NameServer,猜想主要原因是:

  • RocketMQ已经是一个中间件了,不想再依赖其他中间件。
  • Zookeeper比较重,有很多功能RocketMQ是用不到的,不如写一个轻量级的注册中心。
  • Zookeeper是CP,一旦触发领导选举,那么注册中心就不可用了,而RocketMQ的注册中心,不需要强一致性,只要保证最终一致性。

RocketMQ消息领域模型

Message

  • 传输的消息。
  • 消息必须有Topic。
  • 消息可以有多个Tag和多个Key,可以看做消息的附加属性。

Topic

  • 一类消息的集合。
  • 每个消息必须有一个Topic。
  • 消息的第一级类型。

Tag

  • 一个消息除了有Topic之外,还可以有Tag,用来细分同一个Topic下的不同种类的消息。
  • Tag不是必须的。
  • 消息的第二级类型。

Group

分为ProducerGroup,ConsumerGroup,我们更多的是关注ConsumerGroup,ConsumerGroup包含多个Consumer。

在集群消费模式下,一个ConsumerGroup下的Consumer共同消费一个Topic,且每个Consumer会被分配到N个队列,但是一个队列只会被一个Consumer消费,不同的ConsumerGroup可以消费同一个Topic,一条消息会被订阅此Topic的所有ConsumerGroup消费。

Queue

  • 一个Topic默认包含四个Queue。
  • 在集群消费模式下,同一个ConsumerGroup中的Consumer可以消费多个Queue的消息,但是一个Queue只能被一个Consumer消费。
  • Queue中的消息是有序的。
  • 分为读Queue和写Queue,一般来说,读Queue的数量和写Queue的数量是一致的,否则很容易出问题。

消费模式

消费模式有两种:Clustering(集群消费)和Broadcasting(广播消费)。

和其他MQ不同,其他MQ是在发送消息的时候,指定是集群消费还是广播消费,RocketMQ是在消费者端设置是集群消费还是广播消费。

Clustering(集群消费)

默认情况下是集群消费模式,该模式下,ConsumerGroup所有的Consumer共同消费一个Topic的消息,每个Consumer负责消费N个队列的消息(N也可能为1,甚至是0,没有分配到队列),但是一个队列只会被一个Consumer消费。如果某个Consumer挂掉,ConsumerGroup下的其他Consumer会接替挂掉的Consumer继续消费。

集群消费模式下,消费进度维护在Borker端,存储路径为${ROCKET_HOME}/store/config/ consumerOffset.json,如下图所示:



使用topicName@consumerGroupName为Key,消费进度为Value,Value的形式是queueId:offset ,说明如果有多个ConsumerGroup,每个ConsumerGroup的消费进度是不同的,需要分开来存储。

Broadcasting(广播消费)

广播消费消息会发给ConsumerGroup中所有的Consumer。

广播消费模式下,消费进度维护在Consumer端。

消费队列负载算法与重平衡机制

消费队列负载算法

我们知道了在集群消费模式下,ConsumerGroup下所有的Consumer共同消费一个Topic的消息,每个Consumer负责消费N个队列的消息,那么具体是如何分配的呢?这就涉及到消费队列负载算法了。

RocketMQ提供了众多的消费队列负载算法,其中最常用的是两种算法,即AllocateMessageQueueAveragely、AllocateMessageQueueAveragelyByCircle。下面我们来看下这两个算法的区别。

假设,现在一个Topic有16个队列,用q0~q15表示,有3个Consumer,用c0-c2表示。

用AllocateMessageQueueAveragely消费队列负载算法的结果如下:

  • c0:q0 q1 q2 q3 q4 q5
  • c1:q6 q7 q8 q9 q10
  • c2:q11 q12 q13 q14 q15

用AllocateMessageQueueAveragelyByCircle消费队列负载算法的结果如下:

  • c0:q0 q3 q6 q9 q12 q15
  • c1:q1 q4 q7 q10 q13
  • c2:q2 q5 q8 q11 q14

ConsumerGroup下所有的Consumer共同消费一个Topic的消息,每个Consumer负责消费N个队列的消息,但是一个队列不能同时被N个Consumer消费,这意味着什么?

聪明的你一定可以想到,如果一个Topic只有4个队列,而有5个Consumer,那么有一个Consumer将不能分配到任何队列,所以在RocketMQ中,Topic下队列的个数直接决定了Consumer的最大个数,也就说明,不能光靠增加Consumer来提高消费速度。

重平衡

虽然建议在创建Topic的时候,就应该充分考虑队列的个数,但是实际情况往往是不尽人意的,哪怕队列数没有发生改变,Consumer的数量也一定会发生改变,比如Consumer的上下线,比如某个Consumer挂了,比如新增了Consumer。队列的扩容、缩容,Consumer的扩容、缩容都会导致重平衡,也就是为Consumer重新分配消费的队列。

在RocketMQ中,Consumer会定时查询Topic的队列的个数,Consumer的个数,如果发生了改变,就会触发重平衡。

重平衡是RocketMQ内部实现的,程序员无需关心。

Pull OR Push?

一般来说,MQ有两种方法获取消息:

  • Pull:Consumer主动拉取消息,好处是Consumer可以控制拉取消息的频率,条数,Consumer知道自身的消费能力,所以在Consumer端不容易造成消息堆积,但是实时性不是太好,效率相对较低。
  • Push:Broker主动发送消息,好处是实时性、效率比较高,但是Broker无法知道Consumer端的消费能力,如果发给Consumer的消息过多,会造成Consumer端的消息堆积;如果发给Consumer的数据太少,又会造成Consumer端的空闲。

不管是Pull,还是Push,Consumer总会与Broker产生交互,交互的方式一般有短连接、长连接、轮询三种方式。

看起来,RocketMQ支持既支持Pull,也支持Push,但是实际上Push也是用Pull实现的,那么Consumer是怎么与Broker产生交互的呢?

这就是RocketMQ设计的巧妙的地方了,既不是短连接,也不是长连接,也不是轮询,而是采用的长轮询。

长轮询

Consumer发起拉取消息的请求,分为两种情况:

  • 有消息:Consumer拿到消息后,连接断开。
  • 没有消息:Borker Hold(保持)住连接一定时间,每隔5秒,检查下是否有消息,如果有消息,给Consumer,连接断开。

事务消息

RocketMQ支持事务消息,Producer把事务消息发送给Broker后,Broker会把消息存储在系统Topic:RMQ_SYS_TRANS_HALF_TOPIC,这样Consumer就无法消费到这条消息了。

Broker会有一个定时任务,消费RMQ_SYS_TRANS_HALF_TOPIC的消息,向Producer发起回查,回查的状态有三种:提交、回滚、未知。

  • 如果回查的状态是提交,回滚,会触发消息的提交和回滚;
  • 如果是未知,会等待下一次回查,RocketMQ可以设置一条消息的回查间隔与回查次数,超过一定的回查次数,消息会自动回滚。

延迟消息

延迟消息是指息发到Broker后,不能立刻被Consumer消费,需要等待一定的时间才可以被消费到,RocketMQ只支持特定的延迟时间:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

消费形式

RocketMQ支持两种消费形式:并发消费、顺序消费。

如果是顺序消费,需要保证排序的消息在同一个队列。如何选择队列发送呢,RocketMQ发送消息的方法有好几个重载,其中有一个重载方法支持队列的选择。

同步刷盘、异步刷盘

Producer把消息发送到Borker中,Borker是需要把消息持久化的,RocketMQ支持两种持久化策略:

  • 同步刷盘:Borker把消息持久后才返回ACK给Producer,好处是消息可靠性高,但是效率较慢。
  • 异步刷盘:Broker把消息写入到PageCache中,就返回ACK给Producer。好处是效率极高,但是如果服务器挂了,消息可能会丢失,如果只是RocketMQ服务挂了,不会造成消息丢失。

同步复制、异步复制

为了MQ的可靠性、可用性,在生产环境,一般会部署Follower节点,Follower节点会复制Master的数据,RocketMQ支持两种持复制策略:

  • 同步复制:Master、Follower都把消息写入成功,才返回ACK给Producer,可靠性较高,但是效率较慢。
  • 异步复制:只要Master写入成功,就返回ACK给Producer,效率较高,但是可能会丢失消息。

"写入"是写入PageCache,还是写入硬盘,要看Follower Broker的配置。

再谈谈Producer

RocketMQ提供了三种发送消息的方法:

  • oneway:fire and forget,单向消息,指消息发送出去后,就不管了,这个方法是没有返回值的。
  • 同步:消息发送出去后,同步等待Borker的响应。
  • 异步:消息发送出去后,立即返回,收到Boker的响应后,会执行函调方法。

在实际开发中,一般选用同步方法,如果要提高RocketMQ的性能,一般都是修改Borker端的参数,特别是刷盘策略和复制策略。

发送消息重试

消息发送时,如果使用了MessageQueueSelector,那消息发送的重试机制将会失效。

发送消息响应可能为以下四种:

public enum SendStatus {
SEND_OK,
FLUSH_DISK_TIMEOUT,
FLUSH_SLAVE_TIMEOUT,
SLAVE_NOT_AVAILABLE,
}

除了第一种,其他情况都是有问题的,为了保证消息不丢失,需要设置Producer参数:RetryAnotherBrokerWhenNotStoreOK为true。

故障规避机制

如果消息发送失败了,重试的时候,还是发送给这个Borker,那么大概率发送还是失败的,RockteMQ设计精巧之处在于,重试的时候,会自动避开这个Borker,而选择其他Borker,但是目前为止,异步发送没有那么智能,只会在一个Borker上重试,所以强烈建议选择同步发送方式。

RocketMQ提供了两种故障规避机制。用参数SendLatencyFaultEnable来控制。

  • false:默认值,只有在重试的时候,才会启用故障规避机制,比如发送消息给BorkerA失败了,重试的时候,会选择BorkerB,但是下次发送消息,还是会选择发送给BorkerA。
  • true:开启延迟退避机制,一旦消息发送给BorkerA失败,就会悲观的认为在一段时间内,BorkerA不可用,在将来的一段时间内,不会再向BorkerA发送消息。

延迟退避机制看起来很好用,但是一般来说Borker端繁忙,导致Borker不可用或者网络不可用只是一瞬间的事情,马上就可以恢复,如果开启了延迟退避机制,本来可用的Borker在一段时间内却被规避了,其他Borker更加繁忙,那可能情况更糟糕。

再谈谈Consumer

Consumer线程注意事项

Consumer有两个参数,可以消费的并行度,即ConsumeThreadMinConsumeThreadMax,看起来给人的感觉是,如果Consumer端堆积消息比较少,消费线程数为ConsumeThreadMin;如果Consumer端堆积消息比较多,就自动开启新的线程来消费,直到消费线程数为ConsumeThreadMax。但是并不是这样,Consumer内部持有一个线程池,选用的是无界队列,也就是ConsumeThreadMax参数是无效的,所以在实际开发中,ConsumeThreadMinConsumeThreadMax往往设置成一样。

ConsumeFromWhere

如果查询不到消费进度的时候,Consumer从哪里开始消费,RocketMQ支持从最新消息、最早消息、指定时间戳这三种方式进行消费。

消费消息重试

RocketMQ会为每个ConsumerGroup都设置一个Topic名称为%RETRY%+consumerGroup的重试队列,用来保存需要给ConsumerGroup重试的消息,但是重试需要一定的延时时间,RocketMQ对于重试消息的处理是先保存至Topic名称为SCHEDULE_TOPIC_XXXX的延迟队列中,后台定时任务按照对应的时间进行Delay后重新保存至%RETRY%+consumerGroup的重试队列中。

消息堆积、消费能力不够,怎么办

  • 提高消费进度,这是最好的办法。
  • 增加队列,增加Consumer。
  • 原先的Consumer作为搬砖工,根据一定的规则把消息“搬”到多个新的Topic,再开几个ConsumerGroup去消费不同的Topic。
  • 新开一个ConsumerGroup去消费,也就是两个ConsumerGroup同时消费一个Topic,但是需要注意offset的判断,比如一个ConsumerGroup消费offset为奇数的消息,一个ConsumerGroup消费offset为偶数的消息。

本来以为写扫盲文,应该会写的很顺,但是还是想多了,因为是扫盲文,面向的是没有怎么接触过RocketMQ的小伙伴,但是RocketMQ有没有那么简单,不可能用一篇博客,就让没有怎么接触过RocketMQ的小伙伴顺利入门,所以在写博客的时候,一直在想,这个东西重要吗,需要仔细描述吗;这个东西可以忽视,可以不介绍吗 等等,大家可以看到本文基本都是在介绍各种概念,几乎没有涉及到API的层面,因为一旦涉及到API,那么估计写两个星期也写不完。

End

RocketMQ扫盲篇的更多相关文章

  1. 转摘 MySQL扫盲篇

    一下文章摘自:http://www.jellythink.com/archives/636 MySQL扫盲篇 2014-09-15 分类:MySQL / 数据库 阅读(1412) 评论(1)  为什么 ...

  2. 分布式协调服务Zookeeper扫盲篇

    分布式协调服务Zookeeper扫盲篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 身为运维工程师对kubernetes(k8s)可能比较熟,那么etcd(go语言实现)分布式协 ...

  3. HTTP/2协议–特性扫盲篇

    HTTP/2协议–特性扫盲篇 随着web技术的飞速发展,1999年制定的HTTP 1.1已经无法满足大家对性能的要求,Google推出协议SPDY,旨在解决HTTP 1.1中广为人知的性能问题.SPD ...

  4. 高级Linux运维工程师必备技能(扫盲篇)

    高级Linux运维工程师必备技能(扫盲篇) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在了解文件系统之前,我们要学习一下磁盘存储数据的方式,大家都知道文件从内存若要持久化存储的 ...

  5. C语言扫盲篇

    C语言扫盲篇 作者:尹正杰 版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接. 一.什么是C语言 C 语言是一种通用的高级语言,最初是由 ...

  6. 01--Qt扫盲篇

    Qt扫盲篇 1.What is Qt 一个跨平台应用程序和UI开发框架,主要偏向于UI框架方面,由诺基亚公司开发维护. 使用 Qt 只需一次性开发应用程序,无须重新编写源代码,便可跨不同桌面和嵌入式操 ...

  7. Httpd服务入门知识-http协议版本,工作机制及http服务器应用扫盲篇

    Httpd服务入门知识-http协议版本,工作机制及http服务器应用扫盲篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Internet与中国 Internet最早来源于美 ...

  8. MySQL数据库扫盲篇

    MySQL数据库扫盲篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任.   一.MySQL概述 1>.什么是MySQL MySQL是瑞典的MySQL AB公司开发的一个可用于各 ...

  9. C#扫盲篇(三):Action和Func委托--实话实说

    一.基础定义 老王想找老张的老婆出去耍,但是一看,老张还在厨房煮饭.于是老王就对老张隔壁的淑芬说:"等下老张吃完饭出去喝茶,你就把前门晒的苞谷收了,老张从左门出,你就收右边的苞谷,我就知道从 ...

随机推荐

  1. python3 for

    当range中只有一个参数时,此参数表示终点,但不包括.(从0开始) 当range中有两个参数时,分别表示起点和终点.(左闭但不包括终点) 当range中有三个参数时,分别表示起点和终点,和步长,意思 ...

  2. SpringCloud实战 | 第一篇:Windows搭建Nacos服务

    前言 为什么放弃eureka选择nacos?本地开发环境需要搭建nacos-server,想着是很简单的事但是被一些文章(少了关键必要的步骤)给带偏了,所以亲测成功后写了这篇文章. 搭建nacos-s ...

  3. 常见重构技巧 - 5种方式去除多余的if else

    常见重构技巧 - 去除多余的if else 最为常见的是代码中使用很多的if/else,或者switch/case:如何重构呢?方法特别多,本文带你学习其中的技巧. 常见重构技巧 - 去除多余的if ...

  4. Maven学习总结:几个常用的maven插件

    我们使用maven做一些日常的工作开发的时候,无非是想利用这个工具带来的一些便利.比如它带来的依赖管理,方便我们打包和部署运行.这里几个常见的插件就是和这些工程中常用的步骤相关. maven-comp ...

  5. dubbo学习(四)配置dubbo 注解方式配置

    provider(生产者) service注解暴露服务 /** * 用户管理实现类 */ @Service //用的dubbo的注解,表明这是一个分布式服务 @Component //注册为sprin ...

  6. 这10道springboot常见面试题你需要了解下

    ​ 1.什么是Spring Boot? 多年来,随着新功能的增加,spring变得越来越复杂.只需访问https://spring.io/projects页面,我们就会看到可以在我们的应用程序中使用的 ...

  7. zico2靶机渗透

    zico2靶机渗透 开放了四个端口,分别是22,80,111以及57781端口. 扫到了目录http://192.168.114.152/dbadmin/ 进入看到php文件,访问,发现一个登录窗口. ...

  8. 学会Git玩转GitHub(第一篇) 入门详解 - 精简归纳

    学会Git玩转GitHub(第一篇) 入门详解 - 精简归纳 JERRY_Z. ~ 2020 / 9 / 25 转载请注明出处!️ 目录 学会Git玩转GitHub(第一篇) 入门详解 - 精简归纳 ...

  9. spark-1-架构设计&基本流程

    Spark运行架构包括: (1)集群资源管理器(Cluster Manager) (2)运行作业任务的工作节点(Worker Node) (3)每个应用的任务控制节点(Driver)和每个工作节点上负 ...

  10. windows10 + docker利用文件映射进行编程开发

    0. 以安装swoole框架"easyswoole"举例,建议使用powershell或者cmder输入命令   1. 首先准备好window10专业版开启Hyper-V,然后下载 ...