工作需要,得装一个MQ,选择了rocketmq,具体对比一搜很多,其实也就是普通使用,既然大家都说这个好,那就这个吧。
官网的下载地址http://rocketmq.apache.org/dowloading/releases/,现在最新版本是4.5.2,选择rocketmq-all-4.5.2-bin-release.zip,这个是编译好的,下载下来可以直接用。
大概就是这样,上面的logs文件夹是自己加的,里面有broker,nameserver,console的三个日志文件,几个sh脚本也是自己加的,方便启动和停止。
192.168.1.224是我本地装的虚拟机的地址,路径就是装rocketmq的路径了。
注意java代码里配置的ip,即使程序和mq装在同一个机器上,也不能使用localhost,必须写局域网ip才可以!!!
broker_startup.sh
nohup sh bin/mqbroker -n 192.168.1.224:9876 >/home/rocketmq/rocketmq-all-4.5.2-bin-release/logs/broker.log 2>&1 &
sh bin/mqshutdown broker
sh bin/mqshutdown broker
nameserver_startup.sh
nohup sh bin/mqnamesrv >/home/rocketmq/rocketmq-all-4.5.2-bin-release/logs/mqnamesrv.log 2>&1 &
nameserver_shutd.sh
sh bin/mqshutdown namesrv
console_startup.sh
nohup java -jar lib/rocketmq-console-ng-1.0.1.jar >/home/rocketmq/rocketmq-all-4.5.2-bin-release/logs/console.log 2>&1 &
console_shutd.sh写的有点问题,就不贴了。
修改conf/broker.conf
autoCreateTopicEnable=false和autoCreateSubscriptionGroup=false关闭自动创建topic和group。
地址写虚拟机本地ip,不要写localhost。
如果需要可视化的控制台,可以到github上https://github.com/apache/rocketmq-externals下载,有个rocketmq-console的子项目,就是控制台,下载之后修改application.properties,修改这几个就可以了
server.port=12581
rocketmq.config.namesrvAddr=192.168.1.224:9876
rocketmq.config.isVIPChannel=false
pom.xml文件中有个
mvn clean package -Dmaven.test.skip=true
第一次编译花了18分钟。。。。。期间哗哗的报错,最后居然提示编译成功,把编译好的包放到刚才的mq的包的lib路径下,然后将包压缩成tar,上传到服务器,压缩纯粹是为了上传的快,不压缩上传的特别慢。
解压命令
tar -xvf rocketmq-all-4.5.2-bin-release.tar
然后设置环境变量
export NAMESRV_ADDR=192.168.1.224:9876
上面这样设置只是临时生效,要永久生效的话需要修改etc/profile,在最后面加上上面这一句
然后执行刷新生效
source /etc/profile
之后进入到rocketmq的目录,用写好的脚本启动mq,然后执行测试消息
#发送消息
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer
#消费消息
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer
看到消息刷屏了就说明启动的没问题。
然后新建topic和group
#新建topic $位置替换为topic的名字
sh bin/mqadmin updateTopic -n 192.168.1.224:9876 -b 192.168.1.224:10911 -t $
#新建group $位置替换为group的名字
sh bin/mqadmin updateSubGroup -n 192.168.1.224:9876 -b 192.168.1.224:10911 -g $
之后就把9876,10911,12581三个端口视情况放行,修改防火墙设置就行,如果是云服务器,则需要设置安全策略放行。
如果你的机器内存很小,需要修改下面这几个文件
bin/tools.sh,bin/runbroker.sh,bin/runserver.sh
其中都有
JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m"
这样的一行配置,修改成合适的内存大小就行了,执行新建topic和group,以及测试消息的时候会用到bin/tools.sh
到此rocketmq就安装好了,下面是使用的部分。
首先是pom.xml,注意版本号和安装的版本要一致
org.apache.rocketmq
rocketmq-client
4.5.2
配置文件,因为我的生产者和消费者是在同一个应用里,所以在一起,一般都是分开的
rocketMQ.producer.statement.address=192.168.1.224:9876
rocketMQ.producer.statement.group=statement
rocketMQ.producer.statement.instanceName=statement
rocketMQ.consumer.statement.address=192.168.1.224:9876
rocketMQ.consumer.statement.group=statement
rocketMQ.consumer.statement.topic=statement
rocketMQ.consumer.statement.tag=*
配置类,用于在一些地方取值
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MQProperties {
//对账topic
private static String statementTopic;
//对账tag
private static String statementTag;
public static String getStatementTopic() {
return statementTopic;
}
public static String getStatementTag() {
return statementTag;
}
@Value("${rocketMQ.consumer.statement.topic}")
public void setStatementTopic(String statementTopic) {
MQProperties.statementTopic = statementTopic;
}
@Value("${rocketMQ.consumer.statement.tag}")
public void setStatementTag(String statementTag) {
MQProperties.statementTag = statementTag;
}
}
生产者,这里没用@Bean主要是考虑以后要是再加多个生产者,方便用名字区分,然而并不会再加了,感觉多此一举
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Slf4j
@Component
public class MQProducerHandler {
@Value("${rocketMQ.producer.statement.address:}")
private String statementAddress;
@Value("${rocketMQ.producer.statement.group:}")
private String statementGroup;
@Value("${rocketMQ.producer.statement.instanceName:}")
private String statementInstanceName;
private DefaultMQProducer statementProducer;
private boolean isAvailable_statementProducer = false;
@PostConstruct
public void createStatementProducer() {
log.info("StatementProducer 创建中");
statementProducer = new DefaultMQProducer(statementGroup);
statementProducer.setNamesrvAddr(statementAddress);
statementProducer.setInstanceName(statementInstanceName);
statementProducer.setVipChannelEnabled(false);
statementProducer.setRetryTimesWhenSendAsyncFailed(2);//发送失败后重试次数
try {
statementProducer.start();
isAvailable_statementProducer = true;
log.info("StatementProducer 创建成功");
} catch (MQClientException e) {
log.error("StatementProducer 创建失败", e);
}
}
public DefaultMQProducer getStatementProducer() {
return statementProducer;
}
public boolean isAvailable_statementProducer() {
return isAvailable_statementProducer;
}
}
消费者同理
import com.xxx.ccc.service.IEtcStatementService;
import com.xxx.ccc.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
@Slf4j
@Component
public class StatementConsumer {
@Value("${rocketMQ.consumer.statement.address:}")
private String address;
@Value("${rocketMQ.consumer.statement.group:}")
private String group;
@Value("${rocketMQ.consumer.statement.topic:}")
private String topic;
@Value("${rocketMQ.consumer.statement.tag:}")
private String tag;
@Autowired
private IEtcStatementService iEtcStatementService;
private DefaultMQPushConsumer consumer;
@PostConstruct
public void createStatementConsumer() {
try {
log.info("StatementConsumer 创建中");
consumer = new DefaultMQPushConsumer(group);
consumer.setNamesrvAddr(address);
consumer.subscribe(topic, tag);
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
consumer.registerMessageListener((MessageListenerConcurrently) (messageList, context) -> {
try {
for (MessageExt messageExt : messageList) {
String message = new String(messageExt.getBody(), StandardCharsets.UTF_8);
iEtcStatementService.statementConsume(message);
}
} catch (Exception e) {
log.error("StatementConsumer消费失败:" + JsonUtils.serialize(messageList), e);
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
consumer.start();
log.info("StatementConsumer 创建成功");
} catch (MQClientException e) {
log.error("StatementConsumer 创建失败", e);
}
}
}
生产的地方,send方法的重载巨多,最简单的就是传一个string,同步返回,失败了就抛出异常
//推送到队列
DefaultMQProducer statementProducer = mqProducerHandler.getStatementProducer();
String messageKey = "statement_add_" + etcStatement.getId();
Message message = new Message(
MQProperties.getStatementTopic(),
MQProperties.getStatementTag(),
messageKey,
("" + etcStatement.getId()).getBytes(StandardCharsets.UTF_8)
);
try {
//同步发送,发送失败则会抛出异常
statementProducer.send(message);
log.info("消息发送成功:" + messageKey);
} catch (Exception e) {
log.error("消息发送失败:" + messageKey, e);
throw ExpFactory.resp(ErrorResps.ETC_STATEMENT_NULL.get());
}
这样简单的使用还是很容易的。
补一个遇到的坑,装了rocketmq之后我又在虚拟机上装了docker,然后我本地跑的程序就连不上mq了,看控制台
这里的地址应该是1.224,是虚拟机的ip,但是现在显示的是172.17.0.1:10911,这个ip是docker的ip
网上搜了下说是broker自动搜网络,搜错了,所以要在conf文件夹下加一个broker.properties文件,里面就配一个变量
brokerIP1=192.168.1.224
然后修改下启动broker的脚本
nohup sh bin/mqbroker -n 192.168.1.224:9876 -c conf/broker.properties >/home/rocketmq/rocketmq-all-4.5.2-bin-release/logs/broker.log 2>&1 &
加上-c指定配置文件, 这样启动起来网络就对了。