64bit OS, Linux/Unix/Mac is recommended;
64bit JDK 1.8+;
Maven 3.2.x
Git
构建
$ git clone -b develop https://github.com/apache/incubator-rocketmq.git
$ cd incubator-rocketmq
$ mvn -Prelease-all -DskipTests clean install -U
$ cd distribution/target/apache-rocketmq
启动NameServer
$ nohup sh bin/mqnamesrv &
$ tail -f ~/logs/rocketmqlogs/namesrv.log
The Name Server boot success...
启动Broker
$ nohup sh bin/mqbroker -c conf/2m-2s-sync/broker-a.properties -n localhost:9876 &
$ tail -f ~/logs/rocketmqlogs/broker.log
The broker[%s, 172.30.30.233:10911] boot success...
发送和接受消息
在发送/接收消息之前,我们需要告诉客户NameServer的位置。RocketMQ提供了多种方法来实现这一点。为了简单起见,我们使用环境变量NAMESRV_ADDR
# export NAMESRV_ADDR=localhost:9876
# sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
SendResult [sendStatus=SEND_OK, msgId= ...
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer
ConsumeMessageThread_%d Receive New Messages: [MessageExt...
关闭服务器
sh bin/mqshutdown broker
The mqbroker(36695) is running...
Send shutdown request to mqbroker(36695) OK
sh bin/mqshutdown namesrv
The mqnamesrv(36664) is running...
Send shutdown request to mqnamesrv(36664) OK
1.新创建的消费者ID开始消费的消息在哪里?
- 如果主题在三天内发送消息,则消费者从服务器中保存的第一条消息开始消费消息。
- 如果主题三天前发送消息,消费者开始从服务器中的最新消息中消费消息,换句话说,从消息队列的尾部开始。
- 如果这样的消费者重新启动,则它开始消耗来自最后消费位置的消息。
2.消费失败时如何重新发送消息?
群集消费模式消费者业务逻辑代码返回Action.ReconsumerLater,NULL或抛出异常,如果消息未能被使用,则会重试最多16次,之后消息将被删除。
广播消费模式广播消费仍然确保消息至少消耗一次,但不提供重新发送选项。
3.如果有消费失败,如何查询失败的消息?
按时间使用主题查询,您可以在一段时间内查询邮件。
使用主题和消息标识来准确地查询消息。
使用主题和消息密钥准确地查询具有相同消息密钥的消息类。
4.消息是否正确传送一次?
RocketMQ确保所有message至少传送一次。在大多数情况下,消息不会重复。
5.如何添加新的Broker?
启动新的Broker并将其注册到相同的NameServer列表中。
默认情况下,仅自动创建内部系统主题和用户组。如果您希望在新节点上拥有您的业务主题和消费者组,请从现有Broker复制它们。提供Admin tool和命令行来处理此问题。
1.消息保存在服务器上多长时间?
存储的邮件将被保存最多3天,并且不会消耗超过3天的邮件将被删除。
2.消息体的大小限制是多少?
一般256KB。
3.如何设置消费者线程数?
当您启动Consumer时,设置一个ConsumeThreadNums属性,示例如下:
consumer.setConsumeThreadMin(20);
consumer.setConsumeThreadMax(20);
1.如果您启动生产者或消费者失败,错误消息是生产者组或消费者重复?
原因:使用相同的Producer / Consumer Group在同一JVM中启动多个Producer / Consumer实例可能会导致客户端无法启动。
解决方案:确保与一个Producer / Consumer Group对应的JVM只有一个Producer / Consumer实例启动。
2.如果消费者无法在广播模式下启动加载json文件?
原因:Fastjson版本太低,无法让广播消费者加载本地的offsets.json,导致用户启动失败。损坏的fastjson文件也可能导致同样的问题。
解决方案:Fastjson版本必须升级到rocketmq客户端依赖版本,以确保可以加载本地的offsets.json。默认情况下,offsets.json文件位于/home/{user}/.rocketmq_offsets中。或检查fastjson的完整性。
3.Broker崩溃的影响是甚么?
a.主机崩溃
消息不能再发送到该Broker集,但是如果您有另一个Broker集可用,则可以在主题存在的情况下发送消息。消息仍然可以从奴隶消费。
b.一些奴隶崩溃
只要有另一个工作的奴隶,就不会对发送消息产生影响。除了消费者组最好设置为从这个服务器消费以外,消费消息也不会有任何影响。默认情况下,comsumer组从Master消耗。
c.所有奴隶都崩溃了
向主机发送消息不会有任何影响,但如果主机是SYNC_MASTER,则生产者将获得SLAVE_NOT_AVAILABLE,指示该消息未发送到任何从站。消费消息也不会有任何影响,除非消费者组最好设置为从服务器消费。默认情况下,comsumer组从主服务器消耗。
4.生产者抱怨“无主题路线信息”,如何诊断?
当您尝试将消息发送到生产者的路由信息不可用的主题时,会发生这种情况。
确保生产者可以连接到名称服务器,并能够从中获取路由元信息。
确保名称服务器确实包含主题的路由元信息。您可以使用Admin Tool或Web控制台通过topicRoute从名称服务器查询路由元信息。
确保您的Broker正在将心跳发送到您的制作人连接的同一名称服务器。
确保主题的权限为6(rw-)或至少2(-w-)。
如果您找不到此主题,请通过管理工具命令updateTopic或Web控制台在Broker程序中创建此主题。
Broker
Broker是RocketMQ系统的主要组成部分。提供轻量级的TOPIC和QUEUE机制来处理消息存储,它接收来自生产者的消息,存储它们并准备处理消费者的提取请求。它还存储消息相关的元数据,包括消费者组,消费进度偏移和主题/队列信息。
角色分为:ASYNC_MASTER,SYNC_MASTER或SLAVE,如果您不能容忍消息丢失,我们建议您部署SYNC_MASTER并附加SLAVE。如果您对丢失感到确定,但您希望Broker始终可用,则可以使用SLAVE部署ASYNC_MASTER。如果您只想使其变得容易,您可能只需要没有SLAVE的ASYNC_MASTER。
作用:Broker服务器负责消息存储和发送,消息查询,HA保证等。
远程模块处理来自客户端的请求。
客户端管理,管理客户(生产者/消费者)并维护消费者的主题订阅。
存储服务,提供简单的API来存储或查询物理磁盘中的消息。
HA服务提供master broker and slave broker的数据同步功能。
索引服务,通过指定的密钥构建消息的索引,并提供快速的消息查询。
master broker 提供RW访问,而slave broker只接受读访问。
要部署没有单点故障的高可用性RocketMQ集群,应部署一系列Broker集。Broker集包含一个masterId设置为0和几个具有非零brokerID的从站。一组中的所有Broker都具有相同的BrokerName。在严重的情况下,我们应该在一个经纪集中至少有两个Broker。每个主题都在两个或多个Broker中。
NameServer
NameServer作为路由信息提供者。生产商/消费者客户查找主题以找到相应的Broker列表。
使用RocketMQ以三种方式发送消息:可靠的同步,可靠的异步和单向传输。
Broker将心跳数据发送到所有名称服务器。生产者和消费者可以在发送/消费消息时从任何可用的名称服务器查询元数据。
作用:
Broker管理,NameServer接受来自Broker集群的注册,并提供心跳机制来检查Broker是否存活。
路由管理,每个NameServer将保存有关Broker群集的完整路由信息和客户端查询的队列信息
RocketMQ客户端(Producer / Consumer)会从NameServer查询队列路由信息,四中方法:
程序里设置,like
producer.setNamesrvAddr("ip:port").
Java 启动选项, use rocketmq.namesrv.addr.
环境变量, use NAMESRV_ADDR.
HTTP Endpoint.
Producer
生产者将业务应用系统生成的消息发送给经纪人。RocketMQ提供了多种发送模式:同步,异步和单向。
生产组:具有相同作用的生产者分组在一起。如果原始Producer崩溃,则Broker可以联系同一生产者组的不同生产者实体来提交或回滚事务。
警告:考虑到提供的生产者在发送消息方面足够强大,每个生产者组只允许一个实例来避免生产者实例的不必要的初始化。
isWaitStoreMsgOK = true(默认为true)。如果没有,我们将永远得到SEND_OK,如果没有异常抛出。以下是每个状态的说明列表:
FLUSH_DISK_TIMEOUT
FLUSH_SLAVE_TIMEOUT:slave broker 不会在MessageStoreConfig的syncFlushTimeout(默认为5秒)内完成与主机的同步,则将获得此状态
SLAVE_NOT_AVAILABLE:如果broker的角色是SYNC_MASTER(默认为ASYNC_MASTER),但没有slav broker 配置,您将获得此状态。
SEND_OK:SEND_OK并不意味着它是可靠的。为确保不会丢失任何消息,您还应启用SYNC_MASTER或SYNC_FLUSH。
异步发送
默认发送(msg)将阻塞,直到返回响应。所以如果你关心性能,我们建议你使用send(msg,callback),它将以异步方式运行。
性能
如果您希望在一个JVM中有多个生产者进行大数据处理,我们建议:
使用异步发送与几个生产者(3〜5就够了)
setInstanceName为每个生产者
Consumer
PullConsumer,PushConsumer
与之前提到的生产者组相似,消费者将完全相同的角色组合在一起,并命名为消费者组,在消息消费方面实现负载平衡和容错的目标是非常简单的.
警告:消费者组的消费者实例必须具有完全相同的主题订阅。
Topic
主题是生产者提供消息和消费者提取消息的类别。主题与生产者和消费者的关系非常松散。具体来说,主题可能有零个,一个或多个生成器向其发送消息; 相反,生产者可以发送不同主题的消息。在消费者的角度来看,零个,一个或多个消费群体可以订阅主题。同样,消费者组织也可以订阅一个或多个主题,只要该组的实例保持其订阅一致。
Message
消息是要传递的信息。消息必须有一个主题,可以解释为您的邮件地址。消息还可以具有可选标记和额外的键值对。例如,您可以为消息设置业务密钥,并在代理服务器上查找消息以在开发期间诊断问题。
Message Queue
主题分为一个或多个子主题“消息队列”。
tag
标签,换句话说子主题,为用户提供了额外的灵活性。使用标签,与同一业务模块具有不同目的的消息可能具有相同的主题和不同的标签。标签将有助于保持您的代码干净和一致,标签也可以方便RocketMQ提供的查询系统。
Message Model
Clustering
Broadcasting
Message Order
当使用DefaultMQPushConsumer时,您可以决定消费消息Orderly或Concurrently。
消费消息有序地意味着消息的消费与生产者为每个消息队列发送的相同的顺序。如果处理全局顺序是强制性的场景,请确保您使用的主题只有一个消息队列。
警告:如果指定消费有序,则消息消耗的最大并发性是消费者组订阅的消息队列数。
当同时消费消息时,消息消耗的最大并发只受到为每个消费者客户端指定的线程池的限制。
警告:如果指定消费有序,则消息消耗的最大并发性是消费者组订阅的消息队列数。
当建立新的消费者群体时,需要决定是否需要消费经纪人已经存在的历史信息。CONSUME_FROM_LAST_OFFSET将忽略历史消息,并消耗之后产生的任何内容。CONSUME_FROM_FIRST_OFFSET将消耗代理中存在的每个消息。您也可以使用CONSUME_FROM_TIMESTAMP来消费在指定的时间戳之后生成的消息。
复制模式:同步和异步。
同步代理等待,直到提交日志被复制到从站才能确认。相反,异步代理在主服务器处理消息后立即返回。
广播正在向主题的所有订阅者发送消息。如果您希望所有用户收到有关主题的消息,广播是一个不错的选择。
public class BroadcastProducer {
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");
producer.start();
for (int i = 0; i < 100; i++){
Message msg = new Message("TopicTest",
"TagA",
"OrderID188",
"Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult sendResult = producer.send(msg);
System.out.printf("%s%n", sendResult);
}
producer.shutdown();
}
}
public class BroadcastConsumer {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("example_group_name");
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
//set to broadcast mode
consumer.setMessageModel(MessageModel.BROADCASTING);
consumer.subscribe("TopicTest", "TagA || TagC || TagD");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List msgs,
ConsumeConcurrentlyContext context) {
System.out.printf(Thread.currentThread().getName() + " Receive New Messages: " + msgs + "%n");
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.printf("Broadcast Consumer Started.%n");
}
}
预定消息与正常消息不同,因为它们将在以后提供的时间内不会被传送。
1.启动消费者等待传入的订阅消息
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.common.message.MessageExt;
import java.util.List;
public class ScheduledMessageConsumer {
public static void main(String[] args) throws Exception {
// Instantiate message consumer
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ExampleConsumer");
// Subscribe topics
consumer.subscribe("TestTopic", "*");
// Register message listener
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List messages, ConsumeConcurrentlyContext context) {
for (MessageExt message : messages) {
// Print approximate delay time period
System.out.println("Receive message[msgId=" + message.getMsgId() + "] "
+ (System.currentTimeMillis() - message.getStoreTimestamp()) + "ms later");
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// Launch consumer
consumer.start();
}
}
2.发送预定的消息
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.common.message.Message;
public class ScheduledMessageProducer {
public static void main(String[] args) throws Exception {
// Instantiate a producer to send scheduled messages
DefaultMQProducer producer = new DefaultMQProducer("ExampleProducerGroup");
// Launch producer
producer.start();
int totalMessagesToSend = 100;
for (int i = 0; i < totalMessagesToSend; i++) {
Message message = new Message("TestTopic", ("Hello scheduled message " + i).getBytes());
// This message will be delivered to consumer 10 seconds later.
message.setDelayTimeLevel(3);
// Send the message
producer.send(message);
}
// Shutdown producer after use.
producer.shutdown();
}
}
3.验证
您应该看到消息比他们的存储时间晚了大约10秒钟。
批次
1.批量发送消息提高了传递小消息的性能
使用限制:
相同批次的消息应具有:相同的主题,相同的waitStoreMsgOK和没有计划支持,另外一批message的总大小不能超过1MiB。
String topic = "BatchTest";
List messages = new ArrayList<>();
messages.add(new Message(topic, "TagA", "OrderID001", "Hello world 0".getBytes()));
messages.add(new Message(topic, "TagA", "OrderID002", "Hello world 1".getBytes()));
messages.add(new Message(topic, "TagA", "OrderID003", "Hello world 2".getBytes()));
try {
producer.send(messages);
} catch (Exception e) {
e.printStackTrace();
//handle the error
}
当您发送大批量时,复杂性只会增加,您可能无法确定它是否超过了大小限制(1MiB)。
在这个时候,你最好分割列表:
public class ListSplitter implements Iterator<List<Message>> {
private final int SIZE_LIMIT = 1000 * 1000;
private final List messages;
private int currIndex;
public ListSplitter(List messages) {
this.messages = messages;
}
@Override public boolean hasNext() {
return currIndex < messages.size();
}
@Override public List next() {
int nextIndex = currIndex;
int totalSize = 0;
for (; nextIndex < messages.size(); nextIndex++) {
Message message = messages.get(nextIndex);
int tmpSize = message.getTopic().length() + message.getBody().length;
Map properties = message.getProperties();
for (Map.Entry entry : properties.entrySet()) {
tmpSize += entry.getKey().length() + entry.getValue().length();
}
tmpSize = tmpSize + 20; //for log overhead
if (tmpSize > SIZE_LIMIT) {
//it is unexpected that single message exceeds the SIZE_LIMIT
//here just let it go, otherwise it will block the splitting process
if (nextIndex - currIndex == 0) {
//if the next sublist has no element, add this one and then break, otherwise just break
nextIndex++;
}
break;
}
if (tmpSize + totalSize > SIZE_LIMIT) {
break;
} else {
totalSize += tmpSize;
}
}
List subList = messages.subList(currIndex, nextIndex);
currIndex = nextIndex;
return subList;
}
}
//then you could split the large list into small ones:
ListSplitter splitter = new ListSplitter(messages);
while (splitter.hasNext()) {
try {
List listItem = splitter.next();
producer.send(listItem);
} catch (Exception e) {
e.printStackTrace();
//handle the error
}
}
过滤器示例
使用限制:只有使用push consumer才能通过SQL选择消息。
public void subscribe(final String topic, final MessageSelector messageSelector)
在大多数情况下,标签是一个简单有用的设计,用于选择所需的消息。例如:
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(“CID_EXAMPLE”);
consumer.subscribe(“TOPIC”, “TAGA || TAGB || TAGC”);
一个消息只能有一个标签,在这种情况下,您可以使用SQL表达式过滤消息。
在RocketMQ定义的语法下,您可以实现一些有趣的逻辑。
RocketMQ只定义了一些基本的语法来支持这个功能。您也可以轻松数值比较:如>,>=,<,<=,BETWEEN,=;
字符比较:如=,<>,IN;
IS NULL或IS NOT NULL;
逻辑AND,OR,NOT,
常数类型有:
数字,如123,3.1415;
字符,如“abc”,必须用单引号;
NULL,特殊常数;
布尔值,TRUE或FALSE;
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4");
// only subsribe messages have property a, also a >=0 and a <= 3
consumer.subscribe("TopicTest", MessageSelector.bySql("a between 0 and 3");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) {
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
OpenMessaging包括建立行业指南和消息传递,流式传输规范为金融,电子商务,物联网和大数据区域提供了一个共同的框架。设计原则是分布式异构环境中面向云,简单,灵活和语言无关的原则。符合这些规范将有可能在所有主要平台和操作系统上开发异构消息应用程序。
public class OMSProducer {
public static void main(String[] args) {
final MessagingAccessPoint messagingAccessPoint = MessagingAccessPointFactory
.getMessagingAccessPoint("openmessaging:rocketmq://IP1:9876,IP2:9876/namespace");
final Producer producer = messagingAccessPoint.createProducer();
messagingAccessPoint.startup();
System.out.printf("MessagingAccessPoint startup OK%n");
producer.startup();
System.out.printf("Producer startup OK%n");
{
Message message = producer.createBytesMessageToTopic("OMS_HELLO_TOPIC", "OMS_HELLO_BODY".getBytes(Charset.forName("UTF-8")));
SendResult sendResult = producer.send(message);
System.out.printf("Send sync message OK, msgId: %s%n", sendResult.messageId());
}
{
final Promise result = producer.sendAsync(producer.createBytesMessageToTopic("OMS_HELLO_TOPIC", "OMS_HELLO_BODY".getBytes(Charset.forName("UTF-8"))));
result.addListener(new PromiseListener() {
@Override
public void operationCompleted(Promise promise) {
System.out.printf("Send async message OK, msgId: %s%n", promise.get().messageId());
}
@Override
public void operationFailed(Promise promise) {
System.out.printf("Send async message Failed, error: %s%n", promise.getThrowable().getMessage());
}
});
}
{
producer.sendOneway(producer.createBytesMessageToTopic("OMS_HELLO_TOPIC", "OMS_HELLO_BODY".getBytes(Charset.forName("UTF-8"))));
System.out.printf("Send oneway message OK%n");
}
producer.shutdown();
messagingAccessPoint.shutdown();
}
}
使用OMS PullConsumer来轮询来自指定队列的消息。
public class OMSPullConsumer {
public static void main(String[] args) {
final MessagingAccessPoint messagingAccessPoint = MessagingAccessPointFactory
.getMessagingAccessPoint("openmessaging:rocketmq://IP1:9876,IP2:9876/namespace");
final PullConsumer consumer = messagingAccessPoint.createPullConsumer("OMS_HELLO_TOPIC",
OMS.newKeyValue().put(NonStandardKeys.CONSUMER_GROUP, "OMS_CONSUMER"));
messagingAccessPoint.startup();
System.out.printf("MessagingAccessPoint startup OK%n");
consumer.startup();
System.out.printf("Consumer startup OK%n");
Message message = consumer.poll();
if (message != null) {
String msgId = message.headers().getString(MessageHeader.MESSAGE_ID);
System.out.printf("Received one message: %s%n", msgId);
consumer.ack(msgId);
}
consumer.shutdown();
messagingAccessPoint.shutdown();
}
}
public class OMSPushConsumer {
public static void main(String[] args) {
final MessagingAccessPoint messagingAccessPoint = MessagingAccessPointFactory
.getMessagingAccessPoint("openmessaging:rocketmq://IP1:9876,IP2:9876/namespace");
final PushConsumer consumer = messagingAccessPoint.
createPushConsumer(OMS.newKeyValue().put(NonStandardKeys.CONSUMER_GROUP, "OMS_CONSUMER"));
messagingAccessPoint.startup();
System.out.printf("MessagingAccessPoint startup OK%n");
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
consumer.shutdown();
messagingAccessPoint.shutdown();
}
}));
consumer.attachQueue("OMS_HELLO_TOPIC", new MessageListener() {
@Override
public void onMessage(final Message message, final ReceivedMessageContext context) {
System.out.printf("Received one message: %s%n", message.headers().getString(MessageHeader.MESSAGE_ID));
context.ack();
}
});
}
}
SubscriptionData 订阅信息的封装
MessageModle 消费模式,默认为Cluster
本地模式 Local
Cluster
更新offset2Broker
ConsumerOffsetManager管理offset