入门文章:
看了那么多,讲的比较好的: https://m.aliyun.com/yunqi/articles/66110
比较全面的:https://blog.csdn.net/column/details/learningrocketmq.html
集群部属 rocketmq 4.2.0:
前提:安装 64bit jdk1.8+ maven(编译源码使用)
服务器分配
192.168.226.141 A name service
192.168.226.128 B broker1 master
192.168.226.142 C broker1 slave
192.168.226.143 D broker2 master
192.168.226.144 E broker2 slvae
配置各机器机器host
192.168.226.141 A
192.168.226.128 B
192.168.226.142 C
192.168.226.143 D
192.168.226.144 E
下载
https://www.apache.org/dyn/closer.cgi?path=rocketmq/4.2.0/rocketmq-all-4.2.0-source-release.zip
编译
$ unzip rocketmq-all-4.2.0-source-release.zip
$ cd rocketmq-all-4.2.0/
$ mvn -Prelease-all -DskipTests clean install -U
$ cd distribution/target/apache-rocketmq
apache-rocketmq.zip 就是是编译后的可执行文件
把可以执行文件copy 到 ~ 目录下并解压
$ cp apache-rocketmq.zip ~ && unzip apache-rocketmq.zip -d rocketmq
目录说明
benchmark 用于基准测试
bin 可执行的命令
conf 配置文件(包含broker 配置,和日志文件的配置)
lib 依赖的包
LICENSE
NOTICE
README.md
修改配置 (参考官网配置: https://rocketmq.apache.org/docs/rmq-deployment/)
这里使用2个主,每个主有一个备机、异步组刷盘方式 conf/2m-2s-async
主要修改
namesrvAddr=a:9876 #如果有多个,使用 ; 隔开
brokerId=0 #主 broker 此值是0,备机的都大于0, 互为主备的服务 brokerName 相同
brokerRole=ASYNC_MASTER # 备机是SLVAE
修改配置如下: 目录:rocketmq/conf/2m-2s-async
broker-a.properties
brokerClusterName=DefaultCluster
brokerName=broker-a
brokerId=0
deleteWhen=04
fileReservedTime=48
brokerRole=ASYNC_MASTER
flushDiskType=ASYNC_FLUSH
namesrvAddr=a:9876
broker-a-s.properties
brokerClusterName=DefaultCluster
brokerName=broker-a
brokerId=1
deleteWhen=04
fileReservedTime=48
brokerRole=SLAVE
flushDiskType=ASYNC_FLUSH
namesrvAddr=a:9876
broker-b.properties
brokerClusterName=DefaultCluster
brokerName=broker-b
brokerId=0
deleteWhen=04
fileReservedTime=48
brokerRole=ASYNC_MASTER
flushDiskType=ASYNC_FLUSH
namesrvAddr=a:9876
broker-b-s.properties
brokerClusterName=DefaultCluster
brokerName=broker-b
brokerId=1
deleteWhen=04
fileReservedTime=48
brokerRole=SLAVE
flushDiskType=ASYNC_FLUSH
namesrvAddr=a:9876
注意:配置左右不能有空格,在源码中没有字符串 trim
修改jvm 启动参数: 参考官网:http://rocketmq.apache.org/docs/system-config/
$ vim bin/runserver.sh内存大小和GC设置如下
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
JAVA_OPT="${JAVA_OPT} -XX:+AlwaysPreTouch -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m"
修改broker jvm 参数,修改内存大小
$ vim bin/runbroker.sh
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m"
把修改后的rocketmq 发送到各服务器(其它机器省略了)
$ scp -r rocketmq jamin@b:~
在各机器上创建日志目录(日志路径可以看相关配置,使用的是logback记录日志的)
$ cd ~ && mkdir -p logs/rocketmqlogs
启动nameserver,在机器A 上执行
$ nohup sh bin/mqnamesrv &启动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 &
d服务器上启动:
$ 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.226.141: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)