介绍功能
1、应用解耦
复杂的应用里会存在多个子系统,如果各个子系统之间的耦合性太高,整体系统的可用性就会大幅降低。比如一个电商系统,任何一个子系统出了故障或者因为升级等原因暂时不可用,都会造成下单操作异常,影响用户使用体验。
当转变成基于消息队列的方式后,系统可用性就高多了,比如物流系统因为发生故障,需要几分钟的时间来修复,在这几分钟的时间里,物流系统要处理的内容被缓存在消息队列里,用户的下单操作可以正常完成。当物流系统恢复后,补充处理存储在消息队列里的订单信息即可,终端用户感知不到物流系统发生过几分钟的故障。
2、流量消峰
通过缓冲机制,承受住短时大流量的冲击。通过利用消息队列,把大量的请求暂存起来,分散到相对长的一段时间内处理,能大大提高系统的稳定性和用户体验。
3、 消息分发
在大数据时代,数据对很多公司来说就像金矿,公司需要依赖对数据的分析,进行用户画像、精准推送、流程优化等各种操作,并且对处理的实时性要求越来越高。数据是不断产生的,各个分析团队、算法团队都要依赖这些数据来进行工作,这个时候有个可持久化的消息队列就非常重要。数据的产生方只需要把各自的数据写入一个消息队列即可,数据使用方根据各自需求订阅感兴趣的数据,不同数据团队所订阅的数据可以重复也可以不重复,互不干扰,也不必和数据产生方关联。
组成及功能:
RocketMQ由四部分组成,先来直观地了解一下这些角色以及各自的功能。https://www.jianshu.com/p/2838890f3284(推荐)
分布式消息队列是用来高效地传输消息的,它的功能和现实生活中的邮局收发信件很类似,我们类比地说一下相应的模块。现实生活中的邮政系统要正常运行,离不开下面这四个角色,一是发信者,二是收信者,三是负责暂存、传输的邮局,四是负责协调各个地方邮局的管理机构。对应到RocketMQ中,这四个角色就是Producer、Consumer、Broker和NameServer。
发信者 =====> Producer 收信者 =====> Consumer
负责暂存 =====> Broker 传输的邮局 ====> NameServer
大概就是下面这个样子(这个图很迷,望指点):
解释一下:生产者(集群)根据topic发消息到broker,消费者从broker接收消息。
启动 RocketMQ 的顺序是先启动 NameServer,再启动 Broker,这时候消 息队列已 经可以提供服务了,想发送消息就使用 Producer来发送,想接收消息 就使用 Consumer来接收 。 很多应用程序既要发送,又要接收,可以启动多个Producer 和 Consumer 来发送多种消息,同时接收多种消息 。
NameServer 维护这些配置信息 、 状态信 息,其他角色都通过 NameServer 来协同执行
NameServer是整个消息队列中 的状态服务器,集群的各个组件通过它来了 解全局的信息 。 同时,各个角色的机器都要定期 向 NameServer上报自己的状态,超时不上报的话,NameServer 会认为 某个机器出故障不可用了,其他的组 件会把这个机器从可用列表里移除 。
NameServer可以部署多个,相互之间独立,其他角色同时向多个 NameServer 机器上报状态信息,从而达到热备份的目的。 NameServer本身是无状态的,也就 是说 NameServer 中的 Broker、 Topic 等状态信息不会持久存储,都是由各个角色定时上报并存储到内存中的。
集群状态的存储结构及位置 NameServer 的主要工作就是维 护这五个变量中存储的信息
private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;
说明:topicQueueTable 这个结构的 Key 是 Topic 的名称,它存储了所有 Topic 的属性信息 。
Value 是个 QueueData 队列 , 队里的长度 等于这 个 Topic 数据存储的 MasterBroker的个数,
QueueData里存储着 Broker的名称、 读写queue的数量、 同步标识等。
private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
说明:以 BrokerName 为 索 引 ,相 同 名 称的 Broker 可能存在多台机器, 一个 Master
和多个 Slave。 这个结构存储着一个 BrokerName 对应的属性信 息,包括所属的 Cluster 名称,
一 个 Master Broker 和多个 Slave Broker 的地址信息 。
private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
说明:存储的是集群中 Cluster 的信息,结果很简单,就是一个 Cluster 名称对 应一个由 BrokerName组成的集合。
private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
说明:这个结构和 BrokerAddrTable有关系,但是内容完全不同,这个结构的 Key 是 BrokerAddr,也就是对应着一台 机器,
BrokerAddrTable 中的 Key 是BrokerName, 多个机器的BrokerName可以相同。 BrokerLiveTable 存储的内容是这台
Broker机器的实时状态,包括上次更新状态的时间 戳, NameServer会定期检查这个时间戳,超时没有更新就认为这个 Broker无效了,
将其从 Broker列表里清除。
private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
说明:Filter Server是过滤服务器,是 RocketMQ 的一种服务端过滤方式,一 个 Broker 可以有 一个 或 多个 Filter Server。
这个结构的 Key 是 Broker 的地址, Value 是和这个 Broker关联的多个 Filter Server 的地址 。
状态维护逻辑
因为其他角色会主动向 NameServer上报状态,所以 NameServer 的主 要逻 辑在 DefaultRequest Processor类中,根据上报消息里的请求码做相 应 的处理, 更新存 储的对应 信息 。 此外,连接断开的 事 件也 会 触发状态 更新。
当NameServer和Broker的长连接断掉以后,NameServer会把这个 Broker的信息清理出去。
NameServer还有定时检查时间戳的逻辑, Broker向 NameServer发送的心 跳会更新时间戳, 当 NameServer检查到时间戳长时间没有更新后,便会触发 清理逻辑(10秒检查一次,时间戳超过 2分钟则认为 Broker已 失效。)。
了解了四种角色以后,再介绍一下Topic和Message Queue。一个分布式消息队列中间件部署好以后,可以给很多个业务提供服务,同一个业务也有不同类型的消息要投递,这些不同类型的消息以不同的Topic名称来区分。所以发送和接收消息前,先创建Topic,针对某个Topic发送和接收消息。有了Topic以后,还需要解决性能问题。如果一个Topic要发送和接收的数据量非常大,需要能支持增加并行处理的机器来提高处理速度,这时候一个Topic可以根据需求设置一个或多个Message Queue, Message Queue类似分区或Partition。Topic有了多个Message Queue后,消息可以并行地向各个Message Queue发送,消费者也可以并行地从多个Message Queue读取消息并消费。
1、生产者和消费都是通过topic找到具体某大类消息类型进行消息的投递和消费,topic是rocketmq中一大类消息传递的桥梁
2、一个topic可以创建在一个broker上,也可以创建在多个broker上。如果消息创建在一个broker上那么消息会全部发送到指定的 broker中,一旦broker发生故障,那么当前topic对应的生产者和消费者是无法进行消息的投递和消费的,因此rocketmq提供了HA模式即高可用master-slave。
对当前brokerName进行下利用这主从配置,防止单点故障.一般数据量的情况种配置完全可以满足生产环境rocketmq的使用。但是消息数量达到一定程度的时候势必会对当前broker造成很大的压力,因此引出了另一种配置方式,一个topic配置在多个broker下面,多个broker共同完成消息的消费,分散broker的压力.
3、创建一个topic的时候会首先指定broker,然后指定broker下的读队列数量和写队列数量,还有当前topic的权限(2||4||6 ,2是写权限,4是读权限,6是读写权限)。我们可以理解为producer发送消息到设置好的MessageQueue中,单台broker的情况下MessageQueue是负载均衡的,一个topic的消息和负载的投递到不同的MessageQueue中,我们也可以在producer中指定发送到具体某个MessageQueue中。多个broker且集群模式下,MessageQueue是不共享的(当前broler只处理自己的MessageQueue),
而且从broker只负责读操作,并不负责写操作.只有主broker挂掉的时候从节点才会进行写操作.
4、ProcessQueue是MessageQueue的快照队列,在PushConsumer模式运行的情况下,每个MessageqQueue都会对应的创建一个ProcessQueue用具记录MessageQueue消息处理的快照。ProcessQueue对象中主要结构是一个TreeMap和一个读写锁,TreeMap中以MessageQueue的offset作为key,以消息内容的引用作为value,保存所有从MessageQueue中获取到但还没有被消费掉的消息.其中读写所控制着多个线程对于treeMap的访问.
下载安装:http://rocketmq.apache.org/dowloading/releases/
注意:一定要使用1.8+版本,1.7版本不能识别rocketMQ中的metaspace特性,该特性由1.8最新提出
提示:rocketmq下载zip压缩包即可,binary是无需配置maven的。pom.xml
spring简单应用:
pom.xml配置
<!-- https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-client -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>${rocketmq.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-common -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-common</artifactId>
<version>${rocketmq.version}</version>
</dependency>
mq配置:
@Configuration
public class RocketmqConfiguration {
@Value("${apache.rocketmq.namesrvAddr}")
private String nameServer;
@Value("${apache.rocketmq.producer.producerGroup}")
private String produceGroupName;
@Bean
public DefaultMQProducer defaultMQProducer() throws MQClientException {
DefaultMQProducer defaultMQProducer = new DefaultMQProducer(produceGroupName);
defaultMQProducer.setNamesrvAddr(nameServer);
defaultMQProducer.setVipChannelEnabled(false);
defaultMQProducer.setInstanceName("test-producer");
defaultMQProducer.setRetryTimesWhenSendAsyncFailed(10);
defaultMQProducer.start();
return defaultMQProducer;
}
}
消息生产者启动:
@Configuration
@SpringBootApplication
public class DataSyncApplication implements CommandLineRunner {
@Autowired
private DefaultMQProducer defaultMQProducer;
public static void main(String[] args) {
new SpringApplicationBuilder(DataSyncApplication.class).web(false).run(args);
}
@Override
public void run(String... args) throws Exception {
Message message = new Message("TopicTest","push","Hello World!!!!!!!".getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult result = defaultMQProducer.send(message);
}
}
消息消费者:
@Component
public class RocketmqConsumer {
@Value("${apache.rocketmq.consumer.PushConsumer}")
private String consumerGroupName;
@Value("${apache.rocketmq.namesrvAddr}")
private String nameServer;
@PostConstruct
public void consumer(){
//消费者的组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(consumerGroupName);
//指定NameServer地址,多个地址以 ; 隔开
consumer.setNamesrvAddr(nameServer);
consumer.setVipChannelEnabled(false);
try {
//订阅PushTopic下Tag为push的消息
consumer.subscribe("TopicTest", "push");
//设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
//如果非第一次启动,那么按照上次消费的位置继续消费
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
consumer.registerMessageListener((MessageListenerConcurrently) (list, context) -> {
try {
for (MessageExt messageExt : list) {
System.out.println("messageExt: " + messageExt);//输出消息内容
String messageBody = new String(messageExt.getBody(), RemotingHelper.DEFAULT_CHARSET);
System.out.println("消费响应:msgId : " + messageExt.getMsgId() + ", msgBody : " + messageBody);//输出消息内容
}
} catch (Exception e) {
e.printStackTrace();
return ConsumeConcurrentlyStatus.RECONSUME_LATER; //稍后再试
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; //消费成功
});
consumer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
介绍一本书《RocketMQ实战与原理解析》-杨开元