RocketMQ教程第一篇:第一个范例

[前言:本教程基于RocketMQ4.2,项目基于maven构建,使用操作系统为*nix]

今天我们要介绍的就是RocketMQ,它是阿里巴巴在2012年开源的分布式消息中间件,目前已经捐赠给Apache基金会,在2016年11月成为Apache孵化项目。

在最开始,来介绍一下什么是消息队列,比如一个用户注册后,我们可能需要更新很多统计信息,比如用户总数、当天注册用户数、当月注册用户数,有些网站还会给这个用户发邮件,会给用户注册im账号,这些操作可能比较耗时,那怎么办呢?

我们可以通过消息队列来进行解耦,在用户注册后,我们把用户的信息发送到消息队列中,然后其他功能只需要不断的从消息队列中获取数据,然后进行相关的操作即可。

这里可以看到,消息队列至少有两个好处:
1.解耦了各个模块,让各个模块可以独立开发和部署。
2.减少主流程的步骤,非核心流程操作不会阻塞主流程。

那么都有哪些消息队列可用呢?最简单的消息中间件可能要数Redis,但是它不是一个传统意义上的消息队列,它缺少可靠性保证,它更注重的是性能,但是传统意义上的消息队列不仅要注重性能,还要关注其他几个点。

消息队列需要关注的点有:
第一,消息的可靠性。如果纯内存的消息队列,那么就可能会因为程序突然中止而导致数据丢失,所以一般消息队列都会记录到磁盘来保证其可靠性。
第二,消息的顺序。比如我们发送两个消息,比如消息a是修改用户名为aa,消息b是修改用户名为bb,正确的顺序是先发送消息a,然后发送消息b,如果我们的发送顺序是正确的,但是我们的消息队列因为网络等原因让消息b先到达,那么就会产生未知的bug。

首先来科普几个基础概念:
第一个概念即topic,也就是"主题",它是用来区分消息的类型,比如我们在用户注册时发的消息和用户退出时发的消息,应该是两种类型的消息,这两种消息是不相关的,我们如何区分它们呢?我们可以通过"主题"来区分不同的消息。
第二个概念即producer,也就是"生产者",它用来生产消息。比如我们要在用户注册时发一个消息,那么我们就可以写一个SignupProducer来作为消息的生产者。
第三个概念即consumer,也就是"消费者",它用来消费消息。比如我们要在用户注册的时候修改统计数据,我们就可以把这个代码写到SignupConsumer来作为消息的消费者。
第四个概念即Broker,它相当于一个中转站,它从生产者获取消息,然后把消息分发到消费者,需要说明的是,broker需要有一些确认机制,它需要确保我们的消费者正确的收到消息。

对于RocketMQ来说,还有一个概念,即NameServer,它是命名服务器,即RocketMQ的寻址服务,它用于把Broker的路由信息做聚合,客户端通过命名服务器来获取指定topic的路由信息,从而决定对哪些broker做连接。

RocketMQ部署【双Master方式】
3.1. 服务器环境

序号IP用户名密码角色模式
1192.168.100.24rootnameServer1,brokerServer1Master1
2192.168.100.25rootnameServer2,brokerServer2Master2


3.2. Hosts添加信息

IPNAME
192.168.100.24rocketmq-nameserver1
192.168.100.24rocketmq-master1
192.168.100.25rocketmq-nameserver2
192.168.100.25rocketmq-master2


# vi /etc/hosts
3.3. 上传解压【两台机器】
# 上传alibaba-rocketmq-3.2.6.tar.gz文件至/usr/local
# tar -zxvf alibaba-rocketmq-3.2.6.tar.gz -C /usr/local
# mv alibaba-rocketmq alibaba-rocketmq-3.2.6
# ln -s alibaba-rocketmq-3.2.6 rocketmq
ll /usr/local
3.4. 创建存储路径【两台机器】
# mkdir /usr/local/rocketmq/store
# mkdir /usr/local/rocketmq/store/commitlog
# mkdir /usr/local/rocketmq/store/consumequeue
# mkdir /usr/local/rocketmq/store/index
3.5. RocketMQ配置文件【两台机器】
# vim /usr/local/rocketmq/conf/2m-noslave/broker-a.properties
# vim /usr/local/rocketmq/conf/2m-noslave/broker-b.properties
#所属集群名字
brokerClusterName=rocketmq-cluster
#broker名字,注意此处不同的配置文件填写的不一样
brokerName=broker-a|broker-b
#0 表示 Master, >0 表示 Slave
brokerId=0
#nameServer地址,分号分割

namesrvAddr=rocketmq-nameserver1:9876;rocketmq-nameserver2:9876
#在发送消息时,自动创建服务器不存在的topic,默认创建的队列数
defaultTopicQueueNums=4
#是否允许 Broker 自动创建Topic,建议线下开启,线上关闭
autoCreateTopicEnable=true
#是否允许 Broker 自动创建订阅组,建议线下开启,线上关闭
autoCreateSubscriptionGroup=true
#Broker 对外服务的监听端口
listenPort=10911
#删除文件时间点,默认凌晨 4点
deleteWhen=04
#文件保留时间,默认 48 小时
fileReservedTime=120
#commitLog每个文件的大小默认1G
mapedFileSizeCommitLog=1073741824
#ConsumeQueue每个文件默认存30W条,根据业务情况调整
mapedFileSizeConsumeQueue=300000
#destroyMapedFileIntervalForcibly=120000
#redeleteHangedFileInterval=120000
#检测物理文件磁盘空间
diskMaxUsedSpaceRatio=88
#存储路径
storePathRootDir=/usr/local/rocketmq/store
#commitLog 存储路径
storePathCommitLog=/usr/local/rocketmq/store/commitlog
#消费队列存储路径存储路径
storePathConsumeQueue=/usr/local/rocketmq/store/consumequeue
#消息索引存储路径
storePathIndex=/usr/local/rocketmq/store/index
#checkpoint 文件存储路径
storeCheckpoint=/usr/local/rocketmq/store/checkpoint
#abort 文件存储路径
abortFile=/usr/local/rocketmq/store/abort
#限制的消息大小
maxMessageSize=65536
#flushCommitLogLeastPages=4
#flushConsumeQueueLeastPages=2
#flushCommitLogThoroughInterval=10000
#flushConsumeQueueThoroughInterval=60000
#Broker 的角色
#- ASYNC_MASTER 异步复制Master
#- SYNC_MASTER 同步双写Master
#- SLAVE
brokerRole=ASYNC_MASTER
#刷盘方式
#- ASYNC_FLUSH 异步刷盘
#- SYNC_FLUSH 同步刷盘
flushDiskType=ASYNC_FLUSH
#checkTransactionMessageEnable=false

#发消息线程池数量
#sendMessageThreadPoolNums=128
#拉消息线程池数量
#pullMessageThreadPoolNums=128
3.6. 修改日志配置文件【两台机器】
# mkdir -p /usr/local/rocketmq/logs
# cd /usr/local/rocketmq/conf && sed -i 's#${user.home}#/usr/local/rocketmq#g'
*.xml
3.7. 修改启动脚本参数【两台机器】
# vim /usr/local/rocketmq/bin/runbroker.sh
#============================================================
==================
# 开发环境JVM Configuration
#============================================================
==================
JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn512m -
XX:PermSize=128m -XX:MaxPermSize=320m"
# vim /usr/local/rocketmq/bin/runserver.sh
JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn512m -
XX:PermSize=128m -XX:MaxPermSize=320m"
3.8. 启动NameServer【两台机器】
# cd /usr/local/rocketmq/bin
# nohup sh mqnamesrv &
3.9. 启动BrokerServer A【192.168.100.24】
# cd /usr/local/rocketmq/bin
# nohup sh mqbroker -c /usr/local/rocketmq/conf/2m-noslave/broker-a.properties >/dev/null 2>&1 &
# netstat -ntlp
# jps
# tail -f -n 500 /usr/local/rocketmq/logs/rocketmqlogs/broker.log
# tail -f -n 500 /usr/local/rocketmq/logs/rocketmqlogs/namesrv.log


3.10. 启动BrokerServer B【192.168.100.25】
# cd /usr/local/rocketmq/bin
# nohup sh mqbroker -c /usr/local/rocketmq/conf/2m-noslave/broker-b.properties >/dev/null 2>&1 &
# netstat -ntlp
# jps
# tail -f -n 500 /usr/local/rocketmq/logs/rocketmqlogs/broker.log
# tail -f -n 500 /usr/local/rocketmq/logs/rocketmqlogs/namesrv.log

package cn.ls.rocketmq.producer;

import com.alibaba.rocketmq.client.producer.DefaultMQProducer;
import com.alibaba.rocketmq.client.producer.SendResult;
import com.alibaba.rocketmq.common.message.Message;

public class Producer {
	public static void main(String[] args) throws Exception {
		 DefaultMQProducer producer = new DefaultMQProducer("mengzhidu-user");
		 producer.setNamesrvAddr("192.168.100.24:9876;192.168.100.25:9876");
		 producer.start();
		 
		 for(int i = 0; i < 5 ; i ++) {
			 Message m = new Message("user","man",String.valueOf(i),new String("lius-"+i).getBytes());
			 SendResult result = producer.send(m);
			 System.out.println("消息id为: "+result.getMsgId() + " 发送状态为:"+result.getSendStatus()+"_"+i);
		 }
		 
	}
}

在生产者范例中,我们制定了消费组为"mengzhidu-user",并且我们制定了命名服务器的地址,然后我们就开始了生产者,然后我们在一个for循环中发送了五次消息,然后我们在生产者中获取了消息的状态。

然后我们新建一个消费者范例,我们写入如下代码:

package cn.ls.rocketmq.consumer;

import java.util.List;

import com.alibaba.rocketmq.client.consumer.DefaultMQPushConsumer;
import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import com.alibaba.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import com.alibaba.rocketmq.client.exception.MQClientException;
import com.alibaba.rocketmq.common.consumer.ConsumeFromWhere;
import com.alibaba.rocketmq.common.message.MessageExt;

public class Consumer {

	public static void main(String[] args) {
		try {
			DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("mengzhidu-user");
			consumer.setNamesrvAddr("192.168.100.24:9876;192.168.100.25:9876");
			consumer.subscribe("user", "man");
			
			//从队列头取还是从最后一个尾取。
			consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
			consumer.registerMessageListener(new MessageListenerConcurrently() {
				@Override
				public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext arg1) {
					// TODO Auto-generated method stub
					MessageExt message = list.get(0);
					try {
						System.out.println("收到消息内容为:" + new String(message.getBody()));
						int a = 1/0;
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
						if(message.getReconsumeTimes() == 2) {
							System.out.println("重试发送2次后,还是失败,记录数据库日志");
							return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
						}else {
							//如果报异常通知mq 重新发送
							return ConsumeConcurrentlyStatus.RECONSUME_LATER;
						}
						
					}
					return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;//通知mq 已经成功执行完成
				}
			});
			consumer.start();
			
		} catch (MQClientException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

我们的消费者也制定了主题为"mengzhidu-user",而且它也指定了命名服务器的地址,然后设置了在收到消息后的处理方式,然后就启动了消费者。

然后我们首先启动消费者,然后我们启动生产者,在一小段时间后,我们会看到消费者端打印如下:

消费者收到消息的内容:辛星-1
消费者收到消息的内容:辛星-2
消费者收到消息的内容:辛星-0
消费者收到消息的内容:辛星-3
消费者收到消息的内容:辛星-4

而生产者端打印如下:

消息id为:  C0A81F5200002A9F0000000000000CB2  发送状态为:SEND_OK
消息id为:  C0A81F5200002A9F0000000000000D34  发送状态为:SEND_OK
消息id为:  C0A81F5200002A9F0000000000000DB6  发送状态为:SEND_OK
消息id为:  C0A81F5200002A9F0000000000000E38  发送状态为:SEND_OK
消息id为:  C0A81F5200002A9F0000000000000EBA  发送状态为:SEND_OK

至此,我们的第一个基于RocketMQ的例子就完成了,不过我们的操作略显粗糙,在之后,我们会做得更加细腻一些。



作者:辛星0913
链接:https://www.jianshu.com/p/888e83b3a046
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值