一、RocketMQ基本介绍
阿里原来有一个自研的MQ,kafka开源之后,参考kafka用java开发了MetaQ,所以很多设计思想和kafka有很多相似的地方,后面改名字叫RocketMQ,2012年开源。2017年9月成为了Apache的顶级项目。目前RocketMQ有两个版本一个是开源的社区版本,一个商业的云服务版本。RocketMQ在阿里内部应用非常广泛的,而且经历过双十一万亿级别的消息,TPS可以达到几十万。
二、RocketMQ架构
2.1、broker
RocketMQ的服务或者说一个进程,叫做broker,broker的作用就是存储和转发消息。RocketMQ单机能承受10万QPS。
为了提升broker的可用性(单点故障问题),以及提升服务器的性能实现负载均衡,通常都是集群部署。
这个和kafka 或者 redis cluster 一样,RocketMQ集群的每个broker 节点保存总数据的一部分,因此和实现横向扩展。和kafka一样为了提高可靠性防止数据丢失,每个broker 可以有自己的副本。默认情况下读写都发生在master上,不过和kafka不同的是RocketMQ的副本slave也可以参与读负载 slaveReadEnable=true
,但是默认brokerId=1的slave才会参与读负载,
2.2、topic
topic是用于消息按照主题划分,和kafka不同的是topic是一个逻辑的概念,消息不是按照topic划分存储的。
producer 把消息发向指定的topic,consumer订阅这个topic就可以收到对应的消息。
和kafka一样,如果topic不存在会自动创建。
topic跟生产者和消费者都是多对多的关系, 一个生产者可以发送消息到多个topic,一个消费者可以订阅多个topic。
2.2.1、Tag
Tag:消息标签,用于在同一个主题下面区分不同类型的消息
2.3、NameServer
当不同的消息存储在不同的broker上,生产者和消费者对应broker的选择,或者路由选择是一个非常关键的问题。
NameServer作用
所以和分布式调用场景需要一个注册中心一样,在RocketMQ中需要一个角色来管理broker的信息。就像kafka中是用zk 来管理的一样。
RocketMQ是自己实现了一个注册服务,这个就是NameServer,它就是RocketMQ的路由中心,每个NameServer节点都保存着全量的路由信息,为了保证高可用,NameServer自身需要集群部署,有点像Euraka。
简单来说,就是broker会在NameServer上注册自己,生产者和消费者在NameServer中发现broker。
NameServer作为路由中心怎么工作?
每个broker在启动时候 ,都会根据配置遍历NameServer的列表,与每个NameServer建立TCP长链接,然后注册自己的信息,之后每隔30s发送心跳信息(主动注册),如何broker挂掉了,不发送心跳,所以除了主动注册之外,还要定时探活,每个NameServer每隔10s检查一下各个broker的最近一次心跳时间,如果发现某个broker超过120s都没有发送心跳,就认为这个broker挂了,会将其从路由信息里移除。
为什么不用zk?
实际上在早期的版本,服务管理也使用zk,和kafka一样,但是 在3.x去掉了zk的依赖,采用了自己的NameServer。
RocketMQ的架构设计决定只需要一个轻量级的元数据服务器就足够了,只需要保持最终一致性,而不需要zk这样的cp解决方案,不需要在依赖另一个中间件,从而减少整体维护成本。
NameServer如何保持一致?
NameServer之间互相不通信,没有主从之分。
如果NameServer全部挂掉了,也没有关系,客户端肯定要缓存broker的信息,不能完全依赖NameServer。
2.4、producer
生产者发送消息,会定时从NameServer拉取路由信息,然后根据路由信息与指定的broker建立TCP 长链接,从而将消息发到broker上,发送逻辑一致的producer可以组成一个group。
同意支持批量发送,不过list 要自己传进去,写数据只能操作master节点。
2.5、consumer
所有消费者,通过NameServer获取topic路由信息,连接到对应的broker消费信息,消费逻辑一致的consumer可以组成一个group,这里和kafka逻辑一样,消息会在consumer之间负载。
由于master和slave都可以读消息,因此consumer会与master和slave都建立连接。
消费者由两种消费方式:
从消费模型来说,支持push和pull 两种模式
2.6、Message Queue
RocketMQ 支持多master的架构。这个就就是类似kafka的partition分片思想。
不过一个broker,RocketMQ 只有一个存储文件(commitlog),并不是kafka那样按照topic分开存储。那这样数据根据什么分布呢?
RocketMQ 里面设计了Message Queue的逻辑概念(作用和partition 类似)。在创建topic的时候会指定读写队列的数量, writeQueueNums、readQueueNums。
写队列的数量决定有几个Message Queue,读队列的数量 决定有几个线程来消费这些Message Queue(只是用来负载的)。
磁盘上看到的队列数量,是由写队列决定的,而且所有的master节点上的个数是一样的,但是存储的数据不一样。比如;集群中有两个master,topic中有2个写队列、一个读队列,那么两台集群的consumer queue目录会出现2个队列,一共四个队列。也就是总队列数= 写队列*节点数,而且读写队列数量最好一致,否则会出现消费不了的情况。
三、RocketMQ 开发
官方提供了java客户端的API;
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.3.0</version>
</dependency>
生产者代码示例
private static String topic;
private DefaultMQProducer producer;
private RocketMQProducerHelper(String nameServer, String topicName, String groupID) {
try {
if (nameServer != null && topicName != null && groupID != null) {
topic = topicName;
producer = new DefaultMQProducer(groupID);
producer.setRetryTimesWhenSendFailed(4);
producer.setRetryAnotherBrokerWhenNotStoreOK(true);
producer.setNamesrvAddr(nameServer);
producer.start();
LOG.info("producer started...");
} else {
LOG.error("parameter init error");
throw new Exception("parameter init error");
}
} catch (Exception e) {
LOG.error("producer init error...");
throw new RuntimeException(e);
}
}
public SendResult send(String topic, String tag, String key, byte[] data, final MessageQueueSelector selector) {
SendResult sendResult = null;
try {
Message msg;
if (tag == null || tag.length() == 0) {
msg = new Message(topic, data);
} else if (key == null || key.length() == 0) {
msg = new Message(topic, tag, data);
} else {
msg = new Message(topic, tag, key, data);
}
if (selector != null) {
sendResult = producer.send(msg, new MessageQueueSelector() {
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
return selector.select(mqs, msg, arg);
}
}, key);
} else {
sendResult = producer.send(msg);
}
} catch (Exception e) {
e.printStackTrace();
LOG.error("Send message error");
}
return sendResult;
}
代码解读:
消费者代码示例
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
public class Consumer {
public static void main(String[] args) throws MQClientException {
String nameServer = "localhost:9876";
String groupID = "groupid";
String topic = "test";
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupID);
consumer.setNamesrvAddr(nameServer);
consumer.subscribe(topic, "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
for(MessageExt msg : msgs) {
String result = Bytes.toString(msg.getBody());
System.out.println("--------------------msg="+result);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
}
}
代码解读:
**3.1、SpringBoot集成RocketMQ **
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>
也很简单,第一、依赖pom;第二、写配置(yaml、config);第三步、写注解使用
生产者只要注入RocketMQTemplate
就可发送消息,有三种类型:
消费者只要加上@RocketMQMessageListener 注解监听消息
注解属性: