1
.rocketmq 源码下载
git clone https://github.com/apache/rocketmq.git
2. 运行namesrv启动方法 默认端口9876
# run
org.apache.rocketmq.namesrv.NamesrvStartup#main
# 会报错
Please set the ROCKETMQ_HOME variable in your environment to match the location of the RocketMQ installation
# 根据提示 配置ROCKETMQ_HOME 环境变量 工程根目录下找到/rocketmq/distribution 取这个绝对路径
ROCKETMQ_HOME=/Users/IdeaProjects/rocketmq/distribution 如下图
再次启动 OK
3.运行broker 启动方法
# 运行
org.apache.rocketmq.broker.BrokerStartup#main
# 报错 ROCKETMQ_HOME 需要配置环境变量 如上
# 同时 需要告诉broker Namesrv的地址 通过环境变量配置
NAMESRV_ADDR=127.0.0.1:9876
再次启动 ok
抱歉以下未整理 因为图片大小难以控制 仅给自己留个痕
界面工具已经连接到了namesrv
broker 已经连接到了namesrv
新建一个topic
我们上面选的1 这里只出现一个队列 如你设置5 就会有5个队列 这些队列会平分收到的消息 (生产者发送100条,则每个队列20条)
生产者启动后在这里可以查看到
消费者启动后也可以在这里看到
在这里可以看到生产者发的所有消息
消费者启动后不会停止 会一直监听消息队列, 一有新消息产生, 消费者就一直在打印
1.NameServer集群
提供轻量级的服务发现和路由。 每个 NameServer 记录完整的路由信息,提供等效的读写服务,并支持快速存储扩展
2.Broker集群
每个Broker启动时都会将自己注册到NameServer上,其通过提供轻量级的 Topic 和 Queue 机制来处理消息存储,同时支持推(push)和拉(pull)模式以及主从结构的容错机制,Broker Master1 和 Broker Slave1 是主从结构,它们之间会进行数据同步,即 Date Sync。一个Master1可以对应多个Slave1,但是一个Slave1只能对应一个Master1。
3.Producer集群
产生消息的实例,拥有相同 Producer Group 的 Producer 组成一个集群。
4.Consumer集群
接收消息进行消费的实例,拥有相同 Consumer Group 的Consumer 组成一个集群
Producer 与 NameServer 集群中的其中一个节点(随机选择)建立长连接,定期从 NameServer 获取 Topic 路由信息,并向提供 Topic 服务的 Broker Master 建立长连接,且定时向 Broker 发送心跳。Producer 只能将消息发送到 Broker master。
Consumer 则不一样,它同时和提供 Topic 服务的 Master 和 Slave建立长连接,既可以从 Broker Master 订阅消息,也可以从 Broker Slave 订阅消息。
组成
https://github.com/apache/rocketmq-externals/tree/master/rocketmq-docker
在官方git下查看安装文档,这里我们选择使用docker方式.
(如果你不想使用docker,那就不要继续往下看了 右侧 从源码角度启动rocketmq)。
$ git clone https://github.com/apache/rocketmq-externals.git
......
Resolving deltas: 100% (4143/4143), done.
$ cd rocketmq-externals/rocketmq-docker/4.3.0
$ sh play-docker.sh
Sending build context to Docker daemon 12.8kB
Step 1/10 : FROM centos:7
......
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
apache/rocketmq-broker 4.3.0 0741f6251b82 20 seconds ago 380MB
apache/rocketmq-namesrv 4.3.0 2cf4e87c274c 22 seconds ago 380MB
apache/rocketmq-base 4.3.0 a161d2692b67 25 seconds ago 380MB
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7cba766fa7b3 apache/rocketmq-broker:4.3.0 "/bin/sh -c 'cd ${RO鈥?" 6 minutes ago Up 6 minutes 0.0.0.0:10909->10909/tcp, 0.0.0.0:10911->10911/tcp rmqbroker
052284e86012 apache/rocketmq-namesrv:4.3.0 "/bin/sh -c 'cd ${RO鈥?" 6 minutes ago Up 6 minutes 0.0.0.0:9876->9876/tcp rmqnamesrv
生成了三个镜像,其中两个服务NameServer 和 Broker 都是必须运行的。在生成今镜像的同时已经自动启动了。是不是非常简单
1. NameServer服务 默认端口:9876
2. Broker服务 两个端口都要开放:10909(Vip通道) 10911(默认通道)
两个服务都启动后,这里要确认Broker是否已经注册到了NameServer上。
开始测试
生产者和消费者在使用时都只需要连接到NameServer上,NameServer会自动路由。
pom.xml
<rocketmq.version>4.0.0-incubating</rocketmq.version>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>${rocketmq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-common</artifactId>
<version>${rocketmq.version}</version>
</dependency>
生产者
package com.wangwenjian.cloud.rocketmq;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
public class Producer {
public static void main(String[] args) throws Exception{
/**
* 一个应用创建一个Producer,由应用来维护此对象,可以设置为全局对象或者单例<br>
* 注意:ProducerGroupName需要由应用来保证唯一<br>
* ProducerGroup这个概念发送普通的消息时,作用不大,但是发送分布式事务消息时,比较关键,
* 因为服务器会回查这个Group下的任意一个Producer
*/
DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.setInstanceName("Producer");
//不使用VIP通道(10909端口), 使用(10911端口)
producer.setVipChannelEnabled(false);
/**
* Producer对象在使用之前必须要调用start初始化,初始化一次即可<br>
* 一个Producer对象可以发送多个message
* 注意:切记不可以在每次发送消息时,都调用start方法
*/
producer.start();
try {
/**
*
* 1。 Topic 是一种消息的逻辑分类,比如说你有订单类的消息,也有库存类的消息,就需要进行分类,
* 一个是订单 Topic 存放订单相关的消息,一个是库存 Topic 存储库存相关的消息
*
* 2。 Tag 标签可以被认为是对 Topic 进一步细化。一般在相同业务模块中通过引入标签来标记不同用途的消息。
*
* 3。 剩余就是键值对,一个 key 对应一个消息 body。
*/
Message msg = new Message("dingdantopic",// topic
"TagA",// tag
"OrderID001",// key
("Hello MetaQ").getBytes());// body
/**
* 注意:send方法是同步调用,只要不抛异常就标识成功。但是发送成功也可会有多种状态,
* 例如消息写入Master成功,但是Slave不成功,这种情况消息属于成功,但是对于个别应用如果对消息可靠性要求极高,
* 需要对这种情况做处理。另外,消息可能会存在发送失败的情况,失败重试由应用来处理。
*/
SendResult sendResult = producer.send(msg);
System.out.println(sendResult);
} catch (Exception e) {
e.printStackTrace();
}
/**
* 应用退出时,要调用shutdown来清理资源,关闭网络连接,从MetaQ服务器上注销自己
* 注意:我们建议应用在JBOSS、Tomcat等容器的退出钩子里调用shutdown方法
*/
producer.shutdown();
}
}
消费者
package com.wangwenjian.cloud.rocketmq;
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 Consumer {
/**
* 当前例子是PushConsumer用法,使用方式给用户感觉是消息从RocketMQ服务器推到了应用客户端。<br>
* 但是实际PushConsumer内部是使用长轮询Pull方式从MetaQ服务器拉消息,然后再回调用户Listener方法<br>
*/
public static void main(String[] args) throws Exception {
/**
* 一个应用创建一个Consumer,由应用来维护此对象,可以设置为全局对象或者单例<br>
* 注意:ConsumerGroupName需要由应用来保证唯一
*/
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConsumerGroupName");
/**
* 设置NameServer地址,此处应改为实际NameServer地址,多个地址之间用;分隔
* NameServer的地址必须有,但是也可以通过环境变量的方式设置,不一定非得写死在代码里
*/
consumer.setNamesrvAddr("192.168.56.101:9876");
// instancename 设置与否不影响
// producer.setInstanceName("Consumber");
//不使用VIP通道(10909端口), 使用(10911端口
producer.setVipChannelEnabled(false);
/**
* 订阅指定 userTopic 下tags 分别等于 userTagA 或 userTagB 或 userTagC
*/
consumer.subscribe("userTopic", "userTagA || userTagB || userTagC");
/**
* 订阅指定orderTopic下所有消息<br>
* 注意:一个consumer对象可以订阅多个topic
*/
consumer.subscribe("orderTopic", "*");
// 设置消费者消费方式
consumer.registerMessageListener(new MessageListenerConcurrently() {
/**
* 默认msgs里只有一条消息,可以通过设置consumeMessageBatchMaxSize参数来批量接收消息
*/
@Override
public ConsumeConcurrentlyStatus consumeMessage(
List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
System.out.println(msgs + "" + context);
MessageExt msg = msgs.get(0);
if (msg.getTopic().equals("userTopic")) {
if (msg.getTags() != null && msg.getTags().equals("userTagA")) {
System.out.println(new String(msg.getBody()));
} else if (msg.getTags() != null && msg.getTags().equals("userTagB")) {
System.out.println(new String(msg.getBody()));
} else if (msg.getTags() != null && msg.getTags().equals("userTagC")) {
System.out.println(new String(msg.getBody()));
}
} else if (msg.getTopic().equals("orderTopic")) {
System.out.println(new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
/**
* Consumer对象在使用之前必须要调用start初始化,初始化一次即可<br>
*/
consumer.start();
System.out.println("Consumer Started.");
}
}
3. console(这个是一个web展示界面非必须,不在源码里,有需要可单独下载安装)
$ docker run --name rmqconsole -p 8180:8080 --link rmqnamesrv -e "JAVA_OPTS=-Drocketmq.namesrv.addr=rmqnamesrv:9876" -t styletang/rocketmq-console-ng
打开页面没有报connection exception 并且看到集群下有broker 说明该界面工具已经完美连接到了nameserver。
常见异常
# 我们生产者要使用 usertopic, broker上没有该 topic。
error: No route info of this topic, usertopic
解决方法:
# 自己手动在broker上创建
# 启动broker时加上autoCreateTopicEnable=true