公司近期项目需用到RocketMQ消息中间件,学习了入门技巧,这里将其记录下来
由于条件有限,就装了个虚拟机,搭了个单机版的学习
了解RocketMQ的术语:https://blog.youkuaiyun.com/sinat_28434649/article/details/80045583
一、先把jdk环境装上,作者使用的jdk1.8
下载地址:https://www.oracle.com/technetwork/cn/java/javase/downloads/jdk8-downloads-2133151-zhs.html
jdk安装教程问度娘......
二、下载RoketMQ,作者直接下载编译好的4.2.0Binary版本,有需要的可以下载源码自己编译
下载地址:http://rocketmq.apache.org/dowloading/releases/
三、安装RocketMQ
将下载好rocketMq上传至服务器
unzip rocketmq-all-4.2.0-bin-release.zip 解压安装包
根据自己机子的情况,修改NameServer和Broker的启动脚本(在bin目录下的runserver.sh和runbroker.sh)
修改runserver.sh
修改runbroker.sh
四、启动服务
启动前先看下防火墙开启对应端口没,这步很重要,不然会出现一些连接超时的错误。作为学习,作者直接将防火墙关了
启动mqnamesrv,nameService的默认端口是9876
查看mqnamesrv启动日志tail ~/logs/rocketmqlogs/namesrv.log
启动mqbroker, -n 参数需指明nameService的ip,就是我们上面启动的mqnamesrv
查看mqbroker启动日志 tail ~/logs/rocketmqlogs/broker.log
如果出现 Please set the JAVA_HOME variable in your environment, We need java(x64)!,是java的环境变量配置得有问题
解决办法:1, vi /etc/profile 修改java的环境变量如下,一定要export JAVA_HOME,不然mq找不到JAVA_HOME
2, 简单暴力,修改bin目录下的runserver.sh和runbroker.sh的启动脚本,如下
把1行JAVA_HOME修改成自己jdk的路径,把2,3行的代码注释掉,重新启动mqnamesrv,mqbroker就可以了
五、目前单机版的mq就启动好了,可以示例程序进行发送和接收消息了
export NAMESRV_ADDR=localhost:9876
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer
也可用命令查看和创建topic
sh bin/mqadmin TopicList -n 192.168.126.157:9876
sh bin/mqadmin updateTopic -n 192.168.126.157:9876 -b 192.168.126.157:10911 -t topic_dev
关闭NameServer和Broker
sh bin/mqshutdown broker
sh bin/mqshutdown namesrv
六、安装mq的后台管理项目rocketmq-console
教程:https://my.oschina.net/buru1kan/blog/1814356
七、整合SpringBoot
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
生产者服务层
@Service
public class ProducerServiceImpl implements ProducerService {
private DefaultMQProducer defaultMQProducer;
/**
* 创建topic
*/
@Override
public void createTopic(String topic) throws MQClientException {
// key: 系统已经存在的一个topic的名称
defaultMQProducer.createTopic("TopicTest",topic,8);
}
/**
* 发送消息
*/
@Override
public void sendMsg(String topic, String msg) throws Exception {
Message message = new Message(topic,msg.getBytes());
SendResult send = defaultMQProducer.send(message);
System.out.println(send.getMsgId());
}
/**
* 初始化生产者
*/
@PostConstruct
public void initProducer(){
defaultMQProducer = new DefaultMQProducer();
// 设置生产组
defaultMQProducer.setProducerGroup("LvTestGroup1");
// 设置
defaultMQProducer.setInstanceName("instance1");
// 设置nameService地址,多个用';'隔开
defaultMQProducer.setNamesrvAddr("192.168.126.157:9876");
// 设置当产出消息失败时,重试的次数
defaultMQProducer.setRetryTimesWhenSendFailed(3);
try {
defaultMQProducer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
}
/**
* 摧毁生产者
*/
@PreDestroy
public void destroyProducer(){
if(defaultMQProducer == null){
defaultMQProducer.shutdown();
}
}
}
消费者服务层
@Service
public class ConsumerServiceImpl implements ConsumerService {
// 主动拉取型
// private DefaultMQPullConsumer defaultMQPullConsumer
/**
* 被动推送型
*/
private DefaultMQPushConsumer defaultMQPushConsumer;
/**
* 初始化消费者
*/
@PostConstruct
public void initConsumer() {
defaultMQPushConsumer = new DefaultMQPushConsumer();
// 设置消费组 用于把多个Consumer 组织到一起
defaultMQPushConsumer.setConsumerGroup("LvTestGroup");
// 设置nameService地址
defaultMQPushConsumer.setNamesrvAddr("192.168.126.157:9876");
// 设置消费模式
// 默认集群模式:同一个组里所有的Consumer消费的内容合起来才是所订阅Topic 内容的整体
// 广播模式: 同一个组里每个Consumer都能消费到所订阅Topic的全部消息
defaultMQPushConsumer.setMessageModel(MessageModel.CLUSTERING);
try {
// * 标签通配符,表示所有标签,也可以直接写某个标签的名称
defaultMQPushConsumer.subscribe("lv_topic", "*");
// MessageListenerOrderly型消费监听器,保证并发消费时,同一个Consumer Queue不会被并发消费
defaultMQPushConsumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
System.out.println(context.getMessageQueue().getQueueId());
for (MessageExt msg : msgs) {
System.out.println("消费队列queueId=" + msg.getQueueId() + "的消息:" + new String(msg.getBody()));
/**
* 业务逻辑
*/
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
defaultMQPushConsumer.start();
System.out.println("lv_topic 消费者启动成功...");
} catch (MQClientException e) {
e.printStackTrace();
}
}
/**
* 关闭消费者
*/
@PreDestroy
public void destroyConsumer() {
if (defaultMQPushConsumer != null) {
defaultMQPushConsumer.shutdown();
}
}
}
controller调用层
@RestController
@RequestMapping("mq")
@Api(value = "compShopService", description = "xxx")
public class MqRest {
@Autowired
private ProducerService producerService;
@GetMapping("send")
@ApiOperation(value = "发送消息", notes = "xxx")
public InvokeResult send(String topic,String msg) throws Exception {
producerService.sendMsg(topic,msg);
return InvokeResult.success();
}
@GetMapping("createTopic")
@ApiOperation(value = "创建topic", notes = "xxx")
public InvokeResult detail(String topic) throws MQClientException {
producerService.createTopic(topic);
return InvokeResult.success();
}
}
一个简单的demo就完成了,这种方式比较简单,也足够灵活,但不是很“ Spring Style"
遇到最大的问题就是,启动broker时,RocketMQ会指定为内网地址,使用的是172.17.0.3。会导致外网生产者无法连接到broker。报错信息:
org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to <172.17.0.3:10909> failed
conf/broker.conf文件增加配置项brokerIP1 = xxx.xxx.xxx.xxx
。这里的ip地址指定为外网地址。
并且docker启动时需要增加命令参数-c ../conf/broker.conf
。
模板RocketMQTemplate的使用
可以用统一的接口模板RocketMQTemplate进行操作,如下:
rocketmq.nameServer=192.168.126.129:9876
rocketmq.producer.group=lvJieTestGroup
// 生产者代码
@Resource
private RocketMQTemplate rocketMQTemplate;
@Override
public void sendSimpleMsg(String msg) {
//封装消息体
Message<String> message = MessageBuilder
.withPayload(msg)
.setHeader(RocketMQHeaders.TAGS,"自定义TAGS")
.setHeader(RocketMQHeaders.KEYS,"自定义KEY")
.setHeader(RocketMQHeaders.FLAG,"自定义FLAG")
.build();
rocketMQTemplate.send("topic:tag", message);
}
// 消费者代码
@Service
@RocketMQMessageListener(
topic = "topic",//指定toipc
consumerGroup = "defaultGroupName"//指定消费者组
)
public class DefaultConsumerListener implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt messageExt) {
String tags = messageExt.getTags();
String topic = messageExt.getTopic();
log.info("topic->" + topic + "__tags->" + tags + ",received message: {}", new String(messageExt.getBody()));
}
}