入门文章:
看了那么多,讲的比较好的: https://m.aliyun.com/yunqi/articles/66110
比较全面的:https://blog.youkuaiyun.com/column/details/learningrocketmq.html
rocketmq中的节点
NameServer
NameServer是一个几乎无状态节点,所有人信息都一致,可集群部署,节点之间无任何信息同步。
记录消息元数据在那一个Broker上,通过NameServer将消息发送和消费指定到某一个具体的Broker上。
Broker
Broker 部署相对复杂,Broker分为Master和Slave,一个Master可以对应多个Slave,但是一个Slave只能对应一个Master,Master与Slave的对应关系通过指定相同的BrokerName,不同的BrokerId来定义,BrokerId为0表示Master,非0表示Slave。Master也可以部署多个。每个Broker与NameServer集群中的所有节点建立长连接,定时注册 Topic 信息到所有 NameServer。
Producer
Producer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由信息,并向提供Topic服务的Broker Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署。
Consumer
Consumer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,订阅规则由Broker配置决定。
物理部署图:
集群部属 rocketmq 4.3.0
前提:安装 64bit jdk1.8+
机器分配:
192.168.121.129 A nameserver
192.168.121.130 B broker1_master
192.168.121.131 C broker1_slave
192.168.121.132 D broker2_master
192.168.121.143 E broker2_slave
修改hosts
192.168.121.129 A
192.168.121.130 B
192.168.121.131 C
192.168.121.132 D
192.168.121.143 E
下载已经编译的bin-release 版本:
https://www.apache.org/dyn/closer.cgi?path=rocketmq/4.3.0/rocketmq-all-4.3.0-bin-release.zip
解压:
# unzip rocketmq-all-4.3.0-bin-release.zip
目录说明
benchmark 用于基准测试
bin 可执行的命令
conf 配置文件(包含broker 配置,和日志文件的配置,里面包含了主从同步/异步的配置模板等)
lib 依赖的包
LICENSE
NOTICE
README.md
修改配置 (参考官网配置: http://rocketmq.apache.org/docs/rmq-deployment/)
使用conf/2m-2s-async配置模板(2主2从,异步组刷盘方式):
broker-a.properties
broker-a-s.properties
broker-b.properties
broker-b-s.properties
4个文件添加如下配置,指定nameserver,其它的参考官网解释不用修改。
namesrvAddr=a:9876
修改jvm 启动参数(http://rocketmq.apache.org/docs/system-config/)
修改内存大小
nameserver 启动文件 bin/runserver.sh
修改内存大小 -Xms512m -Xmx512m -Xmn256m
broker 启动文件 bin/runbroker.sh
修改内存大小 -Xms512m -Xmx512m -Xmn256m
注:runserver.sh runbroker.sh 有 一个 -Xloggc 配置,指定gc日志的,可以指定自己的目录
修改rocketmq 日志目录,配置文件在 conf/ 文件夹下:
默认在目录:${user.home}/logs/rocketmqlogs 下,这里使用默认的
创建日志目录:
cd ~ && mkdir -p logs/rocketmqlogs
把配置好的分发到各机器上:
scp -r rocketmq root@B:/root
scp -r rocketmq root@C:/root
scp -r rocketmq root@D:/root
scp -r rocketmq root@E:/root
启动:
启动nameserver,在机器A 上执行
# nohup sh bin/mqnamesrv &
查看启动的进程:
# jps
2592 Jps
2548 NamesrvStartup
分别启动broker
b服务器上启动:
# nohup sh bin/mqbroker -c conf/2m-2s-async/broker-a.properties &
c服务器上启动:
# nohup sh bin/mqbroker -c conf/2m-2s-async/broker-a-s.properties &
d服务器上启动:
# nohup sh bin/mqbroker -c conf/2m-2s-async/broker-b.properties &
e服务器上启动:
# nohup sh bin/mqbroker -c conf/2m-2s-async/broker-b-s.properties &
都是java进程,可以使用jps查看是否启动
注:rocketmq 进行系统优化,使用NOOP调度算法
$ sudo ~/rocketmq/bin/os.sh
测试:
代码
/**
* mq 配置信息
*/
public class RocketMQConfigure {
/**
* name server 地址,多个用分号隔开
*/
public static String NAMESRV_ADDR = "192.168.121.129:9876";
/**
* broker 组名
*/
public static String BROKER_GROUP = "test_broker_group";
/**
* consumer 组名
*/
public static String CONSUMER_GROUP = "test_consumer_group";
/**
* 消息主题
*/
public static String MESSAGE_TOPIC ="test_message_topic";
/**
* 消息key 前缀,消息key一般是业务的主键
*/
public static String MESSAGE_KEY_PREFIX = "message_key_prefix:";
/**
* 消息tag
*/
public static String MESSAGE_TAG_A ="test_tag_a";
public static String MESSAGE_TAG_B ="test_tag_b";
/**
* 默认编码格式
*/
public static String DEFAULT_CHARSET="UTF-8";
}
生产者:
/**
* 简单生产者
*/
public class SimpleProducer {
public static void main(String[] args) throws Exception{
//使用 默认生产者,参考官网
DefaultMQProducer producer = new DefaultMQProducer(RocketMQConfigure.BROKER_GROUP);
//设置name server 地址
producer.setNamesrvAddr(RocketMQConfigure.NAMESRV_ADDR);
try {
//启动生产者
producer.start();
for (int i = 0; i < 10; i++) {
//创建一个消息,指定 topic , tags, 消息体
Message msg = new Message(RocketMQConfigure.MESSAGE_TOPIC ,RocketMQConfigure.MESSAGE_TAG_A,
("Hello RocketMQ " +i).getBytes(RocketMQConfigure.DEFAULT_CHARSET));
//发送消息到一个broker,默认是 SYNC即可靠消息传输。还有异步传输,单向传输 可以参考官网
SendResult sendResult = producer.send(msg);
System.out.printf("%s%n", sendResult);
}
} catch (MQClientException e) {
e.printStackTrace();
} finally {
producer.shutdown();
}
}
}
消费者:
public class SimpleConsumer {
public static void main(String[] args) throws Exception{
//使用 默认生产者,参考官网
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(RocketMQConfigure.CONSUMER_GROUP);
consumer.setNamesrvAddr(RocketMQConfigure.NAMESRV_ADDR);
//订阅消息,指定订阅消息的 topic 和 tag
consumer.subscribe(RocketMQConfigure.MESSAGE_TOPIC, RocketMQConfigure.MESSAGE_TAG_A);
//设置集群消息
consumer.setMessageModel(MessageModel.CLUSTERING);
//设置消息监听,这里使用并发的监听 MessageListenerConcurrently,还有一个顺序的监听 MessageListenerOrderly,可以查看源码
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
msgs.forEach(msg->{
System.out.println("消费消息:"+new String(msg.getBody()));
});
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
}
}
测试结果:
先启动消费者
再启动生产者,生产者打印输出如下:
SendResult [sendStatus=SEND_OK, msgId=C0A8E28000002A9F0000000000000714, messageQueue=MessageQueue [topic=test_message_topic, brokerName=broker-a, queueId=0], queueOffset=4]
SendResult [sendStatus=SEND_OK, msgId=C0A8E28000002A9F00000000000007AB, messageQueue=MessageQueue [topic=test_message_topic, brokerName=broker-a, queueId=1], queueOffset=4]
SendResult [sendStatus=SEND_OK, msgId=C0A8E28000002A9F0000000000000842, messageQueue=MessageQueue [topic=test_message_topic, brokerName=broker-a, queueId=2], queueOffset=2]
SendResult [sendStatus=SEND_OK, msgId=C0A8E28000002A9F00000000000008D9, messageQueue=MessageQueue [topic=test_message_topic, brokerName=broker-a, queueId=3], queueOffset=2]
SendResult [sendStatus=SEND_OK, msgId=C0A8E28F00002A9F00000000000004B8, messageQueue=MessageQueue [topic=test_message_topic, brokerName=broker-b, queueId=0], queueOffset=2]
SendResult [sendStatus=SEND_OK, msgId=C0A8E28F00002A9F000000000000054F, messageQueue=MessageQueue [topic=test_message_topic, brokerName=broker-b, queueId=1], queueOffset=2]
SendResult [sendStatus=SEND_OK, msgId=C0A8E28F00002A9F00000000000005E6, messageQueue=MessageQueue [topic=test_message_topic, brokerName=broker-b, queueId=2], queueOffset=2]
SendResult [sendStatus=SEND_OK, msgId=C0A8E28F00002A9F000000000000067D, messageQueue=MessageQueue [topic=test_message_topic, brokerName=broker-b, queueId=3], queueOffset=2]
SendResult [sendStatus=SEND_OK, msgId=C0A8E28000002A9F0000000000000970, messageQueue=MessageQueue [topic=test_message_topic, brokerName=broker-a, queueId=0], queueOffset=5]
SendResult [sendStatus=SEND_OK, msgId=C0A8E28000002A9F0000000000000A07, messageQueue=MessageQueue [topic=test_message_topic, brokerName=broker-a, queueId=1], queueOffset=5]
测试总结:
1、从日志中可以看出,每个消息被分配到不同的 MessageQueue。
猜测:每个broker 管理多个 MessageQueue,这些MessageQueue 又通过 message topic 分组。所有就有了 MessageQueue [topic=test_message_topic, brokerName=broker-a, queueId=1] 类似这样的结构(知道的可以在留言)。
2、在consumer 中消费消息也可以通过定义 MessageQueue 拉取消息消费,代码如下:
DefaultMQPullConsumer pullConsumer = new DefaultMQPullConsumer();
pullConsumer.fetchConsumeOffset(MessageQueue mq, boolean fromStore);
3、通过这两个方法获取 topic 的 MessageQueue,返回的都是Set<MessageQueue>
pullConsumer.fetchMessageQueuesInBalance(String topic)
pullConsumer.fetchSubscribeMessageQueues(String topic)
注:
1、如果提示没有topic 可以手动创建topic
创建一个topic (mqadmin 参考官网 http://rocketmq.apache.org/docs/cli-admin-tool/)
# ./mqadmin updateTopic -n 192.168.121.129:9876 -c DefaultCluster -t test_message_topic
2、关闭防火墙
rocketmq 设计
数据存储结构:
consumerQueue 存储的信息是消息在commitlong中位置的信息如下:
8字节 commitlog offset
4字节 消息大小
8字节 MessageTag hashcode
发送消息: topic 被分配到多个broker(nameserver 会记录topic 分配到的brocker上),在broker当消息被写到commitlog 后,会在topic 分配的一个consumerQueue写上消息的信息(写的方式有几种),消费都通过写入consumerQueue的信息就可以打到commitlog中对应的真实消息。
消费消息:
consume消费消息时会先从nameserver 获取topic被分配的broker,然后遍历broker 的 consumer Queue,在Broker端进行Message Tag比对(这里比的是 tag 的hash code值),如果存储的Message Tag与订阅的Message Tag不符合,则跳过,继续比对下一个,符合则传输给Consumer。
Consumer收到过滤后的消息后,再比对MessageTag字符串。
注:这里只是通过 tag 获取消息,后面还有其它的获取消息的方式。
优点:
1、consumer Queue 存储的是messageTag 的hashCode,这样存储定长,节约空间
2、过滤不访问commitlog,过滤快,消息堆积过滤也很快
3、consumer 会对消息的tag值的过虑,解决了hash冲突
可见rocketmq 的负载是通过consumer Queue来完成的。发送消息消息被分配到brocker 的consumer Queue上,消费消息时遍历consumer Queue。
其它的特点参考: https://m.aliyun.com/yunqi/articles/66110